Восстановление зависших загрузок и уведомления о падении (state-reconciliation)
Долгий 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>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
## 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 и
|
||||
существующие поля задачи).
|
||||
Reference in New Issue
Block a user