Skip to content

ADR-025 — Итерация активности: Standard Loop и Multi-Instance

Поле Значение
Статус Принято
Версия v.2
Дата 2026-07-22
Владелец Руслан Габитов
Уточняет SAD-001 v.1 §5 / §15.3, ADR-023 v.2 (модель области выполнения, которую это переиспользует), ADR-018 v.1 (граничный перехват для брошенных behavior-событий), ADR-017 v.1 (модель single-writer-выполнения, которую расширяет §2.12), ADR-006 v.3 (бросание/перехват событий)

EN-оригинал — канонический: ADR-025-activity-iteration-loop-and-multi-instance.md. Этот файл — его перевод (twin).

Принято (v.2) — решает, как активность, помеченная loop characteristics, исполняется более одного раза: BPMN'ский Standard Loop (последовательный цикл, управляемый условием) и Multi-Instance (fan-out по кардинальности, последовательный или параллельный, поверх коллекции данных). Прескриптивно и обосновано в объектной модели BPMN 2.0 (§13.3.6–§13.3.7); сопровождающие SRD приземляют это инкрементально на существующем субстрате области выполнения. Имена code-символов намеренно отсутствуют — это grounding принадлежит SRD.

v.2 добавляет §2.12: композитная зациклённая активность итерируется на собственном (off-loop) выполнении активности — декоратор итерации — а не под управляющим кодом, гоняемым на per-instance loop-горутине. Это делает behavior-бросок §2.8 обычным off-loop-эмитом с детерминированным граничным перехватом (v.1-приземление не могло реализовать его корректно), сохраняя при этом инвариант single-writer из ADR-017 v.1 нетронутым. Семантика §2.1–§2.11 не меняется; сдвигается только то, кто управляет композитной итерацией. SRD, приземлившие управляемую loop-горутиной композитную модель, вытесняются и удаляются по завершении пере-приземления декоратора.


1. Контекст и проблема

Сегодня каждая активность в gobpm исполняется ровно один раз на каждый достигающий её токен. BPMN 2.0 позволяет активности нести loop characteristics, которые заставляют её исполняться повторно без дублирования узла на диаграмме. Существуют две формы (§13.3), обе присоединяемы к любой ActivityTask, SubProcess или CallActivity:

  • Standard Loop (§13.3.6) — структурированный while/until цикл: исполнить активность, пере-вычислить булев loopCondition, повторить. Чисто последовательный. Workflow-паттерн WCP-21 (Structured Loop).
  • Multi-Instance (§13.3.7) — исполнить активность фиксированное число раз (решённое один раз при активации), либо последовательно (одну за другой), либо параллельно (все сразу), обычно по разу на каждый элемент коллекции. Workflow-паттерны WCP-13/14 (Multiple Instances), WCP-34/36. Это ответ движка на «сделай X для каждой строки заказа».

Обе — ядро цели BPMN Process Execution Conformance (conformance.md, scope проекта). Массовую поэлементную обработку без них выразить невозможно.

Проблема, которую решает этот ADR, — концептуальная, а не механическая: что значит, что одна активность — это много исполнений — как считаются экземпляры, как каждый экземпляр изолирован от сиблингов, как per-instance данные расщепляются из коллекции и пере-собираются в неё, когда всё это считается завершённым, и как за прогрессом можно наблюдать с границы. Механика (какой существующий рантайм-шов несёт каждую часть) — работа SRD.

Объектная модель (BPMN 2.0, дословно из vendored-извлечения)

Из activities.md §StandardLoopCharacteristics / §MultiInstanceLoopCharacteristics / §ComplexBehaviorDefinition и multi-instance.md:

  • StandardLoopCharacteristics → LoopCharacteristics → BaseElement: testBefore (Boolean, дефолт False), loopCondition (Expression), loopMaximum (Integer).
  • MultiInstanceLoopCharacteristics → LoopCharacteristics → BaseElement: isSequential (Boolean, дефолт False), behavior (MultiInstanceBehavior, дефолт All), loopCardinality (Expression), loopDataInputRef / loopDataOutputRef (ItemAwareElement refs), inputDataItem (DataInput), outputDataItem (DataOutput), complexBehaviorDefinition (0..*), completionCondition (Expression), oneBehaviorEventRef / noneBehaviorEventRef (EventDefinition refs).
  • ComplexBehaviorDefinition → BaseElement: condition (FormalExpression), event (ImplicitThrowEvent).

