- четыре новые записи: проверить шаг migrations так же, как шаг сверки версий Go; свести шесть расхождений между документами канона; запретить обращаться к Bot API мимо клиента бота; разведка о шагах гейта, теряющих предмет - context-cancel-in-pipeline приведена к правде: дописан перечень сделанного попутно, критерий с оракулом «тест на трёх прерываниях подряд» разбит надвое — проверена была только его узкая половина
6.5 KiB
🧹 Прервать шаг конвейера отменой контекста
- Тип: 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 секунд может оказаться меньше неотменяемого минимума — тогда число меняется, и это решение человека.