Skip to content

ADR-007 — Долгие ожидания в памяти: дегидратация и пробуждение по триггеру

Поле Значение
Статус Принято
Версия v.2.1
Дата 2026-07-29
Владелец Руслан Габитов
Уточняет ADR-001 v.6 Execution Model §4.7 (инвариант долгого ожидания, который здесь реализуется)
Связано ADR-006 v.4 Events & Subscriptions (доставка триггера), ADR-033 v.1 Persistence & State §2.4/§2.5 (долговечная проекция этой in-memory модели), ADR-013 v.2 Observability (факты резидентности), ADR-020 v.1 Human Interaction / ADR-021 v.1 Service Task (два из держателей ожиданий)

Этот ADR решает, как долгое ожидание освобождает свои горутины и как триггер его возобновляет — в памяти: механизм, которого требует ADR-001 §4.7 и который черновик v.1 обещал написать, когда работа над долгими ожиданиями дойдёт до реализации. Долговечная проекция (та же модель, пережившая рестарт) — предмет ADR-033; это одна модель на двух уровнях резидентности (§2.6). Сопровождающий SRD приземляет механику.

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

  • Инвариант ADR-001 §4.7: долгое ожидание (UserTask, ждущий три дня, Timer, ReceiveTask) не должно удерживать горутину. Сегодняшний рантайм его нарушает: дорожка, дошедшая до узла ожидания, переходит в TrackWaitForEvent, но её горутина остаётся живой и заблокированной на канале событий всё время ожидания, а рядом остаётся резидентным цикл событий экземпляра. Один экземпляр, ждущий одного таймера, удерживает поэтому три горутины — цикл экземпляра, припаркованную дорожку и (для таймера) отдельную горутину-вейтер, держащую дедлайн. Десять тысяч экземпляров, каждый ждущий неделю, удерживают тридцать тысяч горутин, которые не делают ничего, кроме занятия стеков.
  • Ожидание — это не исполнение. Семантика исполнения BPMN говорит, что ловящее промежуточное событие / Receive Task / граничное событие ждёт своего триггера и, когда триггер приходит, срабатывает — связывает payload и активирует исходящий поток. Стандарт молчит о среде исполнения: является ли ожидание заблокированным потоком, подпиской или сохранённой записью — выбор движка. Выбор gobpm (ADR-001): горутины — среда исполнения, запись — это состояние, поэтому ожидание, которое не исполняется, не имеет прав на горутину.
  • Состояние продолжения уже полностью выносимо наружу (ADR-001 §4.7, сохранено в §3): будущее дорожки описывается её позицией (узлом), состоянием дорожки/шага, данными Scope и родословной — на объекте токена нет скрытого состояния, а возобновляемое состояние узла (дедлайн таймера, ключ подписки) живёт по контракту состояния на узел, никогда — в разделяемом неизменяемом определении узла. SRD-070 сделал это конкретным для восстановления после рестарта: чекпоинт записывает ровно это, а Restore пересобирает из него ждущий экземпляр. Дегидратация — та же пересборка, запущенная простоем, а не падением.

2. Решение

2.1 Состояние дегидратированной дорожки — горутина завершается

Новое терминальное состояние дорожки TrackDehydrated отмечает дорожку, чьё долгое ожидание вынесено наружу, а горутина завершилась. Когда экземпляр дегидратируется, каждая его припаркованная на долгом ожидании дорожка переходит из TrackWaitForEvent в TrackDehydrated, а её рабочая процедура возвращает управление вместо того, чтобы продолжать блокироваться. TrackDehydrated терминально для горутины, но не терминально для потока: дорожка не завершила свой узел, она освободила свой поток исполнения — ожидание переживает горутину как учётная запись (реестр ожиданий цикла) и, долговечно, как запись чекпоинта.

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

2.2 Два уровня резидентности

У экземпляра два уровня резидентности, и дегидратация — переход между ними:

  • Резидентный — цикл экземпляра и горутины его дорожек живы; экземпляр сам держит свои ожидания (сегодняшнее поведение). Экземпляр остаётся резидентным, пока у него есть любая выполняющаяся работа.
  • Дегидратированный — каждая дорожка на долгом ожидании, экземпляр полностью простаивает и освободил все свои горутины: и дорожки (§2.1), и сам цикл экземпляра. Экземпляр стоит памяти (свой чекпоинт) и записи в реестре ожиданий движка, но ноль горутин.