LoopCharacteristics — разделяемая абстрактная база; две конкретные формы взаимоисключающи на данной активности.


2. Решение

2.1 Одно семейство: loop characteristics как маркер активности

Итерация активности — это маркер, который она несёт, а не новый тип узла. Абстрактный LoopCharacteristics приобретает две конкретные формы — StandardLoopCharacteristics и MultiInstanceLoopCharacteristics — и активность несёт не более одной. Активность без loop characteristics исполняется один раз, ровно как сегодня (изменение строго аддитивно).

Маркер ортогонален тому, чем активность является: одна и та же модель итерации оборачивает Task, Sub-Process или Call Activity. Зациклённый Sub-Process исполняет всё своё тело за итерацию; зациклённый Task исполняет task за итерацию; зациклённая Call Activity запускает дочерний экземпляр за итерацию.

2.2 Каждая итерация изолирована; механизм подбирается под вид активности

Центральное решение: каждая итерация зациклённой активности исполняется в собственном изолированном контексте выполнения, так что per-iteration состояние — текущий элемент, per-iteration loopCounter — никогда не протекает между итерациями. Изоляция — инвариант; механизм — самый дешёвый, удовлетворяющий её для данного вида активности:

  • Листовая активность (Task) итерируется на месте: движок пере-исполняет активность по разу на проход, каждый проход в свежем frame'е выполнения. Frame и так является per-execution границей данных (ADR-010), поэтому новый frame на итерацию и есть изоляция — более тяжёлая конструкция не нужна, а единственный исходящий flow активности проходится один раз, после выхода из цикла.
  • Композитная активность (Sub-Process, Call Activity) итерируется пере-открытием своей дочерней области на итерацию — тот жизненный цикл вложенной области open/drain/close по ADR-023 v.2, который она уже гоняет для своего тела. Последовательная итерация = область для итерации i+1 открывается только после того, как область итерации i дренировалась и закрылась (шов пере-входа); композит проходит свой единственный исходящий flow один раз, после финальной итерации.

Оба механизма разделяют одну форму жизненного цикла — исполнить, проверить продолжение, повторить — и оба позволяют граничному событию на зациклённой активности взвестись один раз и охранять каждую итерацию (желаемая BPMN-семантика: граничный таймер охватывает весь цикл).

Параллельный Multi-Instance — исключение, которому всегда нужна отдельная per-instance область, потому что его экземпляры исполняются конкурентно и не должны разделять состояние токенов или данных; каждый параллельный экземпляр поэтому получает область с отдельной, стабильной идентичностью, выведенной из активности и порядкового номера экземпляра (§2.5), так что сиблинги адресуемы и никогда не сталкиваются. Этот ADR не навязывает более тяжёлую конструкцию последовательным случаям (Standard Loop, последовательный MI), где исполнение по одному за раз уже гарантирует изоляцию.

Обоснование, почему зациклённому листовому Task не даётся дочерняя область: Task не является контейнером области — область означала бы засев пустого внутреннего графа и маршрутизацию синтетического завершения ради изоляции, которую свежий frame уже даёт. Изоляция-по-frame для листьев и изоляция-по-области для композитов — это один единообразный принцип (per-iteration изоляция), реализованный двумя механизмами, а не две конкурирующие модели.

Этот подраздел фиксирует механизм; кто им управляет для композитной активности — собственное off-loop-выполнение активности, а не per-instance loop-горутина — это §2.12.

2.3 Standard Loop — последовательный цикл, управляемый условием

