feat(watchdog): приостановка вторичных ремонтов во время устранения первопричины

Падение WAN-интерфейса влечет за собой сбой прокси, DNS и проверок доступности. У каждого из них был свой подписчик, и каждый независимо решал перезапустить службу — три ремонта на один сбой, ни один из которых не мог сработать до восстановления WAN. Ограничение частоты в safe_proxy_restart() сдерживало этот каскад только постфактум, но в коде не было явной связи, что сбои прокси и DNS — последствия сбоя WAN.

Шина уже ранжирует восстановители: WAN выполняется первым с приоритетом 10, sing-box с 20, прокси и DNS — с 40 и 50. Чтобы не вводить вторую иерархию, эти ранги вынесены в именованные константы (PRIORITY_WAN, PRIORITY_SINGBOX, PRIORITY_PROXY, PRIORITY_DNS), используемые как при подписке, так и при проверке подавления.

Восстановитель, устраняющий первопричину, удерживает свой ранг на время ремонта. Вторичный восстановитель приостанавливает работу и выводит сообщение в лог. Три свойства гарантируют надежность: причина никогда не подавляет сама себя; следствие не может подавить причину; срок удержания ограничен, поэтому зависший ремонт не заблокирует вочдог навсегда.

Удержание снимается по событию восстановления, не дожидаясь таймаута. Для этого добавлено новое событие EV.WAN_UP — ранее probe_wan публиковал только сбои.

Удержание WAN захватывается до `ifdown wan`, так как сам ifdown вызовет ошибки прокси и DNS ещё до завершения работы ifup.

Тест `tests/heal_suppression.sh` проверяет ранги, точки захвата/освобождения, защиту восстановителей и логирование пропусков.
This commit is contained in:
Dushnilin
2026-08-08 12:47:23 +03:00
parent b4ca111d34
commit 5a10cc158b
4 changed files with 253 additions and 7 deletions
@@ -56,6 +56,7 @@ const EV = {
TPROXY_DOWN: "tproxy.down",
RPCD_FD_LEAK: "rpcd.fd_leak",
WAN_DOWN: "wan.down",
WAN_UP: "wan.up",
SUBNETS_EMPTY: "subnets.empty",
SUBSCRIPTION_UNREACHABLE: "subscription.unreachable",
CONFIG_CORRUPT: "config.corrupt",
@@ -698,6 +699,8 @@ function controller(bus, opts) {
if (no_address || no_gateway)
bus.emit(EV.WAN_DOWN, { iface: iface, no_address: no_address, no_gateway: no_gateway });
else
bus.emit(EV.WAN_UP, { iface: iface });
}
// ── Probe: community subnet nft sets ──────────────────────────────────────