Экземпляр дегидратируется, когда достигает условия полного простоя: все живые дорожки припаркованы на долгих ожиданиях и не осталось незавершённой короткоживущей внутренней синхронизации (нет барьера join в процессе слияния, нет выполняющегося шага). Частичная дегидратация — освобождение части горутин дорожек при резидентном цикле — допустимое промежуточное состояние, которое механизм разрешает (оно уже помогает в случае «много ожиданий в одном экземпляре», например при широком параллельном multi-instance), но главный выигрыш — полностью простаивающий экземпляр, освобождающий свой цикл.

2.3 Пробуждение — это fork-продолжение

Когда для дегидратированного ожидания приходит триггер, экземпляр не возобновляет исчезнувшую дорожку. Он гидратируется (пересобирает освобождённые горутины из записи — путь Restore из SRD-070, теперь запускаемый триггером, а не стартом движка) и продолжает разбуженное ожидание свежей дорожкой, родословно-порождённой от дегидратированной (её цепочка prev — id дегидратированной дорожки и её предки). Это fork-продолжение: он едет на уже существующей в рантайме машинерии fork/родословной, а проекция токенов/истории остаётся связной — дегидратированная дорожка показывает узел ожидания как своё последнее посещение, ребёнок показывает, к чему привёл триггер, и связаны они родословной.

Свежая дорожка ВХОДИТ В УЗЕЛ ОЖИДАНИЯ ЗАНОВО, с триггером на руках. Она не стартует на узел позже ожидания: узел ожидания выполняет работу момента срабатывания, которая живёт на самом узле — Message-catch связывает пришедший payload с данными процесса и выводит из него корреляционные ключи; catch/receive может нести выходной маппинг. Повторный вход в узел с триггером на руках выполняет этот путь срабатывания ровно один раз и переходит на исходящий поток. Отсюда самый острый инвариант этого ADR, который заодно уточняет восстановление после рестарта:

Есть триггер — продолжаем; нет триггера — перевзводим. Пробуждение по триггеру входит в узел ожидания с триггером и срабатывает сквозь него. Холодный рестарт (SRD-070) входит в узел ожидания без триггера и перевзводит ожидание. Единственный различитель — сопровождает ли гидратацию ожидающий триггер: тот же узел, тот же вход, два исхода.

Триггер граничного события — исключение, подтверждающее правило. Он принадлежит не тому узлу, на котором стоит токен, поэтому он НЕ должен ехать как ожидающий триггер этого узла: повторный вход в охраняемый узел с триггером границы сработает не тем элементом. Поэтому пробуждение по границе — без триггера: экземпляр пересобирается, его ожидания перевзводятся, и граница перевзводится на свой записанный дедлайн. Этот дедлайн уже позади (граница сработала, пока экземпляра не было), поэтому взвод не ждёт снова: токен форкается на самом граничном событии, а охраняемая дорожка становится его родителем — прерывающая граница отменяет этого родителя, непрерывающая оставляет его работать, — что граничное срабатывание и означало всегда. Fork-продолжение и fork границы — одна и та же машинерия, направленная на разные узлы.

2.4 Какие ожидания освобождаются и кто их держит

Право на освобождение — это capability, которую объявляет элемент, а не вид, который перечисляет рантайм. Узел ожидания подписывается на дегидратацию, реализуя capability Dehydratable, к которой цикл обращается в момент парковки; узел, который её не реализует, недегидратируем и держит экземпляр резидентным. Это следует установившейся идиоме рантайма (опциональные capability-интерфейсы — DeadlineHinter, capability узлов ожидания) и даёт три вещи, которых жёсткий switch по видам дать не может:

  • Расширяемость — пользовательское событие/задача подписывается на дегидратацию, а рантайму не нужно знать о его существовании.
  • Политика на элемент, управляемая данными — решение может опираться на собственное вычисленное состояние элемента, то есть это не статическая константа на вид. Timer возвращает false для подпорогового дедлайна (двадцатиминутное ожидание не стоит круговорота «чекпоинт + гидратация») и true для долгого; внешне-воркерный ServiceTask возвращает false безусловно (задание в работе — это активная работа, а не пассивное ожидание, ADR-021).
  • Безопасное инкрементальное внедрение — умолчание (нет capability) — «оставаться резидентным», поэтому элементы получают дегидратацию по одному, в ногу со своими держателями.

