Таймеры и фоновые задачи: временное измерение скрипта
Два момента исполнения помимо запросов: короткие быстрые таймерные колбэки (на попечении хоста) и долгая блокирующая фоновая работа (собственные потоки). Где провести черту — тема главы.
Обзор
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 секунд (паттерн самоперезапуска).
Реализуйте /stats/slow: фоновый поток агрегирует 10 000 строк; по завершении результат доступен запросу (очередь задач + опрос флага готовности таймером).
При работающей периодической задаче смените поколение 10 раз; убедитесь: задача следует текущему поколению, выстрелов старого нет.
Итоги
| Тезис | Вывод |
|---|---|
| Таймеры | одноразовые, привязаны к создавшему поколению, колбэки на потоках движка |
| Периодичность | паттерн самоперезапуска — остановка со сменой автоматически, без логики отмены |
| Черта раздела | короткое и быстрое — в колбэк; долгая работа — в фоновый поток + доставка результата |
| Принадлежность IO | соединения/представления только на потоках движка; фон передаёт через очередь задач |
- справочник xrt · thread — создание фоновых потоков и материал очереди задач
- справочник xrt · time — высокоточные часы (замер времени задач)