Files
transcriber/tasks/items/context-cancel-in-pipeline.md
T
av eacaf76d5f tasks: заведён остаток работы о гейте, контексте и токене
- четыре новые записи: проверить шаг migrations так же, как шаг сверки версий Go;
  свести шесть расхождений между документами канона; запретить обращаться к Bot
  API мимо клиента бота; разведка о шагах гейта, теряющих предмет
- context-cancel-in-pipeline приведена к правде: дописан перечень сделанного
  попутно, критерий с оракулом «тест на трёх прерываниях подряд» разбит надвое —
  проверена была только его узкая половина
2026-08-13 10:55:38 +03:00

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 секунд может оказаться меньше неотменяемого минимума — тогда число меняется, и это решение человека.