Право на освобождение (элемент готов освободиться) отличается от держателя (что-то способно его разбудить) и композируется с ним: экземпляр дегидратирует ожидание, только если узел Dehydratable и у каждого взведённого ожидания есть держатель своего вида. Элемент, объявивший себя дегидратируемым для вида без держателя, — ошибка, от которой рантайм страхует: экземпляр остаётся резидентным и пишет лог, но триггер не теряется. Узел catch/receive с одним определением отвечает по своему определению; узел-задача отвечает напрямую.

У охраняемой активности ожиданий больше, чем видит её токен. Прерывающее граничное событие — это ожидание («согласовать за 24 часа или эскалировать» — это две вещи, которых ждут, а не одна), но оно висит на активности, а не занимает её, поэтому ничто в ожидании токена его не описывает. Освобождение экземпляра, у которого задача была удержана, а граница нет, молча потеряло бы эскалацию: дедлайн проходит, ничего не срабатывает, а запись остаётся в полёте без пути назад. Пропущенный бизнес-дедлайн, не давший никакой ошибки, — худший отказ, который это решение способно породить.

Поэтому взведённая граница — удерживаемое ожидание в собственном праве, а право на освобождение считается по ожиданию, а не по токену: экземпляр освобождается, только когда у каждого ожидания, охраняющего дорожку — и того, на котором она стоит, и каждой границы над ней, — есть держатель. Это то же поарочное правило, которое уже подразумевает Event-Based Gateway, применённое с той гранулярностью, которая нужна границе. Виды границ, разрешаемые напрямую, а не ожиданием (Error, Escalation, Compensation, Cancel — сопоставляются в точке броска или отказа и никогда не подписываются), ничего не взводят и потому не стоят резидентности; Conditional-граница принадлежит циклу и потому неудержима — она держит свой экземпляр резидентным ровно так же, как Conditional-catch.

Дедлайн границы — долговечное состояние, а не значение, которое можно пересчитать. Перевзвод восстанавливает о границе из модели всё, кроме того, когда она срабатывает: повторное вычисление «через 24 часа от текущего момента» при восстановлении даёт 24 часа от восстановления, поэтому эскалация, заданная длительностью, уползает вперёд при каждом восстановлении, и экземпляр, восстановленный достаточно часто, не эскалирует никогда. Поэтому разрешённый дедлайн записывается и восстанавливается — то же правило, которое рантайм уже применяет к собственному таймеру дорожки и от которого граница ускользала.

Event-Based Gateway — это узел ожидания, а не его ветви. EBG взводит несколько catch-событий на одной дорожке и устраивает между ними гонку «первый сработавший побеждает», проигравшие отзываются. Решение Dehydratable — свойство узла ожидания, а для EBG узел ожидания — сам шлюз, поэтому EBG возвращает Dehydratable == true безусловно и игнорирует политики своих ветвей-событий: EBG существует только чтобы ждать гонку, значит он всегда чистая точка ожидания, ради которой стоит освобождаться (обычный промежуточный catch по таймеру — может и нет, EBG — всегда). Поэтому дегидратированная дорожка держит множество держателей ветвей (по одному на взведённое ожидание; для обычного catch множество вырождается в один); по триггеру победившей ветви fork срабатывает этой ветвью и отзывает держателей соседних ветвей (освобождая их дедлайны/подписки) — ровно тот Withdrawn, который резидентный EBG выполняет сегодня. Гарантия существования держателя по-прежнему применяется поарочно: EBG освобождается, только когда у каждого вида его ветвей есть держатель (полностью таймерный EBG — как только появится таймер-сервис; EBG с message-ветвью — как только появится держатель сообщений).

Кто держит освобождённое ожидание. Резидентный экземпляр держит свои ожидания сам — его живой цикл и есть подписчик, которому доставляется триггер. Дегидратированный не может: его цикла нет, а триггер, доставленный освободившемуся экземпляру, теряется. Решение:

Освобождаемое ожидание регистрирует как EventProcessor своего держателя — не дорожку и не экземпляр, — помеченного id экземпляра, id дорожки и дескриптором ожидания. Держатель — постоянный подписчик; источник триггера (EventHub или сервис таймеров движка) указывает на него с момента взвода ожидания, поэтому триггер никогда не приходит освободившемуся экземпляру.

