Глава 29 / 51
Том 4 · Глубокое погружение в перезагрузку

Жизненный цикл поколения: полная хронология момента перезагрузки

Что происходит, если горячую перезагрузку подать при 10 конкурентных запросах в полёте? Глава рисует весь путь от компиляции до погребения старого поколения — эталонный ответ на вызов из гл. 3.

Обзор

Позиционирование: собрать рассыпанные ранее фрагменты (слоты, lease, двойные поколения, утилизация) в одну хронологию. После главы у каждого «почему» в утверждениях reload_matrix есть источник.

29.1 Введение

Тяжелейшая часть горячей перезагрузки не «сменить код», а момент смены: порт всё ещё слушает? Кому принадлежат запросы в полёте? Как два поколения сосуществуют и как расстаются? Ответы прячутся в двух механизмах — слот слушателя (источник стабильности порта) и lease (источник принадлежности запросов). Нанизать их на хронологию — и есть эта глава.

29.2 Сквозная хронология

время управляющий поток потоки worker слот/порт ──────────────────────────────────────────────────────────────────── t0 подать намерение → заморозить снимок (Revision) t1 полная компиляция кандидатов (без побочных эффектов) └ провал → FAILED, на этом всё (старое поколение не тронуто) t2 ServiceSwap (старое поколение экспортирует снимок) t3 ═══ переход необратимой точки ═══ t4 ServiceInit нового поколения (xsSwapTake) 10 запросов в полёте порт продолжает слушать «новое поколение загружено» всё ещё на старом gen N (слот не менялся) t5 слот переключает указатель → новые соединения в gen N+1 старые запросы доезжают t6 старые запросы завершаются → lease возвращаются t7 наблюдение ссылок старого поколения …… обнуление t8 GC-поток: ServiceUnit → TCC → драйвер → конфигурация (погребение старого поколения) внимание: в t4–t6 оба поколения сосуществуют; каждый запрос от начала до конца видит одно поколение
  • t3 — линия раздела: до неё всё может провалиться и отмениться; после — только вперёд (новые намерения встают в очередь)
  • переключение t5 атомарно: соединение начинается либо от старого, либо от нового поколения — «полупоколения» не бывает
  • Каждый запрос от начала до конца видит одно поколение — ключевое свойство двойных поколений и весь секрет «правишь код — соединения живут»

29.3 Слоты слушателя и handoff

socket порта 8080 держит слот слушателя (ListenerSlot) pData слота указывает на «текущее поколение» смена поколения = переключение указателя внутри слота (под записывающей блокировкой, микросекунды) — ноль мерцаний порта сравнение эндпоинтов: class/ip/port/tls/backlog/recv_limit полностью совпали → handoff на месте эндпоинты изменились → путь структурных изменений (гл. 13: явный отказ или подсказка рестарта)

Handoff делает возможным «старое и новое поколение на одном порту»: слушающий socket никогда не закрывается и не переоткрывается — извне сервис ни на миллисекунду не становится недоступным. TLS-эндпоинты так же, и выбор личности из ClientHello (SNI) привязан cookie-механизмом к тому же Accept — рукопожатия не раскалываются между поколениями.

29.4 Держатели lease

ДержательПолучениеВозврат
HTTP-запроспосле Accept через текущее поколение слотаответ завершён + тело вычитано (keep-alive держит до конца соединения или переатрибуции следующим запросом)
Соединение WS / TCPсоединение установленотерминал соединения (Close/Abort)
Дейтаграмма UDPпо разу на дейтаграммувозврат по завершении обработки
Таймерпривязан к своему поколению при созданиисработал или умер со сменой поколения
Перехваченное соединениев момент перехватакогда вы наконец Close (контракт гл. 25)

Поэтому ответ на «когда умирает старое поколение» целиком определяют lease: HTTP после смены обнуляется за секунды (вместе с тайм-аутами простоя keep-alive); сервисы с длинными соединениями могут тянуть долго — ровно тот механизм, за которым lifecycle-тест утверждает «старые keep-alive соединения остаются на старом скрипте, терминальная утилизация без тайм-аута».

29.5 Контракт

  • Принадлежность: каждый запрос видит одно поколение; смена атомарна, порт не мерцает.
  • Параллельность: за время сосуществования поколения не мешают друг другу; старое не принимает новых запросов.
  • Утилизация: время смерти старого поколения = возврат последнего lease (детали GC — гл. 30).

Частые ошибки

Надежда, что старое поколение ещё принимает запросы
Симптом
Ожидания вроде «трафик поровну между старым и новым» после перезагрузки разбиваются.
Причина
После переключения указателя слота все новые соединения идут в новое поколение — старое только выпускает. Нужен канареечный розлив — берите vhost / два server, а не пропорции поколений.
«В логе не сменился gen» принято за провал перезагрузки
Симптом
После подачи в логе нет нового номера gen — решено, что не подействовало.
Причина
Номер gen живёт в строке script loaded; если отпечаток содержимого тот же (Revision совпал) или цель написана неверно (сверьте поле result.Target), смены поколения вовсе не было. Ориентируйтесь на терминал xsReloadQuery, а не на догадки по логу.

Упражнения

БазовыйХронология по памяти

Не подглядывая, нарисуйте хронологию 29.2 с отметками t3 (необратимая точка) и t5 (переключение указателя).

Приёмка: объяснимо, почему t2 и t8 находятся до и после необратимой точки.
ПродвинутыйНаблюдение lease

Длинным соединением (WS) + тремя быстрыми перезагрузками соберите три доказательства, что соединение от начала до конца обслуживает одно поколение (поведенческий маркер, gen в логе, данные Swap).

Приёмка: три доказательства сходятся; старое поколение финализируется только после конца соединения.
ВызовСквозная незаметность

По образцу reload_matrix: поток-молот долбит /ready каждые 10 мс, вы тем временем меняете поколение 5 раз; посчитайте отказы.

Приёмка: отказов 0; в логе 5 finalized и 0 force-free.

Итоги

ТезисВывод
Скелет хронологииснимок→компиляция→Swap→необратимая точка→Init→переключение указателя→lease в ноль→GC
Стабильность портаслоты держат socket и переключают pData — handoff без мерцаний
Двойные поколениястарое только выпускает; каждый запрос видит одно поколение
Таблица leaseу запросов/соединений/дейтаграмм/таймеров/перехватов свои точки взятия и возврата
Что дальше по xrt