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

Передача состояния: данные переживают перезагрузку

Статики изолированы по поколениям (гл. 05) — смена поколения их обнуляет, если только не передать явно. ServiceSwap / xsSwapTake и есть тот мост; глава собирает все заложенные с гл. 03 нити.

Обзор

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

28.1 Введение

Проблема вырастает из ямы гл. 05: суммарные запросы, живые соединения, горячий кэш — куда идёт это «ценное между поколениями» состояние при смене? Ответ — пара контрактных функций: старое поколение перед уходом экспортирует снимок через ServiceSwap, новое забирает его в ServiceInit через xsSwapTake. Носитель — дерево xvalue: изоморфно ручкам Custom (гл. 09) и JSON (гл. 18), правила владения переиспользуются полностью.

28.2 Принцип неразрушающего снимка

управляющий поток reload │ все кандидаты скомпилированы (предпроверка без побочных эффектов пройдена) ▼ ServiceSwap (старое поколение, поток reload) ── экспортирует снимок xvalue* │ /* неразрушающе: старое поколение продолжает обслуживать и после экспорта! */ ▼ переход необратимой точки фиксации (атомарная публикация нового поколения) ▼ ServiceInit (новое поколение) ── xsSwapTake забирает снимок ── продолжает ▼ ссылки старого поколения обнулились → GC (владение снимком перешло к новому в момент Take)

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

28.3 Что передавать

Тип состоянияРешениеПричина и практика
Статистические счётчики (запросы/попадания)передаватьчистые значения; снимок продолжает их бесплатно
Кэш контента (горячие данные)можно и так, и такмаленький кэш — передача экономит прогрев; большой — пересборка чище (без распухания снимка)
Реестры сеансов/соединенийне передаватьобъекты соединений под защитой lease старого поколения — сама смена поколения значит: старые соединения дорабатываются старым кодом; новые входят в таблицу нового
Дескрипторы файлов / dbникогда указателиуказатели между поколениями — источник висяков. Два верных пути для db: переоткрытие в каждом поколении (путь демо — файловый слой делит данные естественно) или дескриптор глобальным синглтоном с явным управлением (гл. 32)
Производное от конфигурации (таблицы маршрутов и пр.)сбросInit пересоздаёт — оно из конфигурации; пересборка гарантирует согласованность с новым кодом
Критерий одной фразой: значения передаются, ресурсы нет; слой персистентности за ресурсами (файлы/базы) межпоколенческий от природы. Передача сеансового состояния рискует «новый код не читает старую структуру» — кладите в снимок поле версии.

28.4 Полная реализация: счётчики и кэш

Полный цикл Swap/Take (паттерн контрактных исходников)
static uint64 G_iTotal;        /* всего запросов: передаётся */
static uint64 G_iHits;          /* попадания кэша: передаётся */
/* G_Cache, большой кэш: пересборка в каждом поколении (решение 28.3) */

static bool ServiceSwap(XS_HostInfo* pHost, xvalue** ppShared)
{
    xvalue* pState = xrtValueObject();
    xrtValueObjectSetNew(pState, XRT_STR_LITERAL("ver"),
        xrtValueInt64(1));                       /* версия снимка */
    xrtValueObjectSetNew(pState, XRT_STR_LITERAL("total"),
        xrtValueInt64((int64)G_iTotal));
    xrtValueObjectSetNew(pState, XRT_STR_LITERAL("hits"),
        xrtValueInt64((int64)G_iHits));
    *ppShared = pState;
    return true;      /* снимок действителен; ресурсы не закрываются */
}

static void ServiceInit(XS_HostInfo* pHost)
{
    xvalue* pState = xsSwapTake(pHost);          /* действительно только здесь */
    if ( pState != NULL ) {
        if ( xrtValueInt64(xrtValueObjectGet(pState, "ver")) == 1 ) {
            G_iTotal = (uint64)xrtValueInt64(xrtValueObjectGet(pState, "total"));
            G_iHits  = (uint64)xrtValueInt64(xrtValueObjectGet(pState, "hits"));
        }
        xrtValueRelease(pState);                 /* забрали — владение у приложения */
    }
    Cache_Rebuild();                             /* кэш пересобирается в поколении */
}

Что только что произошло: три детали — снимок несёт поле ver (версию, которую новое поколение не читает, отбрасываем — лучше пусто, чем неверно); окно Take существует только внутри ServiceInit (прозевали — ушло); Release после чтения (владение перешло в момент Take).

28.5 Контракт

  • Swap: неразрушающий снимок — ничего не закрывать, поведения не менять; false = в этом раунде без передачи.
  • Take: действительно только в ServiceInit нового поколения; взятие передаёт владение.
  • Содержимое: только значения; дескрипторы и указатели в снимок не входят; обязательно поле версии.

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

В Swap закрыли базу
Симптом
Обслуживаемые старым поколением после Swap запросы внезапно падают (крайний случай: после отмены фиксации сервис калечится).
Причина
Нарушение принципа неразрушаемости — Swap это фотосессия. Закрывающие действия принадлежат ServiceUnit (настоящей утилизационной деструкции).
void* засунули в снимок
Симптом
Указатель, забранный новым поколением, смотрит в память, уже собранную GC старого.
Причина
xvalue не держит семантики указателей — та память умирает при деструкции старого поколения. Ресурсы — в глобальный синглтон (самоуправляемое время жизни) или просто пересоздавайте в поколении.

Упражнения

БазовыйСчётчики продолжаются

Передайте суммарный счётчик запросов: нагрузкой набить → перезагрузка → убедиться, что не обнулился и продолжает расти.

Приёмка: вывод /stats непрерывен до и после; за перезагрузку ноль ошибок нагрузки.
ПродвинутыйЭволюция с версией

Добавьте поле ver; намеренно напишите новое поколение с ver=2, но другими именами полей — пронаблюдайте безопасный сброс старого снимка.

Приёмка: при несовпадении счётчики сбрасываются в 0 без падений; сброс виден в логе.
ВызовЭксперимент с дескриптором db

Сделайте обе версии: A = переоткрытие db в каждом поколении; B = глобальный синглтон-дескриптор + счётчик ссылок между поколениями. Под нагрузкой сравните ошибки и задержки в момент перезагрузки.

Приёмка: вы можете назвать условия применимости каждой (цена переоткрытия vs сложность синглтона).

Итоги

ТезисВывод
МеханизмServiceSwap экспортирует неразрушающий снимок → xsSwapTake в Init нового поколения
Критерийзначения передаются; ресурсы нет (переоткрытие или глобальный синглтон); производное конфигурации сбрасывается
Версионированиеу снимка поле ver; нечитаемые версии отбрасываются
ОкноTake только в ServiceInit нового поколения; владение переходит приложению
Что дальше по xrt
  • справочник xrt · value — правила построения и доступа носителя снимков (мантра владения гл. 18 действует полностью)