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

Запуск и опрос: отдаём перезагрузку тем, кому нужно

XServer не предписывает «кто и когда перезагружает» — он даёт лишь Submit/Query. Глава проектирует поверхность запуска: управляющие маршруты для эксплуатации, наблюдение файлов для разработки и представление результатов.

Обзор

Позиционирование: модель прошлой главы ложится здесь в «продукт». Управляющий маршрут — выбор большинства развёртываний (в гл. 40 он вырастет в систему плагинов); наблюдение файлов даёт dev-опыт «сохранил в IDE — сразу видно».

27.1 Введение

Дизайн запуска отвечает на три вопроса: кто может запускать (полномочия), с какой гранулярностью (host / server / all) и как узнать результат. Занятно, что XServer оставляет все три приложению — потому что «плоскость управления принадлежит слою приложения» (яма из гл. 1). Глава даёт два проверенных ответа.

27.2 Паттерн управляющего маршрута

Управляющий маршрут /admin/reload (полный паттерн)
static void Handle_Admin_Reload(XS_HttpReq* pReq, const RouteParamHTTP* a, uint32 n)
{
    xvalue* pOut = xrtValueObject();
    XS_ReloadId iId;

    /* 1. аутентификация: плоскость управления обязана сидеть за границей доверия
     *    (здесь фиксированный токен; в продакшн подключается любая система) */
    if ( !CheckAdminToken(pReq) ) {
        (void)ReplyText(pReq, 403, "forbidden");
        goto Out;
    }

    /* 2. гранулярность: scope=host|server|all */
    if ( ScopeIsAll(pReq) )      iId = xsReloadAllSubmit();
    else if ( ScopeIsServer(pReq) ) iId = xsReloadServerSubmit(sName);
    else                        iId = xsReloadHostSubmit(pReq->host);

    /* 3. немедленно ответить приёмом (без ожидания терминала) */
    if ( iId == 0 ) {
        (void)ReplyText(pReq, 503, "reload queue full");
        goto Out;
    }
    /* out: {"id":123} — результат через /admin/reload/status?id=123 */
Out:
    xrtValueRelease(pOut);
}

Что только что произошло — проектные решения:

  • Приём отделён от результата: POST сразу отдаёт ID, результат — отдельный GET-маршрут; ни один поток запроса не ждёт терминала (Submit и так неблокирующий — ждать тем более нельзя)
  • Три гранулярности: на каждый день host (минимальный радиус поражения); структурные изменения — server; глобальная правка конфигурации — all (семантика пакетной транзакции)
  • Аутентификация — первой строкой приложения: управляющий маршрут — это «маршрут, меняющий поведение сервиса»; полномочия те же, риск другой

27.3 Автозапуск: сохранил — перезагрузилось

Триггер dev-опыта — наблюдение каталога devfile и подача при изменении:

Триггер на наблюдении файлов (паттерн)
/* ServiceInit: запустить наблюдение + периодические проверки (наблюдение xrt / опрос mtime — оба варианта) */
static void OnSrcChanged(void* pUserData)
{
    XS_HostInfo* pHost = (XS_HostInfo*)pUserData;
    /* антидребезг: шторм сохранений подаёт только последний (latest-wins как подстраховка) */
    (void)xsReloadHostSubmit(pHost);
}
/* внимание: таймеры умирают со своим поколением — в Init нового перевесить наблюдение */

Антидребезг двухслойный: приложение склеивает быстрые подряд идущие изменения в коротком окне; а если промахнулось — latest-wins выдавит просроченные намерения в SUPERSEDED — худший случай такого триггера: пара лишних компиляций, промежуточное состояние никогда не выйдет в свет. В продакшне автозапуск обычно выключен: перезагрузка — управляемое изменение, а не событие файловой системы.

27.4 Представление результатов

/admin/reload/status (опрос результата)
static void Handle_Admin_ReloadStatus(XS_HttpReq* pReq, const RouteParamHTTP* a, uint32 n)
{
    XS_ReloadResult t;
    xvalue* pOut = xrtValueObject();

    if ( !xsReloadQuery(ParseId(pReq), &t) ) {
        (void)ReplyText(pReq, 404, "no such id");
        return;
    }
    xrtValueObjectSetNew(pOut, XRT_STR_LITERAL("id"),
        xrtValueInt64((int64)t.Id));
    xrtValueObjectSetNew(pOut, XRT_STR_LITERAL("state"),
        xrtValueInt64((int64)t.State));
    xrtValueObjectSetNew(pOut, XRT_STR_LITERAL("revision"),
        xrtValueInt64((int64)t.Revision));
    xrtValueObjectSetNew(pOut, XRT_STR_LITERAL("message"),
        xrtValueString(xrtStrView(t.Message)));  /* причина FAILED */
    (void)ReplyJSON(pReq, 200, pOut);
    xrtValueRelease(pOut);
}

Терминал приходит за десятки миллисекунд — секунды (по объёму скриптов); фронтенд может опрашивать или обновлять — Query идемпотентен и без побочных эффектов.

27.5 Контракт

  • Аутентификация: у управляющего маршрута обязательна прикладная проверка; он меняет поведение сервиса — это не обычный бизнес-эндпоинт.
  • Гранулярность: по умолчанию host; all — только для глобальных изменений конфигурации (пакетная транзакция).
  • Представление: приём отделён от результата; опрос идемпотентен.

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

Голый управляющий маршрут
Симптом
Доступный из интернета /admin/reload — любой может сменить код вашего сервиса.
Причина
Поверхность запуска — это поверхность изменений. Минимум: отдельный vhost (привязка к интрасети) + токен; лучше — дизайн границы доверия из гл. 44.
Наблюдение файлов без учёта поколений
Симптом
После первой горячей перезагрузки автотриггер умирает.
Причина
Таймеры/дескрипторы наблюдения умирают со старым поколением (гл. 30) — ServiceInit нового поколения обязан перевесить наблюдение. Обратная сторона «бесплатной безопасности» изоляции поколений.

Упражнения

БазовыйУправляющий маршрут на три уровня

Реализуйте подачу scope=host/server/all и опрос status; прогоните весь поток curl'ом.

Приёмка: каждый уровень успешен по разу; при битом скрипте status отдаёт FAILED с причиной.
ПродвинутыйТокен-аутентификация

Добавьте к управляющему маршруту сверку токена за постоянное время; неверные токены — с ограничением скорости (идея деградации из гл. 22).

Приёмка: неверный токен 403; перебор задушен до незаметной скорости.
ВызовТриггер режима разработки

Реализуйте «сохранил — перезагрузилось» на опросе mtime (антидребезг + межпоколенческий перевес); измерьте сквозную задержку от сохранения до вступления в силу.

Приёмка: 10 сохранений подряд выпускают только финальную версию; сквозная задержка < 2 с.

Итоги

ТезисВывод
Три вопроса запускакто (аутентификация), гранулярность (host/server/all), результат (Query)
Управляющий маршрутприём отделён от результата; аутентификация первой строкой приложения
Автозапускнаблюдение + Submit; антидребезг двухслойный (склейка + latest-wins)
Поколениятриггер умирает с поколением; перевес в новом Init
Что дальше по xrt