учёт: закрыт переезд хранилища, заведён урожай его ревью
- storage-without-pocketbase закрыта как реализованная: приёмка сошлась по всем
пяти критериям записи и по двенадцати приёмочным свойствам рубрики ревью
дизайна, работа лежит коммитом c9b7765
- урожай триажа ревью развёрнут в одиннадцать записей с тегом партии
review-2026-08-23 и расставлен по зависимости, а не в конец списка
- находка про признак живости воркера слита в stalled-pipeline-metric: у неё та
же причина — вставший конвейер неотличим от простоя
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# 🐞 Не закрывать базу, пока живёт брошенный воркер
|
||||
|
||||
- **Тип:** 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`.
|
||||
|
||||
## Воспроизведение
|
||||
|
||||
1. Занять шаг конвейера работой дольше `[server] force_shutdown_timeout`.
|
||||
2. Послать процессу `SIGTERM`.
|
||||
3. По истечении жёсткого таймаута процесс возвращается из `run()` и закрывает
|
||||
базу; брошенный воркер обращается к закрытому пулу и роняет процесс паникой.
|
||||
4. Запись остаётся с признаком захвата, а в журнале об этом ни строки.
|
||||
|
||||
## Затрагивает
|
||||
|
||||
- порядок остановки в `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`; здесь закрывается только путь после жёсткого
|
||||
таймаута. Сроки захвата и рубежи не двигаются.
|
||||
Reference in New Issue
Block a user