Долгий metaDL больше не убивается агрессивным таймаутом: дефолт magnet_timeout 30m → 24h (страховочный предохранитель), базис отсчёта — added_on из qBittorrent, а не created_at (переживает retry/усыновление). Авто-восстановление: фоновая сверка возвращает в поток задачи, упавшие по нашей нетерпеливости (magnet_timeout/stalled), когда источник ожил и продвинулся за условие падения (downloading/completed по статусу торрента); qbit_error не воскрешается. Конфликт idempotency (infohash занят другой активной задачей) — оставляем в failed. Уведомления: любой переход в failed/stuck пингует автора (включая приёмный qbit_add через ingest), с дебаунсом против спама при флаппинге stalled. Ручной retry добавлен в веб-UI и Telegram; Retry перецепляется к живому торренту вместо слепого Add. Дельта state-reconciliation влита в живые спеки; обновлён workflow.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.9 KiB
Why
Загрузка magnet'ом ушла в терминальный failed/magnet_timeout по
wall-clock таймауту (возраст от created_at, ~1ч), хотя qBittorrent просто
долго тянул метаданные (медленные трекеры / мало пиров). Метаданные в итоге
пришли, торрент жив и качается, но задача застряла в терминальном состоянии
без выхода — восстановить её нельзя. Вдобавок падение происходит молча (нет
уведомления автору), а единственный путь возврата Worker.Retry баговый
(не сбрасывает базис времени → задача мгновенно снова падает) и доступен
только через REST, но не из веб-UI и Telegram.
What Changes
- Терпеливость к
metaDL. Перестаём убивать долгий magnet агрессивным таймаутом. Дефолт[worker].magnet_timeoutподнимается до24h— это редкий страховочный предохранитель, а не рабочий механизм. Настоящие провалы определяются по статусам ошибок qBittorrent (error/missingFiles→qbit_error), а не по wall-clock. (У qBittorrent нет статуса «magnet мёртв» — зависший magnet вечно висит вmetaDL, поэтому единственный сигнал на этот кейс — большой страховочный таймаут.) - Базис таймаута — от факта, а не от
created_at. Возраст дляmagnet_timeout/stalledсчитаем от времени добавления торрента в qBittorrent (added_on), а не от создания записи. Это чинит неверный отсчёт для усыновлённых раздач и устраняет мгновенное повторное падение после возврата вdownloading. - Уведомление о любом падении. Переход в
failed(любойerror_code) иstuckуведомляет автора загрузки черезnotifier(раньше уведомления слались только дляreview/done/orphaned/target_missing). - Авто-восстановление из
failed/stuck. Фоновая сверка (state-reconciliation) замечает, что у задачи в восстановимомfailed/stuck(наша нетерпеливость:magnet_timeout,stalled) источник в qBittorrent жив и продвинулся, и возвращает задачу в поток (downloadingлибоcompletedпо статусу торрента). Пользовательские и реальные провалы (qbit_error,reverted,cancelled) сверка не воскрешает. - Ручной retry из UI и Telegram. Кнопка повторной попытки добавляется в
веб-UI и Telegram-бот (раньше — только Cancel; retry был только в REST).
Worker.Retryчинится: перецепляется к уже живому торренту вместо слепого повторногоAdd, базис таймаута сбрасывается.
Capabilities
New Capabilities
Нет. Уведомления о падении и семантика таймаута относятся к жизненному циклу
загрузки, который пока живёт в docs/specs/workflow.md (ещё не мигрирован в
OpenSpec); заводить отдельную capability notifications сейчас —
преждевременное дробление.
Modified Capabilities
state-reconciliation: восстановимыеfailed/stuck(magnet_timeout,stalled) перестают быть «неприкосновенными» для сверки и подлежат авто-восстановлению при живом продвинувшемся источнике; добавляется требование о восстановлении и о доступности ручного retry.qbit_error,reverted,cancelled,deletedостаются вне восстановления.
Impact
- Спеки: дельта
state-reconciliation; обновление графа переходов и семантики таймаута/уведомлений вdocs/specs/workflow.md(источник истины по жизненному циклу до миграции). - Конфиг: дефолт
[worker].magnet_timeout→24h(internal/config/config.go),config.example.toml. - Код:
internal/worker/worker.go—transition(уведомление о failed/stuck),checkTimeouts(базис отadded_on),reconcile/reconcileDesync(воскрешение из failed/stuck),Retry(перецепление + сброс базиса); новыйEventпадения и его обработка вnotifier/tgbot/httpapi; кнопка retry вinternal/httpapiиinternal/tgbot; вынос строки"magnet_timeout"(и смежныхerror_code) в именованные константы рядом с состояниями. - qBittorrent-клиент: возможно потребуется поле
added_onвqbt.Torrent(если ещё не читается). - Миграции БД: не ожидаются (восстановление опирается на состояние qBit и существующие поля задачи).