- storage-without-pocketbase закрыта как реализованная: приёмка сошлась по всем
пяти критериям записи и по двенадцати приёмочным свойствам рубрики ревью
дизайна, работа лежит коммитом c9b7765
- урожай триажа ревью развёрнут в одиннадцать записей с тегом партии
review-2026-08-23 и расставлен по зависимости, а не в конец списка
- находка про признак живости воркера слита в stalled-pipeline-metric: у неё та
же причина — вставший конвейер неотличим от простоя
4.6 KiB
🐞 Не закрывать базу, пока живёт брошенный воркер
- Тип: fix
- Категория: Очередь — Остановка чинится раньше бюджета остановки: пока база закрывается под живым воркером, замерять мягкий бюджет нечем — процесс падает паникой, а захват стоит до восьми часов
- Зачем: По истечении жёсткого таймаута остановки процесс возвращается из run(), отложенный db.Close() обнуляет пулы, брошенный воркер разыменует nil и роняет процесс паникой; захват записи после этого стоит до восьми часов, и следа не остаётся.
- Теги: review-2026-08-23
Остановка сервиса не дожидается брошенных горутин. По истечении
ForceShutdownTimeout run() возвращается, отложенный db.Close() обнуляет
пулы соединений, а воркер, который не успел закончить шаг, разыменует nil и
роняет процесс паникой.
Замер прогона триажа: Close() вернулся за 2.8 мкс, пока другая горутина
держала пишущую транзакцию, и та зафиксировала её уже после закрытия.
Цена не в самой панике, а в следе: захваченная запись остаётся захваченной до истечения срока захвата — до восьми часов, — и о причине в журнале нет ничего. Инвариант «принятая запись не теряется молча» держится тем, что отказ виден владельцу записи; здесь не виден никому.
Нашли проходы review-code (C2) и review-ops (O3) ревью change
2026-08-23-storage-without-pocketbase.
Воспроизведение
- Занять шаг конвейера работой дольше
[server] force_shutdown_timeout. - Послать процессу
SIGTERM. - По истечении жёсткого таймаута процесс возвращается из
run()и закрывает базу; брошенный воркер обращается к закрытому пулу и роняет процесс паникой. - Запись остаётся с признаком захвата, а в журнале об этом ни строки.
Затрагивает
- порядок остановки в
cmd/transcriber/main.go— возврат изrun()и отложенное закрытие базы; internal/adapter/repo/sqlite/db.go—Close()и жизненный цикл обоих пулов;- цикл воркера
internal/controller/worker— что делает шаг, у которого база закрылась под руками; - ключи конфига
[server] shutdown_timeoutиforce_shutdown_timeout; - поведение при остановке в спеке
pipeline.
Критерии приёмки
- Жёсткая остановка не роняет процесс паникой. Оракул: тест — шаг спит
дольше жёсткого таймаута, процесс останавливается,
recoverв тесте не срабатывает, код возврата нулевой. - Обращение к закрытой базе отвечает отказом, а не паникой. Оракул: тест
репозитория — вызов после
Close()возвращает ошибку, и она отличима от «записи нет». - О брошенной работе остаётся след. Оракул: тест с перехваченным журналом — жёсткая остановка при незакрытом шаге пишет строку с идентификатором записи и причиной.
Рамки
Бюджет мягкой остановки этой задачей не замеряется — это задача
context-cancel-in-pipeline; здесь закрывается только путь после жёсткого
таймаута. Сроки захвата и рубежи не двигаются.