Standard-Loop активность исполняет свою внутреннюю активность повторно последовательно, по одной итерации за раз (механизмом §2.2 под её вид активности), управляемая:

  • loopCondition — булево выражение, пере-вычисляемое каждый проход. Цикл продолжается, пока оно true.
  • testBeforeFalse (дефолт) → post-tested (do…while): исполнить один раз, затем проверить. Truepre-tested (while): проверить перед каждым исполнением, так что возможен ноль итераций.
  • loopMaximum — опциональный потолок: когда задан, исполняется не более этого числа итераций независимо от условия (защита от runaway-циклов; не задан = без ограничения).

Цикл выставляет per-iteration loopCounter (0-based) выражениям внутри активности и loopCondition. У Standard Loop нет collection-потока данных, нет параллелизма, нет completion condition и нет behavior — это концепции Multi-Instance. Когда цикл завершается (условие false или достигнут максимум), управление уходит в исходящий sequence flow активности один раз.

2.4 Multi-Instance — кардинальность решается один раз при активации

Multi-Instance активность вычисляет число своих экземпляров ровно один раз, при активации (§13.3.7), из одного из двух источников — движок поддерживает оба:

  • loopCardinality — целочисленное выражение, вычисляемое один раз.
  • КоллекцияloopDataInputRef указывает на data item со значением- коллекцией; кардинальность — число элементов этой коллекции.

Число фиксировано на время жизни активности: добавление элементов в исходную коллекцию на лету не спавнит больше экземпляров. Если заданы и кардинальность, и коллекция — это ошибка моделирования, всплывающая на валидации (это альтернативные источники кардинальности, не композируемые).

2.5 Последовательный vs параллельный

isSequential выбирает форму исполнения:

  • Trueпоследовательный: экземпляр i+1 начинается только после того, как экземпляр i завершился (механизмом §2.2 под вид активности). В любой момент исполняется не более одного экземпляра; порядок — порядок коллекции/кардинальности.
  • False (дефолт) — параллельный: все экземпляры стартуют при активации и исполняются конкурентно в отдельных per-instance областях (§2.2); активность завершается, когда дренируется последний. Изоляция области обеспечивает, что конкурентные экземпляры никогда не разделяют состояние токенов или per-instance данных.

Обе формы выставляют per-instance loopCounter (0-based порядковый номер) и агрегатные рантайм-атрибуты §2.9 выражениям активности.

Для композитной активности эти две формы — две управляющие стратегии декоратора: await-each (последовательный) и fan-out-then-await-all (параллельный), — гоняемые на собственном выполнении активности (§2.12).

2.6 Поток данных — расщепить на входе, собрать на выходе

Multi-Instance фундаментально является трансформацией коллекции (multi-instance.md §Data semantics). Спека называет медиатор split/assemble «under-specified»; этот ADR фиксирует конкретную конвенцию движка:

  • Split. Перед исполнением каждого экземпляра движок связывает inputDataItem этого экземпляра с элементом loopCounter коллекции loopDataInputRef. Экземпляр читает его по имени в своих input data associations, ровно как любой другой per-scope datum.
  • Assemble. Когда экземпляр завершается, движок пишет outputDataItem этого экземпляра в слот loopCounter коллекции loopDataOutputRef, сохраняя позиционное соответствие со входом.
  • Барьер видимости. Спека рекомендует, чтобы коллекция loopDataOutputRef была недоступна, пока все экземпляры не завершатся (multi-instance.md §Data semantics: «should not be accessible» — одна лишь передача токенов не может гарантировать, что коллекция полностью записана). Движок усиливает эту рекомендацию до гарантии: коллекция не должна быть читаема конкурентными активностями до завершения — собранный вывод публикуется в охватывающую область только при завершении активности, никогда инкрементально.

Позиционная сборка (output slot = input ordinal) — это реализация движком under-specified медиатора спеки, выбранная ради детерминизма: выходная коллекция зеркалит входной порядок независимо от порядка завершения экземпляров (критично для параллельного MI, где порядок завершения недетерминирован).

2.7 Completion condition — раннее упорядоченное отменение