По триггеру держатель ветвится по резидентности экземпляра:

  • Резидентный (у экземпляра ещё есть цикл — например, он дегидратировал только часть дорожек или не дегидратировался вовсе) → держатель возобновляет живую припаркованную дорожку — сегодняшняя доставка, достигнутая через держателя.
  • Дегидратированный → держатель будит экземпляр: Hydrate (пересборка из чекпоинта), а затем — вместо того чтобы перевзвести разбуженное ожидание и доставить повторно — порождает fork-продолжение, которое готовит вход узла из триггера и выполняет путь срабатывания узла ожидания напрямую (§2.3); fork не регистрируется как EventProcessor заново, потому что триггер уже на руках. Это закрывает разрыв «доставка освободившемуся циклу» в корне: ничего никогда не проталкивается исчезнувшему экземпляру — подписан был держатель.

Паттерн держателя — одно решение с реализацией на каждый вид, потому что каждый источник триггера — своя подсистема:

  • Timer → держатель — сервис таймеров уровня движка. Горутина-вейтер на каждое ожидание (сегодня — по одной на ждущий экземпляр) заменяется единым сервисом, хранящим дедлайны с ключом по id экземпляров; на ближайшем дедлайне он срабатывает. Это тот самый долговечный сервис таймеров, который нужен ADR-033 §2.5 и который отслеживает issue #84, — дедлайн и есть дескриптор ожидания, который SRD-070 уже записывает.
  • Message / Signalподписка EventHub нацелена на держателя, с ключом по id экземпляра и с корреляционными ключами из чекпоинта. Подписка переживает освобождение экземпляра; корреляция (ADR-016 v.1) переезжает на держателя — он сам проверяет совпадение ключей разговора, поэтому сообщение с чужим ключом никогда не разбудит экземпляр.
  • Пользовательская задача → держатель — путь завершения в дистрибьюторе задач. Припаркованная задача уже живёт во «входящих» дистрибьютора (ADR-020) независимо от резидентности экземпляра; завершение будит экземпляр, а выходы завершения становятся входом узла для fork'а — там, где сегодня проталкивается запрос в живой канал.
  • Задание внешнего воркеране дегидратируется (Dehydratable его узла возвращает false). Задание воркера — это активная работа в полёте (ей владеет очередь fetch-and-lock из ADR-021), а не пассивное ожидание: экземпляр остаётся резидентным, пока задание не отчитается. Долговечность самой очереди — предмет ADR-021.

Экземпляр дегидратируется, только когда каждая живая дорожка припаркована на Dehydratable-ожидании, у вида которого есть держатель; всё остальное — недегидратируемый элемент, вид ожидания, чей держатель ещё не приземлился, задание воркера в полёте — держит экземпляр резидентным. Право на освобождение и держатели внедряются поэлементно и повидово, и пока нет обоих, такое ожидание просто остаётся резидентным — но триггер не теряется.

2.5 Эффекты и идемпотентность при пробуждении

Пробуждение заново входит в узел ожидания и заново выполняет его путь срабатывания — позиция at-least-once, согласованная с ADR-033 §2.3. Срабатывание идемпотентно по построению: таймер срабатывает своим исходящим один раз (просроченный дедлайн схлопывается в одно срабатывание, ADR-033 §2.5); сообщение связывает payload и идёт дальше; корреляция дедуплицирует повторно доставленное сообщение по ключу (ADR-016). Никакое пробуждение не переигрывает завершённый шаг — дегидратированная дорожка не завершала свой узел, поэтому пробуждение и есть первое и единственное срабатывание узла.

2.6 Одна модель, два уровня резидентности

Этот ADR — in-memory утверждение; ADR-033 §2.4/§2.5 — его долговечная проекция: одна модель, два уровня резидентности.

  • В памяти дегидратация освобождает горутины, а держатель ожидания хранит само ожидание; гидратация пересобирает горутины из живой записи.
  • Долговечно та же запись и есть чекпоинт (SRD-070): она переживает рестарт, а восстановление после рестарта — это гидратация, триггером которой служит «движок запустился». Дегидратированные экземпляры упавшего движка и его резидентные экземпляры восстанавливаются одним и тем же путём Restore — дегидратация не сделала их особым случаем, она сделала каждый ждущий экземпляр заранее пригодным к восстановлению.

Поэтому дегидратация не добавляет новой модели персистентности: она переиспользует чекпоинт SRD-070 как источник гидратации и добавляет только триггер освобождения горутин (простой), триггер пробуждения (событие через держателя) и повторный вход через fork-продолжение.

2.7 Наблюдаемость

