@@ -172,8 +172,9 @@ if (router.failed_unsubscribe_count() != 0) {
172172
173173Исходный unsubscribe callback получает ошибку один раз; внутренний retry не
174174восстанавливает уже использованный публичный handle. Для постоянных ошибок
175- применяй backoff на уровне приложения. ` shutdown() ` делает последнюю
176- best-effort попытку cleanup перед освобождением состояния Router.
175+ применяй backoff на уровне приложения. ` shutdown() ` повторяет cleanup entries,
176+ которые уже один раз завершились ошибкой, но повторная ошибка оставляет Router в
177+ состоянии draining до retry приложения или будущей явной abandon policy.
177178
178179Необязательный subscription callback сообщает о принятии desired state
179180провайдером. Это не callback готовности транспорта:
@@ -341,19 +342,24 @@ tick batches, bar batches и status updates в этот loop. Боты испо
341342
342343Если subscriber уничтожен до выполнения posted subscribe command, команда
343344отменяется, а её callbacks не вызываются. Когда настроенный dispatcher начинает
344- отклонять работу во время shutdown, новые provider completions и deliveries
345- отбрасываются, а не исполняются inline в source thread. Tick, bar и status
346- deliveries не требуют физического cleanup и сразу удаляются.
347-
348- Успешный subscribe completion резервирует concrete provider handle в состоянии
349- Router до постановки completion task в очередь. Owner task должен атомарно
350- забрать эту reservation перед переводом маршрута из pending в active. Если задача
351- отклонена, reservation остаётся у pending маршрута, а Router помещает provider в
352- карантин для новых routes. Если задача принята, но позднее отменена во время
353- shutdown owner loop, reservation также остаётся доступной для
354- `router.shutdown()`. В обоих случаях shutdown отписывает сохранённый handle из
355- owner loop; ни subscription callback, ни callbacks subscriber не выполняются в
356- source thread.
345+ отклонять работу во время shutdown, новые tick, bar и status deliveries
346+ отбрасываются, а не исполняются inline в source thread. Эти deliveries не
347+ требуют физического cleanup и сразу удаляются.
348+
349+ Subscribe и unsubscribe completions записываются в состояние Router до
350+ постановки owner task. Успешный subscribe result резервирует concrete provider
351+ handle; owner task атомарно забирает reservation перед переводом route из pending
352+ в active. Поэтому отклонённая или позднее отменённая owner task не владеет
353+ единственной копией physical handle или completion result.
354+
355+ `process()` продвигает принадлежащую Router deferred lifecycle work в owner
356+ thread. При обычной работе posted completion tasks применяют transitions
357+ напрямую, поэтому Router не требует отдельной прокачки. После начала draining
358+ через `shutdown()` поздние provider completions остаются в состоянии Router, а
359+ `process()` превращает каждый поздний успешный subscribe в physical unsubscribe
360+ без user callbacks и replay. Метод не опрашивает providers или transports; в
361+ ручном режиме платформы сначала вызывай `platform.process()`, чтобы provider
362+ смог сформировать completion.
357363
358364Все callbacks Router сериализованы owner loop, но поля, которые напрямую читает
359365другой поток бота, всё равно требуют собственного mutex, atomics или очереди
@@ -369,18 +375,26 @@ provider и owner dispatcher
369375 который живёт дольше subscribers и posted cleanup work
370376```
371377
372- Используй такой порядок остановки:
378+ ` shutdown() ` — идемпотентный запрос остановки, а не безусловное утверждение, что
379+ все асинхронные provider operations завершились до возврата метода. Он сам
380+ выполняет один проход ` process() ` , поэтому синхронный cleanup по-прежнему
381+ завершается внутри вызова. Используй такой порядок остановки:
373382
3743831 . Прекрати создавать новые команды в потоках ботов.
3753842 . Запроси unsubscribe или уничтожь subscribers, пока dispatcher принимает
376385 работу.
377- 3 . Продолжай обработку owner loop, пока не завершатся unsubscribe commands и
378- вложенные provider completion callbacks. Проверь
379- ` failed_unsubscribe_count() ` и выполни retry по политике приложения, пока
380- owner loop и providers ещё доступны.
381- 4 . Вызови ` router.shutdown() ` из owner loop. Метод идемпотентен, освобождает
382- callback slots провайдера и запрашивает unsubscribe оставшихся маршрутов.
383- 5 . Останови platform/dispatcher, затем уничтожай providers.
386+ 3 . Вызови ` router.shutdown() ` из owner loop. Новые routes и user delivery
387+ прекращаются сразу; pending provider operations остаются cleanup tombstones.
388+ 4 . Оставь provider и owner loop работающими, пока
389+ ` router.is_shutdown_complete() ` не вернёт true. В ручном режиме каждый host
390+ tick вызывает ` platform.process() ` , а затем ` router.process() ` .
391+ 5 . Проверяй ` failed_unsubscribe_count() ` и выполняй retry с backoff приложения,
392+ пока owner loop и providers доступны. Failed cleanup не позволяет
393+ ` is_shutdown_complete() ` стать true.
394+ 6 . Останови platform/dispatcher, затем уничтожай providers.
395+
396+ Если приложение объединяет несколько process/shutdown modules, этот drain loop
397+ должен находиться в lifecycle supervisor, а не в Router-specific business code.
384398
385399Не откладывай уничтожение subscriber до момента, когда dispatcher уже закрыт.
386400Обычно ` MarketDataSubscriberBase ` отправляет оставшиеся handles одной cleanup
0 commit comments