completionCondition — булево выражение, вычисляемое каждый раз, когда экземпляр завершается (§13.3.7):

  • true → Multi-Instance активность завершена сейчас: оставшиеся ещё не завершённые экземпляры отменяются (их области сносятся как единое целое, механизм прерывания ADR-018, применённый к области каждого экземпляра), и управление уходит из активности.
  • false → этот экземпляр засчитан; оставшиеся экземпляры продолжают.

Без completionCondition активность завершается, когда завершились все экземпляры. Отмена упорядочена: отменённые экземпляры не вносят свой outputDataItem (их слот остаётся на pre-run значении), и выходная коллекция всё равно публикуется атомарно по §2.6.

2.8 Behavior — события, бросаемые по мере завершения экземпляров

behavior (MultiInstanceBehavior, дефолт All) управляет тем, бросает ли активность событие по мере завершения экземпляров (multi-instance.md §Event throwing). Брошенные события перехватываемы на границе Multi-Instance активности (механизм границ ADR-018), позволяя модели реагировать на прогресс:

  • All (дефолт) — событие никогда не бросается. Обычный случай; нулевая стоимость.
  • None — событие (noneBehaviorEventRef) бросается на каждое завершение экземпляра.
  • One — событие (oneBehaviorEventRef) бросается один раз, на первое завершение экземпляра.
  • Complex — этим управляют записи complexBehaviorDefinition: на каждое завершение экземпляра вычисляется condition (FormalExpression) каждого определения, и каждое, которое true, бросает ассоциированный ImplicitThrowEvent (events.md §ImplicitThrowEvent). Одно завершение поэтому может бросить несколько различных событий, каждое перехватываемое своим граничным событием — это включает потоки, зависящие от прогресса (например, «брось quorum-reached, когда пришли 3 из 5 одобрений»).

Брошенные события неявно несут рантайм-атрибуты Multi-Instance активности (§2.9), так что граничный обработчик может прочитать, насколько активность продвинулась.

Этот подраздел фиксирует, что бросается и когда; бросок исполняется как обычный off-loop-эмит, выданный декоратором итерации до завершения активности (§2.12), — именно это делает граничный перехват детерминированным.

2.9 Рантайм-атрибуты экземпляров (конвенция движка)

Стандарт заявляет, что рантайм-атрибуты Multi-Instance активности доступны completionCondition, ComplexBehaviorDefinition.condition и data associations behavior-событий, но vendored-извлечение их не перечисляет. gobpm поэтому определяет следующие engine-provided переменные как свою реализацию этого недоперечисленного набора (явный выбор движка, подлежащий закреплению против §13.3.7 при расширении KB):

Переменная Смысл
loopCounter 0-based порядковый номер текущего экземпляра (доступен также внутри каждого экземпляра).
numberOfInstances Полное число экземпляров, зафиксированное при активации (§2.4).
numberOfActiveInstances Экземпляры, исполняющиеся в данный момент (параллельный) — ≤ 1 для последовательного.
numberOfCompletedInstances Экземпляры, завершившиеся к текущему моменту.
numberOfTerminatedInstances Экземпляры, отменённые completion condition (§2.7).

Они read-only в выражениях; движок поддерживает их по мере прогресса экземпляров.

2.10 Отложено: компенсация Multi-Instance

BPMN §13.3.7 специфицирует, что Multi-Instance активность компенсируется только если завершились все её экземпляры, причём последовательные/loop-экземпляры компенсируются в обратном порядке, а параллельные — параллельно (multi-instance.md §Compensation). У gobpm пока нет субстрата компенсации — компенсация — это работа области Transaction (отслеживается отдельно). Этот ADR поэтому откладывает MI-компенсацию: модель итерации спроектирована так, что per-instance области индивидуально адресуемы (§2.2) — это предпосылка, которую будущая работа по компенсации потребит, — но никакого поведения компенсации здесь не реализуется. Когда компенсация приземлится, её ADR расширит этот.

