60 lines
4.5 KiB
Markdown
60 lines
4.5 KiB
Markdown
# 🐞 Не держать общий замок воркера во время обращения к qBittorrent при удалении
|
|
|
|
- **Тип:** fix
|
|
- **Категория:** Инфраструктура
|
|
- **Зачем:** на время удаления встаёт весь фон: опрос, сверка и команды остальных транспортов ждут замок до 30 с на загрузку, пачкой — минутами; замерено на ревью bulk-delete-page
|
|
|
|
`Worker.Delete` берёт `w.mu` и держит его через сетевой вызов `qbt.Delete`, хотя
|
|
правило пакета обратное и записано рядом: медленные вызовы идут **вне** замка
|
|
(`internal/worker/worker.go`, `Poll` зовёт `qbt.Torrents` до `Lock`). Замок при
|
|
этом один на весь воркер, а не на задачу — комментарий пакета и
|
|
`docs/architecture.md` называют его per-download, и это тоже неверно.
|
|
|
|
## Воспроизведение
|
|
|
|
1. Заставить qBittorrent отвечать на `torrents/delete` медленно (подставной
|
|
клиент с задержкой; в жизни — снос раздачи с большим числом файлов на
|
|
загруженном диске).
|
|
2. Во время вызова дёрнуть через тот же воркер любую команду по **другой**
|
|
загрузке.
|
|
|
|
Видно вместо ожидаемого: команда ждёт весь чужой сетевой вызов. Замер ревью: при
|
|
задержке `qbt.Delete` 300 мс конкурентный `Cancel` по неизвестному
|
|
идентификатору заблокирован на `280.450631ms`. Таймаут клиента qBittorrent —
|
|
зашитые 30 с (поля в `[qbittorrent]` нет), поэтому верхняя граница на одну
|
|
загрузку — 30 с, а на пачку группового удаления — минуты, в течение которых не
|
|
идут ни опрос, ни сверка, ни уведомления.
|
|
|
|
Отчёт ревью с оракулами —
|
|
`openspec/changes/archive/2026-08-10-bulk-delete-page/review/report.md`,
|
|
находка 3.
|
|
|
|
## Затрагивает
|
|
|
|
- `Worker.Delete` (`internal/worker/review.go`) — границы критической секции;
|
|
- `w.mu` и комментарий пакета `internal/worker` — «per-download» неверно;
|
|
- `docs/architecture.md`, строка «Переходы состояний» в единых точках — тот же
|
|
неверный факт;
|
|
- потолок времени на проход группового удаления (`httpapi.bulkBudget`) и
|
|
требование о нём в спеке `web-ui` — при сужении замка они теряют основание;
|
|
- поле таймаута в секции `[qbittorrent]` конфига, которого сегодня нет.
|
|
|
|
## Критерии приёмки
|
|
|
|
- Команда по одной загрузке не ждёт сетевого вызова по другой (оракул: тест
|
|
`internal/worker` с подставным клиентом, блокирующимся внутри `Delete`, —
|
|
`w.mu.TryLock()` из теста возвращает `true`).
|
|
- Состояние после сетевого вызова перечитывается, и удаление не переходит в
|
|
`deleted`, если задача успела уйти из допустимого состояния (оракул: тест на
|
|
смену состояния между шагами).
|
|
- Факт про замок исправлен в комментарии пакета и в `docs/architecture.md`
|
|
(оракул: `task gate`, шаг `canon`).
|
|
- Решено и записано, остаётся ли потолок времени на проход группового удаления
|
|
(оракул: дельта-спека и `openspec validate --strict`).
|
|
|
|
## Рамки
|
|
|
|
Трогает операцию, которая сносит файлы: разбор идемпотентности при повторе
|
|
обязателен. Уборка собственного торрента после отмены (`processCatched`) — уже
|
|
устроена нужным образом и служит образцом, менять её не надо.
|