# 🐞 Тормозить опрос qBittorrent бэкоффом при недоступности и эскалировать устойчивый сбой - **Тип:** fix - **Категория:** Инфраструктура — единственный ready-дефект секции: недоступный qBittorrent опрашивается каждые 5 с и пишет WARN на каждом тике бесконечно; решение (бэкофф до минуты + ERROR на N подряд) принято 2026-08-06, критерии с оракулами прописаны - **Зачем:** недоступный 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. ## Воспроизведение 1. Остановить qBittorrent (локально, не на umbar). 2. Смотреть лог воркера в течение нескольких минут поллинга. 3. Наблюдается: запрос к qBittorrent уходит каждые 5 секунд всё время недоступности, а доменная запись `poll failed` идёт WARN на каждом тике и остаётся WARN бесконечно. 4. Ожидается: интервал опроса растёт до минутного потолка, а после N подряд-идущих неудачных тиков уровень поднимается до ERROR — деградация отличается от разового промаха. 5. Поднять 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`).