2.11 Engine notes (отклонения и выборы)

  • Механизм итерации по виду активности (§2.2) — пере-исполнение на месте в свежем frame'е для листового Task, per-iteration дочерняя область для композита — выбор движка: стандарт не мандатирует ни одной конструкции, только что итерации исполняются; движок выбирает самую дешёвую, которая изолирует каждую итерацию.
  • Позиционная сборка вывода (§2.6) — конкретизация движком under-specified медиатора спеки.
  • Взаимоисключение кардинальность-vs-коллекция (§2.4) — выбор валидации движка; спека перечисляет оба атрибута, не запрещая оба вместе, но well-formed MI-активность использует ровно один источник.
  • Набор рантайм-атрибутов (§2.9) — конвенция движка в ожидании расширения KB.

2.12 Композитная итерация гоняется off-loop — декоратор итерации (v.2)

§2.2 фиксирует механизм (композитная активность пере-открывает свою дочернюю область на итерацию); этот подраздел фиксирует, кто им управляет. Композитная зациклённая активность итерируется на собственном выполнении активности — off-loop декоратор итерации — а не под управляющим кодом, гоняемым на per-instance loop-горутине.

Почему это решается здесь. Модель выполнения движка (ADR-017 v.1) имеет single-writer loop-горутину, которая владеет всем состоянием жизненного цикла выполнения (открытые области, позиции токенов, барьер параллельных экземпляров), тогда как работа узла гоняется вне её, на per-token runner-горутине, которая рапортует переходы состояния обратно как события. v.1-приземление гоняло управление итерацией — разрешить число, расщепить данные, вычислить completion condition, решить пере-вход и (§2.8) бросить behavior-событияна loop-горутине, расщепив управление и работу через границу горутины неверным образом. Behavior-бросок §2.8 — доказательство: бросить событие значит передать его в упорядоченный входящий канал цикла, но выданная из самой loop-горутины эта передача самоблокируется в дедлок (цикл — единственный читатель канала и занят внутри броска); сделанная fire-and-forget она вместо этого недетерминированно теряет перехват, потому что бросок и его граничный перехват становятся отдельными шагами цикла, между которыми собственное завершение активности может гоняться в race. Оба — симптомы одного структурного факта: v.1-управление не было тем декоратором, что описывает BPMN (§13.3.6–§13.3.7 обрамляют loop characteristics как обёртку вокруг активности, чьё управление принадлежит выполнению активности).

Решение.

  • Runner активности управляет итерацией. Собственное (off-loop) выполнение композитного хоста разрешает число/условие, открывает каждый экземпляр, ждёт каждого завершения, вычисляет completion condition, собирает вывод (§2.6), бросает behavior-события (§2.8), а затем завершает активность и проходит свой исходящий flow. Хост больше не паркуется, пока цикл гоняет итерацию за него, — его runner и есть управляющий. Парковка возвращается к своему BPMN-смыслу (ожидание внешнего события).
  • Цикл остаётся единственным writer'ом; декоратор запрашивает операции над областями. Гоняясь off-loop, декоратор не должен мутировать loop-owned состояние напрямую (это пере-ввело бы cross-goroutine race'ы, которые ADR-017 v.1 убрал). Он использует протокол запрос/ответ поверх существующего канала событий: он запрашивает операцию (открыть область экземпляра, закрыть дренированную, связать per-instance datum), цикл выполняет мутацию на своей горутине и подтверждает на входящем канале декоратора, а декоратор — заблокированный на этом подтверждении — возобновляется. Строго упорядочено, без разделяемого мутабельного состояния, без lock'а: инвариант single-writer сохранён и расширен, а не ослаблен.
  • Последовательный и параллельный — две управляющие стратегии (§2.5): await-each либо fan-out-then-await-all с барьером N-из-N — обычный control flow на горутине декоратора, а не callback'и, пере-входимые циклом.
  • Behavior-события становятся обычными off-loop-бросками (§2.8). Декоратор эмитит тем же путём, что и любая активность, и может заблокироваться, пока бросок не принят, до завершения активности — так граничный перехват упорядочен перед завершением по построению, на границе, которая всё ещё взведена. v.1-дедлок и недетерминированная потеря на этой модели структурно невозможны.

