Отмена доходит до внешнего собеседника, а токен не покидает единой точки
- контекст проложен от воркера и обоих входов до внешних вызовов: ffmpeg и ffprobe заводятся через exec.CommandContext, SpeechKit и Object Storage принимают ctx вместо context.Background, скачивание записи идёт запросом с контекстом. Прежде остановка сервиса не доходила до чужой работы вовсе - прерванный шаг приговора не выносит: убитый по контексту ffmpeg отдаёт «signal: killed», от настоящего отказа неотличимо ни типом, ни errors.Is, и различает их только ctx.Err(). Задача остаётся на повтор, попытку не тратит и отправителю о несуществующем сбое не сообщает; воркер не считает остановку отказом, а задача не забирается вовсе, если нас уже остановили - клиента Bot API заводит единая точка internal/adapter/telegram: токен стоит в пути каждого обращения, а http.Client кладёт адрес в *url.Error целиком. Чистка на месте употребления закрывала один вызов из пяти — теперь свой Do чистит отказ, подменённый логгер вычищает токен из строк самой библиотеки, а транспорт бота токена не получает вовсе - принятие операции распознавания защищено от отмены своим пределом: SpeechKit мог её принять и начать считать деньги, а потерянный идентификатор заставил бы повтор оплатить ту же запись второй раз - приём по HTTP доводит запись до задачи независимо от отправителя: на контексте запроса один обрыв соединения терял полностью загруженную запись - ответ Telegram с не-2xx кодом больше не становится записью: прежде тело отказа доезжало до хранилища и умирало на ffprobe, уводя диагностику
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package yandex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Принятие операции распознавания защищено от отмены: остановка сервиса не
|
||||
// должна обрывать вызов, который уже мог начать стоить денег и чей результат
|
||||
// нельзя переспросить. Проверяется само средство защиты — проводка к нему
|
||||
// оракула не имеет: клиент SpeechKit подставить нечем, а прогон на реальных
|
||||
// ключах запрещён (CLAUDE.md, «Запреты»).
|
||||
func TestProtectedContextSurvivesParentCancel(t *testing.T) {
|
||||
parent, cancel := context.WithCancel(t.Context())
|
||||
|
||||
protected, release := protectFromCancel(parent, time.Minute)
|
||||
defer release()
|
||||
|
||||
cancel()
|
||||
|
||||
require.Error(t, parent.Err(), "родитель отменён — иначе проверка судит не то")
|
||||
assert.NoError(t, protected.Err(), "защищённый вызов пережил отмену родителя")
|
||||
}
|
||||
|
||||
// Защита не бессрочна: у вызова свой предел, иначе остановка ждала бы вечно.
|
||||
func TestProtectedContextKeepsItsOwnDeadline(t *testing.T) {
|
||||
protected, release := protectFromCancel(t.Context(), time.Minute)
|
||||
defer release()
|
||||
|
||||
deadline, ok := protected.Deadline()
|
||||
|
||||
require.True(t, ok, "у защищённого вызова обязан быть свой предел")
|
||||
assert.WithinDuration(t, time.Now().Add(time.Minute), deadline, 5*time.Second)
|
||||
}
|
||||
Reference in New Issue
Block a user