- четыре новые записи: проверить шаг migrations так же, как шаг сверки версий Go; свести шесть расхождений между документами канона; запретить обращаться к Bot API мимо клиента бота; разведка о шагах гейта, теряющих предмет - context-cancel-in-pipeline приведена к правде: дописан перечень сделанного попутно, критерий с оракулом «тест на трёх прерываниях подряд» разбит надвое — проверена была только его узкая половина
73 lines
6.5 KiB
Markdown
73 lines
6.5 KiB
Markdown
# 🧹 Прервать шаг конвейера отменой контекста
|
|
|
|
- **Тип:** chore
|
|
- **Категория:** Очередь — Отмена контекста трогает те же шаги, что и таймауты: одна окрестность кода, два захода дороже одного.
|
|
- **Зачем:** Половина сделана 2026-08-13 — контекст доходит до внешних вызовов, а прерванный шаг оставляет задачу на повтор и не тратит попытку, — но осталось то, ради чего задача заводилась: хранилище контекста не принимает ни одним методом, и бюджет мягкой остановки не замерен.
|
|
|
|
Долг назван в `docs/architecture.md` — раздел про capability `pipeline`, где
|
|
отмена контекста посреди шага перечислена среди неописанного. Ревью задачи
|
|
`pocketbase-storage` 2026-08-12 его уточнило, и уточнение меняет способ починки:
|
|
**передать `ctx` по всему коду недостаточно**. Хранилище не принимает контекста ни в
|
|
одном из методов записи — ни в сохранении записи, ни в транзакции, ни в чтении по
|
|
идентификатору; отменять там нечем без перехода части записи на сырые запросы,
|
|
у которых контекст есть.
|
|
|
|
Число снято на прогоне: под внешней эксклюзивной блокировкой файла базы захват
|
|
задачи держится 9,53 секунды (таймаут занятости хранилища — 10 секунд) и
|
|
возвращает отличимый отказ, а не «работы нет».
|
|
|
|
**Сделано попутно 2026-08-13** (коммит `f494dcb`, работа о линтерах и контексте
|
|
— задачей не бралась, поэтому карточка и осталась открытой):
|
|
|
|
- контекст проложен от воркера и обоих входов до внешних вызовов: `ffmpeg` и
|
|
`ffprobe` заводятся через `exec.CommandContext`, SpeechKit и Object Storage
|
|
принимают его доводом, скачивание записи идёт запросом с контекстом;
|
|
- прерванный шаг приговора не выносит: задача остаётся пригодной к повтору,
|
|
попытку не тратит и отправителю о несуществующем сбое не сообщает. Отмена
|
|
различается по `ctx.Err()`, а не по ошибке: убитый процесс отдаёт
|
|
«signal: killed», и `errors.Is` его с отменой не свяжет;
|
|
- воркер не считает остановку отказом и не пишет о ней владельцу; задача не
|
|
забирается вовсе, если нас уже остановили;
|
|
- принятие операции SpeechKit от отмены защищено своим пределом: потерянный
|
|
идентификатор заставил бы повтор оплатить ту же запись второй раз;
|
|
- проверки: `internal/service/shutdown_test.go`,
|
|
`TestShutdownIsNotAFailure` в `internal/controller/worker/worker_test.go`.
|
|
|
|
Из пяти критериев приёмки закрыты два — «оборванный шаг оставляет задачу
|
|
пригодной к повтору» и «прерванный шаг не тратит попытку». Открыты три: бюджет
|
|
остановки не замерен (хранилище контекста не принимает, и обрывать в нём нечего),
|
|
три отмены подряд ни один тест не гоняет, спека `pipeline` отмену не описывает.
|
|
|
|
## Затрагивает
|
|
|
|
- цикл воркера `internal/controller/worker`;
|
|
- сигнатуры шагов конвейера в `internal/service`;
|
|
- договор ядра с хранилищем `internal/contract` и его реализация в
|
|
`internal/adapter/repo/pocketbase`;
|
|
- таймауты мягкой и жёсткой остановки — конфиг, `[server] shutdown_timeout` и
|
|
`force_shutdown_timeout`;
|
|
- спека `pipeline`: сегодня раздел `Purpose` объявляет отмену контекста
|
|
неописанной.
|
|
|
|
## Критерии приёмки
|
|
|
|
- Шаг, идущий дольше мягкого таймаута, обрывается по сигналу остановки. Оракул —
|
|
тест с шагом, спящим дольше таймаута: остановка укладывается в бюджет, а не
|
|
доживает до жёсткой.
|
|
- Оборванный шаг оставляет задачу пригодной к повтору. Оракул — тест
|
|
`TestShutdownDuringConversionKeepsJobRetryable`: после остановки задача не
|
|
захвачена, стоит в прежнем состоянии и выдаётся следующему прогону.
|
|
- Прерванный шаг не тратит попытку. Оракул — тот же тест: счётчик попыток задачи
|
|
после отмены равен тому, что был до захвата.
|
|
- Три отмены подряд не уводят задачу в «мертва». Оракул — тест на трёх
|
|
прерываниях подряд: счётчик попыток не растёт до предела, состояние задачи не
|
|
меняется.
|
|
- Спека `pipeline` описывает отмену, и раздел `Purpose` её больше не числит
|
|
неописанной. Оракул — `openspec validate --strict`.
|
|
|
|
## Рамки
|
|
|
|
Таймаут занятости хранилища этой задачей не трогаем: он свойство библиотеки, а не
|
|
наше. Мягкая остановка в 5 секунд может оказаться меньше неотменяемого минимума —
|
|
тогда число меняется, и это решение человека.
|