Резидентность — свойство жизненного цикла, значимое для оператора («сколько из моих десяти тысяч экземпляров дегидратированы» — реальный вопрос), поэтому оба перехода резидентности наблюдаемы на объёме жизненного цикла (ADR-013 v.2): факт Dehydrated, когда экземпляр освобождает горутины (детали: припаркованные виды ожиданий и их количество), и факт Hydrated, когда держатель его будит (детали: вид разбудившего триггера, продолжил ли он поток или завершил экземпляр). Один факт на переход — не на чекпоинт (чекпоинт сопровождает уже наблюдаемый переход) и не на каждое ещё взведённое ожидание. Переходы резидентности едут по существующему потоку наблюдаемых событий и по эху в операторский лог, чисто с точки зрения маскирования (имена и счётчики, никогда payload). Смена состояния TrackDehydrated видна через существующую проекцию состояний дорожек/токенов, поэтому токены дегидратированного экземпляра по-прежнему проецируют свои позиции ожидания.

3. Инварианты рантайма, которые это сохраняет (ADR-001 §4.7)

  • Продолжение дорожки полностью описывается позицией, состоянием дорожки/шага, данными Scope и родословной — скрытого состояния токена нет. Дегидратация на это опирается: дочерний fork пересобирается ровно из этого, а дегидратированная дорожка добавляет только состояние (TrackDehydrated) и свой дескриптор ожидания.
  • Возобновляемое состояние на узел (дедлайн таймера, ключ подписки) живёт по контракту состояния на узел, а никогда — в разделяемом неизменяемом определении узла, поэтому множество экземпляров, дегидратированных на одном узле, разделяют одно определение и держат свои различные ожидания в собственных записях/держателях.
  • Цикл — единственный писатель состояния экземпляра (ADR-017): дегидратация и переход в TrackDehydrated происходят на цикле до его выхода, поэтому освобождение — согласованный срез, а пробуждение входит обратно через цикл, который оно же пересобирает.

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

Альтернатива Плюсы Минусы Решение
A. Блокировать горутину на время ожидания (статус-кво) тривиально; объект дорожки остаётся живым и возобновляется на месте нарушает ADR-001 §4.7; O(ждущих) горутин удерживаются бесконечно — ровно та цена, ради устранения которой эта работа и существует ❌ отклонено — это и есть постановка проблемы
B. Освободить горутину, но возобновлять ТУ ЖЕ дорожку (регидратировать заблокированный объект) нет форка родословной; токен остаётся одной непрерывной дорожкой требует восстановления точного состояния горутины/стека либо отдельного объекта «приостановленной дорожки» со своим протоколом возобновления вне машины состояний; воюет с моделью неизменяемого продолжения ❌ отклонено — §2.1/§2.3: свежий потомок родословной пересобирается из вынесенного состояния без отдельного протокола
C. Свежая дорожка, стартующая на узел ПОСЛЕ ожидания простейшее продолжение — без повторного входа в узел ожидания пропускает работу момента срабатывания узла — Message-catch потерял бы связывание payload'а и вывод корреляционных ключей; корректно только для триггеров без работы срабатывания (таймеров) ❌ отклонено — §2.3: входим в узел С триггером, чтобы работа срабатывания выполнилась; для таймеров это совпадает со «следующим узлом», для сообщений — корректно
D. Состояние TrackDehydrated + fork-продолжение с повторным входом в узел ожидания (выбрано) переиспользует машину состояний, машинерию fork/родословной и чекпоинт SRD-070; одна модель с восстановлением после рестарта; повидовое инкрементальное внедрение нужен держатель ожидания уровня движка на каждый вид (§2.4) — реально, но приземляется независимо ✅ выбрано
E. Долговечный журнал подписок / event-sourced пробуждения идеальный аудит пробуждений вторая модель персистентности рядом с чекпоинтом; ADR-033 §4 уже отклонил event sourcing для состояния ❌ отклонено — §2.6: чекпоинт — единственная запись, а держатели — in-memory индексы, восстанавливаемые из неё

