0067. Самовосстановление служб: падение, потеря владения и зависание
Статус: accepted. Owner: Ian Panaev. Дата: 2026-10-04.
Контекст
API, worker и агент резервных копий работают под супервизором платформы:
- systemd:
Restart=always,RestartSec=10,StartLimitIntervalSec=0; - WinSW и диспетчер служб Windows:
onfailure restart,sc failure … restart/10000сfailureflag 1; последнее действие SCM повторяет при каждом следующем сбое; - Docker Compose:
restart: unless-stopped.
Супервизор перезапускает процесс, который завершился. Падение (uncaught, OOM, kill) так и покрывалось. Потерю владения хранилищем или аренды API и worker уже переводили в выход с кодом 1. Незапрошенный выход с кодом 0 launcher превращает в 1, иначе SCM не считает его сбоем.
Не покрывалось зависание. Процесс с заблокированным event loop (бесконечный цикл, блокирующий вызов, зависание в нативном коде) не отвечает и не завершается. Ни systemd, ни SCM, ни Docker его не трогают; healthcheck Compose только помечает контейнер unhealthy.
Решение
Сторожевой поток в каждом процессе службы (API, worker, агент бэкапов):
- главный поток раз в секунду увеличивает счётчик в
SharedArrayBuffer; - отдельный
worker_threadраз в секунду читает его; - если счётчик не менялся
ARKVORY_WATCHDOG_SECONDSпроверок подряд (по умолчанию 60), поток пишет в stderr записьprocess.stalled(stalledSeconds) и завершает процесс (SIGKILL; на Windows — завершение с кодом 1); - дальше супервизор поднимает процесс, как после падения.
Пропуски считаются по тикам самого сторожа, а не по часам. Когда машина спит или VM на паузе, оба потока стоят, и после пробуждения ложного срабатывания нет.
Код потока берёт встроенные модули через process.getBuiltinModule: eval-код работает и под CommonJS, и под ES-модулем родителя. Сбой самого сторожа не останавливает службу, а пишет process.watchdog_failed.
ARKVORY_WATCHDOG_SECONDS: 10–3600 секунд, 0 — выключено (для отладчика, который ставит процесс на паузу). Неверное значение — ошибка запуска.
Docker Desktop на Windows — приложение пользователя: до его запуска ни один контейнер не работает, какой бы ни была политика перезапуска. Установка Compose и status предупреждают, если в Docker Desktop выключен автозапуск при входе в систему.
Матрица
| Что случилось | systemd | Службы Windows | Docker Compose |
|---|---|---|---|
| Падение, kill, OOM | перезапуск через 10 с | перезапуск через 10 с | перезапуск с нарастающей паузой |
| Выход без запроса остановки (в т.ч. код 0) | перезапуск | launcher даёт код 1 → перезапуск | перезапуск |
| Потеря владения хранилищем или аренды (обрыв БД) | выход 1 → перезапуск | выход 1 → перезапуск | выход 1 → перезапуск |
| Зависание главного потока | сторож → перезапуск | сторож → перезапуск | сторож → перезапуск |
| БД недоступна при старте | выход 1, повтор каждые 10 с | выход 1, повтор каждые 10 с | выход 1, повтор с паузой |
| Перезагрузка машины | WantedBy=multi-user.target | автозапуск (отложенный) | при старте Docker; Docker Desktop — после входа пользователя с включённым автозапуском |
| Плановая остановка оператором | не перезапускается | не перезапускается | docker compose stop — не перезапускается |
Последствия
- Отладчик, поставивший службу на паузу дольше порога, получит её перезапуск; для отладки задайте
ARKVORY_WATCHDOG_SECONDS=0. - Долгая синхронная работа в главном потоке дольше порога считается зависанием. Правила проекта и так запрещают блокировать event loop.
- Агент PostgreSQL и сама БД отдельным сторожем не охвачены; их перезапускают те же супервизоры при падении.
Проверки
tests/event-loop-watchdog.test.mjs:- зависший процесс завершён с записью
process.stalled; - короткие синхронные всплески и выключенный сторож процесс не трогают;
- родитель как ES-модуль;
- разбор настройки.
- зависший процесс завершён с записью
- Гейт
deployment-containers: главные потоки API, worker и агента ставятся на паузу через инспектор (SIGUSR1,Debugger.pause); сторож завершает их, Docker перезапускает, установка снова готова. - Гейт
native-install(Linux systemd локально и на GitHub, Windows на GitHub): то же для установленных служб, на Windows инспектор открывается черезprocess._debugProcessиз сеанса 0 (разовая задача SYSTEM): службы публикуют обработчик отладки только в своём сеансе. - Падения, три подряд, и плановая остановка — прежние гейты
deployment-servicesиnative-install. tests/engine-autostart.test.mjs: чтение настройки автозапуска Docker Desktop.