Files
jellybit/tasks/items/delete-holds-worker-lock.md
T

4.5 KiB

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