Scope. Это управляет композитными активностями (Sub-Process, Call Activity) — единственными активностями, которые несут границы и бросают behavior-события. Цикл листового Task уже гоняется на месте на собственном runner'е task'а (§2.2); он уже вне loop-горутины и не меняется.

Семантика не меняется. Всё, что решают §2.1–§2.11, — число фиксировано один раз (§2.4), split-in/assemble-out с барьером видимости (§2.6), completion condition (§2.7), рантайм-атрибуты (§2.9) — сохранено дословно. Этот подраздел меняет только где гоняется это управление (на декораторе, off-loop), а не что оно вычисляет. Механизм пере-открытия дочерней области §2.2 остаётся; декоратор лишь запрашивает каждое открытие/закрытие, а не цикл выполняет его инлайн.

Engine note. Протокол запрос/ответ над областями — механизм движка, а не BPMN-концепция: BPMN молчит о goroutine-модели движка; протокол существует исключительно ради примирения off-loop-управления с инвариантом single-writer и невидим моделирующему.


3. Grounding по стандарту

Утверждение Источник
Атрибуты и семантика Standard Loop multi-instance.md §Standard Loop; activities.md §StandardLoopCharacteristics (§13.3.6)
Кардинальность MI (выражение | коллекция), фиксирована при активации multi-instance.md §Cardinality (§13.3.7)
Секвенирование isSequential multi-instance.md §Sequencing; activities.md §MultiInstanceLoopCharacteristics
completionCondition отменяет оставшиеся multi-instance.md §Completion
behavior = All/None/One/Complex бросание событий multi-instance.md §Event throwing / §ComplexBehaviorDefinition; activities.md §MultiInstanceLoopCharacteristics / §ComplexBehaviorDefinition
ImplicitThrowEvent как событие complex-behavior events.md §ImplicitThrowEvent
Split/assemble данных, барьер видимости вывода multi-instance.md §Data semantics
Порядок компенсации multi-instance.md §Compensation

Там, где извлечение молчит (набор рантайм-атрибутов MI), §2.9 помечает конвенцию движка явно, а не утверждает мандат спеки.


4. Рассмотренные альтернативы

  • Разделяемый узел итерации для конкурентного случая (параллельный MI). Моделировать параллельный MI как единый узел, внутренне делающий fan-out, без отдельной области на экземпляр. Отвергнуто: параллельной изоляции тогда понадобился бы кастомный per-branch механизм партиционирования данных, дублирующий то, что модель области уже даёт бесплатно, а зациклённому Sub-Process понадобились бы две разные модели композиции (область для тела, что-то ещё для итерации). Переиспользование области ADR-023 на каждый конкурентный экземпляр (§2.2) строго проще — тогда как последовательные случаи избегают области вовсе для листового Task (§2.2), так что церемония не платится там, где исполнение по одному за раз уже изолирует.
  • Инкрементальная публикация вывода. Писать вывод каждого экземпляра в разделяемую коллекцию по мере его завершения, а не при завершении активности. Отвергнуто: нарушает барьер видимости §13.3.7 — конкурентная активность могла бы прочитать полусобранную коллекцию, а порядок завершения (параллельный) утёк бы в наблюдаемый порядок.
  • Откладывание бросания behavior-событий. Приземлить только All/None/One и пропустить Complex. Отвергнуто владельцем: вся поверхность §13.3.7, включая ComplexBehaviorDefinition, в scope — потоки на границе, зависящие от прогресса, — настоящий выигрыш выразительности, а субстрат граничного перехвата (ADR-018) уже существует, чтобы их принимать.
  • Standard Loop через самозацикливающийся sequence flow. Просить моделирующих рисовать явный шлюз-и-обратное-ребро вместо поддержки StandardLoopCharacteristics. Отвергнуто: это first-class BPMN-маркер в scope conformance, и он меняет смысл диаграммы (помеченная активность vs явный цикл).

