- каталог задач переехал в tasks/ в корне, спринт упразднён — приоритет теперь порядок строк в BACKLOG.md, четыре задачи набора вернулись в беклог - гейт: путь docs.py переведён на av-dev-docs вместо снесённого av-dev-pm, добавлены шаги tasks.py check и openspec.py check - относительные ссылки внутри задач и ссылки из docs/ на задачи починены
7.5 KiB
🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой
- Тип: fix
- Категория: Инфраструктура
- Зачем: недоступный qBittorrent опрашивается каждые 5 с и даёт WARN на каждом тике: нужен экспоненциальный бэкофф до минутного потолка со сбросом по первому успеху и ERROR на устойчивой деградации
Остаток от задачи «классификация доменных ошибок + конвенции логирования» (основное реализовано, см. ниже) плюс бэкофф опроса, заказанный 2026-08-06. Речь о поведении фонового цикла, пока зависимость лежит: с какой частотой он её дёргает и каким уровнем об этом пишет.
Что уже сделано (не переоткрывать)
Коммит f8fb4fa (Tier A) + коммит этой задачи закрыли:
- Классификация доменных ошибок: sentinel
worker.ErrInvalidInput→400; обёрткиErrConflictв Cancel/Retry/Defer/Undo;layout.ErrCollision→409 вclassifyErrи ветка в tgbot;logCmdотносит новые классы в DEBUG. - Конвенции:
logging.md— команды воркера = доменная граница, таблица уровней доменных отказов (граница команды vs асинхронная стадия), правило про*url.Error/секреты в URL, канон категорииstate transition(унифицированы cancel/retry/relink/recovery).errors.md— таблица маппинга ошибка→статус, развилка «транзиентный ответ vs персистентная диагностика» решена как (а):error_msg/reasons— операторская поверхность владельца (сырой текст ок, секреты запрещены; аудит показал, что секреты туда не текут). - Мелочи: reason-коды const-блок; лог-поля
id→download_id; preview WARN; комментарий уparseIgnored.
Остаток
Шум ext.* ERROR решено оставить как есть (2026-08-06). Запись
«зависимость недоступна» на каждом тике — легитимный сигнал транспортного слоя,
и гасится он уровнем сбора логов, а не кодом. Варианты с пониженным уровнем у
logging.ExtCall и с дедупом отклонены: первый заводит второе правило уровня
для того же класса вызовов, второй даёт транспортному логгеру память о
состоянии.
Остаются две вещи, и обе стоят на одном счётчике подряд-идущих сбоев тика.
Бэкофф опроса (решение 2026-08-06). Пока qBittorrent недоступен, цикл
продолжает дёргать его каждые poll_interval (5 с) — недоступную зависимость
незачем опрашивать с рабочей частотой. Интервал растёт экспоненциально от
poll_interval до потолка порядка минуты; первый успешный ответ возвращает
рабочий интервал сразу, без ступенчатого спуска. Бэкофф заодно снимает и остроту
шума: записей становится столько же на событие, но событий — единицы в минуту.
Эскалация уровня. Сейчас сбой тика — всегда WARN, сколько бы тиков
подряд он ни падал. docs/conventions/logging.md требует иного: устойчивый сбой
N тиков подряд — это реальная деградация, и она пишется ERROR.
Воспроизведение
- Остановить qBittorrent (локально, не на umbar).
- Смотреть лог воркера в течение нескольких минут поллинга.
- Наблюдается: запрос к qBittorrent уходит каждые 5 секунд всё время
недоступности, а доменная запись
poll failedидёт WARN на каждом тике и остаётся WARN бесконечно. - Ожидается: интервал опроса растёт до минутного потолка, а после N подряд-идущих неудачных тиков уровень поднимается до ERROR — деградация отличается от разового промаха.
- Поднять qBittorrent обратно: опрос возвращается к
poll_intervalс первого успешного ответа.
Затрагивает
- цикл поллинга воркера (
internal/worker) — счётчик подряд-идущих сбоев, текущий интервал тика и его сброс по успеху; - секция
[worker]конфига иconfig.example.toml— потолок бэкоффа и порог эскалации; docs/database.md, таблица «Настройки с числовым значением» — дом обоих чисел;docs/architecture.md, «Характер потока» — там сказано, что фон непрерывный с периодом поллинга; переменный интервал это уточняет;docs/conventions/logging.md— правило эскалации уже записано, меняться не должно; задача приводит код к нему.
Критерии приёмки
- При подряд-идущих сбоях интервал опроса растёт экспоненциально от
poll_intervalи упирается в потолок из конфига, дальше не растёт (оракул: тест цикла с подставным клиентом и управляемыми часами — проверяет последовательность интервалов). - Первый успешный ответ возвращает
poll_intervalнемедленно (оракул: тот же тест, сценарий «серия сбоев, успех, сбой» — после успеха интервал рабочий). - Сбой тика ниже порога пишется WARN, начиная с N-го подряд — ERROR, а успешный тик сбрасывает счётчик (оракул: тест, считающий уровни записей на сценарии «сбой, сбой, успех, сбой»).
- Потолок бэкоффа и порог эскалации читаются из конфига и описаны в
config.example.tomlс единицами и диапазоном (оракул:task gate, шаг канона — сверка сdatabase.md).