# 🐞 Не держать общий замок воркера во время обращения к 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`) — уже устроена нужным образом и служит образцом, менять её не надо.