Для модели выполнения §2.12 (v.2) отвергнутыми альтернативами были:

  • Оставить управление на цикле; вынести off-loop только behavior-бросок. Спец-кейсить бросок §2.8 на транзиентную горутину либо файрить его границу инлайн, оставив итерацию loop-driven. Отвергнуто: лечит симптом, а не структурное несоответствие — управление остаётся на неверной горутине — и требует кастомной inline-fire-машинерии, чтобы упорядочить перехват перед завершением. Не обобщается: каждый будущий control-side-эмит снова бьётся в ту же стену.
  • Ослабить инвариант single-writer. Позволить off-loop-декоратору напрямую открывать/закрывать области и обновлять позиции под lock'ом. Отвергнуто: пере-вводит cross-goroutine-мутацию состояния жизненного цикла, которую ADR-017 v.1 убрал, а lock над мапами позиций/областей — строго худшая синхронизация, чем goroutine-confinement.
  • Fire-and-forget async-бросок (оставить v.1-модель). Эмитить behavior-событие с транзиентной горутины и дать перехвату приземлиться когда-нибудь. Отвергнуто: эмпирически недетерминированно — перехват является более поздним шагом цикла, который гоняется в race с завершением активности, теряя behavior-события и на последовательной, и на параллельной формах. Пробел в корректности, а не выбор стиля.

5. Последствия

  • Положительное. Массовая поэлементная обработка становится выразимой; требования эпика к параллельной изоляции и агрегации выпадают из существующей модели области; единая концепция итерации служит Task, Sub-Process и Call Activity; наблюдаемость прогресса через behavior-события, перехваченные на границе.
  • Стоимость. Модель активности приобретает два конкретных типа loop-characteristics и их валидацию; рантайм учится открывать N областей (последовательно или параллельно) для одной активности и поддерживать агрегатные рантайм-атрибуты; слой данных приобретает медиатор split/assemble и барьер видимости.
  • Риск. Параллельный MI умножает конкурентность — учёт дренажа областей должен быть точен, чтобы активность не завершилась ни рано, ни зависла. Смягчено переиспользованием проверенного жизненного цикла open/drain/close ADR-023 вместо изобретения нового учёта. Complex behavior — наименее используемая, наиболее сложная поверхность; она приземляется последней, после того как доказаны последовательное и параллельное ядра.
  • v.2-переработка (§2.12). Перенос композитной итерации на off-loop-декоратор пере-приземляет пути выполнения композитных Standard Loop и Multi-Instance; SRD элементов, приземлившие управляемую loop-горутиной композитную модель, удаляются и переиспользуются — каждый переписывается на месте на декораторе (его старое содержимое удаляется, его номер переиспользуется), а не помечается Obsolete или перенумеровывается, что сохраняет отображение элемент→SRD стабильным, а код-набор консистентным с кодом, — и conformance-трекер обновляется на новое приземление. Она добавляет одну off-loop↔loop поверхность координации (протокол запрос/ответ над областями) — исторически там, где появляются race'ы; смягчено строгой дисциплиной запрос/ответ (цикл остаётся единственным writer'ом, без разделяемого состояния), пере-приземлением инкрементально (Standard Loop, затем последовательный, затем параллельный) с существующими suite'ами loop / MI / границ как зелёной-насквозь страховочной сеткой и оставлением циклов листовых Task нетронутыми, что ограничивает радиус поражения композитной итерацией.

6. Рекомендации по enterprise-готовности

  • Наблюдаемость. Эмитить факты для жизненного цикла итерации — активация (с разрешённой кардинальностью и источником), старт/завершение/отмена каждого экземпляра (с loopCounter) и завершение активности (с числами completed/terminated) — чтобы операторы могли наблюдать, как fan-out на 10 000 элементов делает прогресс, и заметить застрявший экземпляр. Переиспользовать вокабуляр наблюдаемости ADR-013.
  • Ограниченный fan-out. Кардинальность, управляемая внешними данными, может быть огромной; рекомендуется (и документируется) операционный ограничитель ширины параллельного MI, чтобы патологическая коллекция не исчерпала goroutine'ы/память. loopMaximum охраняет Standard Loop; параллельному MI нужен аналогичный engine-уровневый потолок как операционная забота.
  • Детерминированная агрегация. Гарантия позиционной сборки (§2.6) должна быть частью публичного контракта — потребители могут полагаться на то, что output[i] соответствует input[i].
  • Стоимость выражений. completionCondition и Complex-условия исполняются на каждом завершении экземпляра; документировать, что они должны быть дешёвыми и свободными от побочных эффектов.

