Передача состояния: данные переживают перезагрузку
Статики изолированы по поколениям (гл. 05) — смена поколения их обнуляет, если только не передать явно. ServiceSwap / xsSwapTake и есть тот мост; глава собирает все заложенные с гл. 03 нити.
Обзор
28.1 Введение
Проблема вырастает из ямы гл. 05: суммарные запросы, живые соединения, горячий кэш — куда идёт это «ценное между поколениями» состояние при смене? Ответ — пара контрактных функций: старое поколение перед уходом экспортирует снимок через ServiceSwap, новое забирает его в ServiceInit через xsSwapTake. Носитель — дерево xvalue: изоморфно ручкам Custom (гл. 09) и JSON (гл. 18), правила владения переиспользуются полностью.
28.2 Принцип неразрушающего снимка
«Неразрушающе» — ключевое слово: вызов Swap не означает смерть старого поколения. В крайних случаях (отменённая фиксация, гонки терминальных колбэков) старое поколение обслуживает и после Swap — поэтому Swap ни в коем случае не закрывает ресурсы и не меняет поведение. Это «фотография», а не «переезд».
28.3 Что передавать
| Тип состояния | Решение | Причина и практика |
|---|---|---|
| Статистические счётчики (запросы/попадания) | передавать | чистые значения; снимок продолжает их бесплатно |
| Кэш контента (горячие данные) | можно и так, и так | маленький кэш — передача экономит прогрев; большой — пересборка чище (без распухания снимка) |
| Реестры сеансов/соединений | не передавать | объекты соединений под защитой lease старого поколения — сама смена поколения значит: старые соединения дорабатываются старым кодом; новые входят в таблицу нового |
| Дескрипторы файлов / db | никогда указатели | указатели между поколениями — источник висяков. Два верных пути для db: переоткрытие в каждом поколении (путь демо — файловый слой делит данные естественно) или дескриптор глобальным синглтоном с явным управлением (гл. 32) |
| Производное от конфигурации (таблицы маршрутов и пр.) | сброс | Init пересоздаёт — оно из конфигурации; пересборка гарантирует согласованность с новым кодом |
28.4 Полная реализация: счётчики и кэш
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 это фотосессия. Закрывающие действия принадлежат ServiceUnit (настоящей утилизационной деструкции).
- Симптом
- Указатель, забранный новым поколением, смотрит в память, уже собранную GC старого.
- Причина
- xvalue не держит семантики указателей — та память умирает при деструкции старого поколения. Ресурсы — в глобальный синглтон (самоуправляемое время жизни) или просто пересоздавайте в поколении.
Упражнения
Передайте суммарный счётчик запросов: нагрузкой набить → перезагрузка → убедиться, что не обнулился и продолжает расти.
Добавьте поле ver; намеренно напишите новое поколение с ver=2, но другими именами полей — пронаблюдайте безопасный сброс старого снимка.
Сделайте обе версии: A = переоткрытие db в каждом поколении; B = глобальный синглтон-дескриптор + счётчик ссылок между поколениями. Под нагрузкой сравните ошибки и задержки в момент перезагрузки.
Итоги
| Тезис | Вывод |
|---|---|
| Механизм | ServiceSwap экспортирует неразрушающий снимок → xsSwapTake в Init нового поколения |
| Критерий | значения передаются; ресурсы нет (переоткрытие или глобальный синглтон); производное конфигурации сбрасывается |
| Версионирование | у снимка поле ver; нечитаемые версии отбрасываются |
| Окно | Take только в ServiceInit нового поколения; владение переходит приложению |
- справочник xrt · value — правила построения и доступа носителя снимков (мантра владения гл. 18 действует полностью)