Глава 41 / 51
Том 5 · Расширения, эксплуатация и практика

Таймеры и фоновые задачи: временное измерение скрипта

Два момента исполнения помимо запросов: короткие быстрые таймерные колбэки (на попечении хоста) и долгая блокирующая фоновая работа (собственные потоки). Где провести черту — тема главы.

Обзор

Позиционирование: инструментальная глава. Два API (xsTimerAfter/Cancel) плюс один вопрос потоковой модели. Наблюдение файлов из гл. 27 и сторож пульса из гл. 22 получают здесь единый временной механизм.

41.1 Введение

«Временные» потребности сервиса просты: очистка по истечении, периодическая статистика, отложенные повторы, выбраковка по тайм-ауту. Ответ XServer — одноразовый таймер: периодической версии сознательно нет (см. паттерн перезапуска 41.3), ведь периодическая семантика тонко взаимодействует с жизненным циклом поколений, а явный перезапуск держит каждый шаг под контролем. Таймер привязан к создавшему его поколению (семантика устаревания — гл. 30); колбэк исполняется на потоке движка — вызовы хост-API безопасны.

41.2 Таймеры и поколения

Базовое применение и привязка к поколению
static void OnCleanup(void* pUserData) { ... }

/* в ServiceInit: сработает один раз через 30 секунд */
uint64 iTimer = xsTimerAfter(pHost, 30000, OnCleanup, NULL);
/* 0 = неудача; ненулевой ID можно Cancel */

/* три закона привязки к поколению: */
/* 1. захватывается «текущее поколение» создающего потока — статики колбэка  */
/*    всегда принадлежат ему                                                   */
/* 2. смена поколения → все таймеры старого устаревают (в новое не выстрелят) */
/* 3. продолжение — Init нового поколения перевешивает (периодика становится явной) */

Этот дизайн уничтожает целый класс багов: «таймер старого поколения потрогал статики нового». Цена — принять «таймеры не живут между поколениями», а межпоколенческое продолжение и так должно идти через Swap (гл. 28).

41.3 Паттерн периодических задач

Перезапускающаяся периодическая задача
static XS_HostInfo* g_pHostForTick;  /* записан в Init (колбэк не получает host) */

static void OnTick(void* pUserData)
{
    DoPeriodicWork();                      /* быстрое: статистика/чистка/зонды */
    /* перевесить следующий запуск (вешается, лишь пока живо текущее поколение) */
    (void)xsTimerAfter(g_pHostForTick, 5000, OnTick, NULL);
}
/* ServiceInit: g_pHostForTick = pHost; xsTimerAfter(pHost, 5000, OnTick, NULL); */

Сила самоперезапуска — в «автоматической остановке со сменой поколения»: после последнего выстрела старого поколения перевеса нет (Init нового вешает свой), задача естественно следует поколению — логика отмены не нужна. Обратите внимание: перевес идёт после логики задачи — при ошибке можно не перевешивать (остановка тоже стратегия).

41.4 Граница фоновых потоков

Таймерные колбэки бегают на потоках движка — обязаны быть короткими (контракт гл. 03: задержал worker — задержал все запросы). Долгая работа (массовые вычисления, внешние вызовы, обработка больших файлов) уходит в отдельный поток, и тут встаёт классический вопрос: может ли фоновый поток звать API хоста/xrt?

ОперацияДоступность из фонаПаттерн
Универсальные вычисления xrt (контейнеры/строки/JSON)✓ доступноAPI библиотечного уровня несут собственную потоковую семантику
Файлы / базы (SQLite)✓ доступнодескрипторы под вашим управлением (FULLMUTEX и т.п.)
Приём-передача по объектам соединений✗ в обычном режиме запрещеносоединения принадлежат событийному циклу драйвера — фоновая отправка идёт через «доставку обратно в движок»
Представления колбэков / объекты запросов✗ запрещеновне стека колбэка недействительны (железное правило гл. 03)
API слоя XServer (reload/timer/topo)Submit/Query ✓спроектированы неблокирующими; запросы топологии — по дисциплине lease

Каноническая структура — очередь задач: фоновый поток делает тяжёлое и по завершении доставляет результат в движок (опрос таймером или пробуждение очереди), а поток движка трогает соединения и ответы — направление всегда чистое: тяжёлое наружу, результат обратно, IO остаётся движку.

41.5 Контракт

  • Коротко и быстро: таймерные колбэки короткие; долгая работа — в фоновый поток + доставка результата.
  • Поколения: таймеры не пересекают поколения; периодика самоперезапускается; продолжение состояния — через Swap.
  • IO: соединения и представления трогаются только на потоках движка; фоновые потоки передают через очередь задач.

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

Внешний вызов внутри таймерного колбэка
Симптом
Как только тикает задача — задержки запросов взлетают по всему сервису.
Причина
Колбэк занял worker — внешний HTTP-вызов тянется секунды. Несите в фоновый поток; таймер отвечает только за «запуск».
Фоновый поток ответил напрямую
Симптом
Случайные перепутанные ответы / падения.
Причина
Запрос уже завершён (колбэк вернулся), а фон ещё пишет по указателю представления. Фон завершает «данные»; ответ отправляет доставщик на стороне движка.

Упражнения

БазовыйОчистчик по истечении

Каждые 5 секунд выметать из реестра сеансы без активности 60 секунд (паттерн самоперезапуска).

Приёмка: подброшенные зомби-сеансы выметаются; после перезагрузки очистчик продолжает (пересоздан в Init нового поколения).
ПродвинутыйАсинхронный агрегатор

Реализуйте /stats/slow: фоновый поток агрегирует 10 000 строк; по завершении результат доступен запросу (очередь задач + опрос флага готовности таймером).

Приёмка: задержки запросов при агрегации не страдают; результат в итоге виден.
ВызовВременные задачи под давлением смен

При работающей периодической задаче смените поколение 10 раз; убедитесь: задача следует текущему поколению, выстрелов старого нет.

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

Итоги

ТезисВывод
Таймерыодноразовые, привязаны к создавшему поколению, колбэки на потоках движка
Периодичностьпаттерн самоперезапуска — остановка со сменой автоматически, без логики отмены
Черта разделакороткое и быстрое — в колбэк; долгая работа — в фоновый поток + доставка результата
Принадлежность IOсоединения/представления только на потоках движка; фон передаёт через очередь задач
API, затронутые в главе
Что дальше по xrt