7. План раскатки

Приземляется инкрементально, сначала наименьшее, каждый слайс — свой SRD и PR на существующем субстрате области выполнения:

  1. Standard Loop — последовательный условный цикл (loopCondition, testBefore, loopMaximum, loopCounter); простейшая итерация, доказывающая модель per-iteration области на одном последовательном пути. (Приземляется с этим ADR.)
  2. Multi-Instance последовательный — кардинальность (оба источника), медиатор split/assemble данных + барьер видимости, completionCondition, рантайм-атрибуты; переиспользует жизненный цикл последовательного пере-входа.
  3. Multi-Instance параллельный — конкурентные per-instance области, завершение drain-to-join, плюс поверхность бросания behavior-событий (All/None/One/Complex), перехватываемая на границе MI.

Компенсация (§2.10) вне этой раскатки; она едет на будущей работе Transaction / компенсации.

v.2 пере-приземление (§2.12). Три слайса выше приземлились на управляемой loop-горутиной композитной модели; v.2 пере-приземляет их на off-loop-декораторе, сначала наименьшее, каждый — свой SRD и PR:

  1. Движок декоратора + композитный Standard Loop — протокол запрос/ответ над областями, доказанный пере-приземлением простейшей композитной итерации, зелёный против существующих suite'ов.
  2. Последовательный Multi-Instance на декораторе.
  3. Параллельный Multi-Instance на декораторе — стратегия fan-out-then-await-all с барьером N-из-N, выраженным как control flow декоратора.
  4. Behavior Multi-Instance (§2.8) на декораторе — прямолинейный off-loop-бросок с детерминированным граничным перехватом.
  5. Вывести из строя старый шов — убрать управляемый loop-горутиной шов композитного итератора и обновить conformance-трекер на новое приземление.

Каждый слайс удаляет и переиспользует SRD своего элемента — переписывая его на месте на декораторе (старое содержимое удалено, номер переиспользован), а не помечая Obsolete или заводя новый номер, — так что отображение элемент→SRD остаётся стабильным.


8. Ссылки


Открытые вопросы

Нет.


История документа

Версия Дата Автор Изменение
v.1 2026-07-19 Руслан Габитов Первоначальный черновик — модель итерации Standard Loop и Multi-Instance: per-iteration изоляция механизмом, подходящим виду активности (на месте в свежем frame'е для листового Task, per-iteration дочерняя область для композита, отдельная per-instance область для параллельного MI), кардинальность (выражение | коллекция), последовательное/параллельное секвенирование, медиатор данных split/assemble с барьером видимости, completion condition, полная поверхность бросания behavior-событий (All/None/One/Complex), рантайм-атрибуты как конвенция движка; MI-компенсация отложена до будущей работы Transaction.
v.2 2026-07-21 Руслан Габитов Добавлен §2.12 — композитная итерация гоняется на собственном off-loop-выполнении активности (декоратор итерации), а не под управляющим кодом на per-instance loop-горутине; декоратор запрашивает операции над областями у single-writer-цикла через протокол запрос/ответ (инвариант ADR-017 v.1 сохранён), последовательный/параллельный становятся его двумя управляющими стратегиями, а behavior-бросок §2.8 становится обычным off-loop-эмитом с детерминированным граничным перехватом (некорректно реализуемым на v.1-модели). Семантика §2.1–§2.11 не изменена; сдвигается только то, кто управляет композитной итерацией. Forward-pointer'ы добавлены в §2.2/§2.5/§2.8; альтернативы модели выполнения добавлены в §4; последствия/раскатка v.2-переработки добавлены в §5/§7. Циклы листовых Task не изменены. SRD, приземлившие управляемую loop-горутиной композитную модель, вытесняются и удаляются по завершении пере-приземления.