5. Отложенное и порядок внедрения

  • Держатели по видам внедряются независимо (§2.4). Таймер (закрывающий #84) — самый чистый первый держатель: внешнего актора нет, детерминированный дедлайн уже лежит в записи. Держатели message/signal и пользовательских задач следуют за ним; пока у вида нет держателя, он остаётся резидентным, не теряя триггеров.
  • Приостановка/возобновление оператором (ADR-033 §2.6) — та же дегидратация, запускаемая оператором, а не простоем, на той же машинерии; следующий срез.
  • Пробуждение в мультиузловой конфигурации (триггер приходит движку B для экземпляра, дегидратированного под арендой движка A) едет на ограждении владения из ADR-033 §2.8 и на ADR Distribution & Scale — вне области этого документа; одноузловой дегидратированный экземпляр будит его собственный держатель.

Сопровождающий SRD приземляет механизм (TrackDehydrated, детектор простоя, пробуждение через fork-продолжение) и держателей ожиданий поверх чекпоинта SRD-070.

6. Ссылки

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

Версия Дата Автор Изменения
v.2.1 2026-07-29 Руслан Габитов Граничные события — удерживаемые ожидания: концепция, установленная реализацией, записана на том слое, которому принадлежит. §2.4: у охраняемой активности ожиданий больше, чем видит её токен, поэтому право на освобождение считается по ожиданию, а не по токену — экземпляр освобождается, только когда удержаны и ожидание, на котором стоит токен, и каждая граница над этой дорожкой. Освобождение при удержанной задаче и неудержанной границе молча теряет эскалацию «согласовать за 24 часа или эскалировать» — худший отказ, который это решение способно породить. Виды границ, разрешаемые в точке броска/отказа (Error, Escalation, Compensation, Cancel), ничего не взводят и не стоят резидентности; Conditional-граница принадлежит циклу и неудержима. Дедлайн границы — долговечное состояние: повторное вычисление длительности при восстановлении перезапускает отсчёт, и экземпляр, восстановленный достаточно часто, не эскалирует никогда — то же правило, которое уже действовало для собственного таймера дорожки и от которого граница ускользала. §2.3: триггер границы принадлежит не припаркованному узлу, поэтому пробуждение по границе — без триггера, а срабатывание — fork на самом граничном событии с охраняемой дорожкой в роли родителя; fork-продолжение и fork границы — одна машинерия, направленная на разные узлы.
v.2 2026-07-27 Руслан Габитов Написан полностью, когда работа над долгими ожиданиями дошла до приземления (обещание черновика v.1). Решает in-memory механизм дегидратации/пробуждения: терминальное состояние TrackDehydrated (горутина дорожки завершается), два уровня резидентности (резидентный / полностью простаивающий дегидратированный, освобождающий и цикл), пробуждение как fork-продолжение (свежая дорожка, родословно-порождённая от дегидратированной, входящая в узел ожидания заново с триггером на руках, чтобы работа момента срабатывания — связывание payload'а сообщения, выходной маппинг — выполнилась один раз) и заострённый инвариант есть триггер — продолжаем / нет триггера — перевзводим (который заодно уточняет восстановление после рестарта из SRD-070). §2.4 делает право на освобождение capability Dehydratable, объявляемой элементом (управляется данными: Timer остаётся резидентным ниже порога дедлайна, воркерный ServiceTask не дегидратируется никогда; Event-Based Gateway — это узел ожидания и возвращает true безусловно, игнорируя свои ветви, держит множество держателей ветвей и отзывает проигравших при пробуждении), композируемой с держателем (освобождаемся, если элемент готов И у каждого взведённого вида есть держатель); и держатель — а не дорожка и не экземпляр — регистрируется как EventProcessor (помеченный id экземпляра/дорожки): постоянный подписчик ветвится по резидентности (резидентный → возобновить живую дорожку; дегидратированный → гидратация + прямое срабатывание узла ожидания из триггера, без перерегистрации), что закрывает в корне разрыв «доставка освободившемуся циклу» и переносит корреляцию сообщений на держателя. Паттерн держателя ожидания на каждый вид (сервис таймеров движка — #84; подписка EventHub, нацеленная на держателя; завершение в дистрибьюторе задач), внедряемый инкрементально; задания воркеров остаются резидентными (активная работа, а не ожидание). Одна модель, два уровня резидентности вместе с ADR-033: новой модели персистентности нет — чекпоинт SRD-070 и есть источник гидратации. §2.7 делает резидентность наблюдаемой (факты Dehydrated/Hydrated на объёме жизненного цикла). Отклонённые альтернативы: блокировать горутину, возобновлять ту же дорожку, стартовать после узла ожидания, event-sourced пробуждения.
v.1 2026-06-07 Руслан Габитов Первичный черновик — модель освобождения долгих ожиданий в памяти, перенесённая из ADR-001 v.3 §4.7. Долговечная версия остаётся в ADR по Persistence & State. Ещё не реализовано.