Отмена доходит до внешнего собеседника, а токен не покидает единой точки
- контекст проложен от воркера и обоих входов до внешних вызовов: 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:
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -39,7 +40,12 @@ type stubMetaViewer struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (m *stubMetaViewer) GetInfo(string) (*contract.AudioInfo, error) {
|
||||
func (m *stubMetaViewer) GetInfo(ctx context.Context, _ string) (*contract.AudioInfo, error) {
|
||||
// Настоящий `ffprobe` заведён с контекстом и по отмене умирает; стаб,
|
||||
// который контекст игнорирует, сделал бы проверку приёма неспособной упасть.
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if m.err != nil {
|
||||
return nil, m.err
|
||||
}
|
||||
@@ -50,7 +56,7 @@ func (m *stubMetaViewer) GetInfo(string) (*contract.AudioInfo, error) {
|
||||
// этого файла её не зовёт.
|
||||
type stubConverter struct{}
|
||||
|
||||
func (c *stubConverter) Convert(string, string) error { return nil }
|
||||
func (c *stubConverter) Convert(context.Context, string, string) error { return nil }
|
||||
|
||||
// TestTgSender: приём по HTTP в Telegram не отвечает, но сервису отправитель нужен.
|
||||
type TestTgSender struct{}
|
||||
@@ -727,3 +733,27 @@ func TestGetTranscribeJobStatus_NotFound(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "Job not found", response["error"])
|
||||
}
|
||||
|
||||
// Отправитель, у которого соединение оборвалось после полной загрузки, задачу
|
||||
// всё равно получает: запись доехала целиком, а результат он заберёт позже по
|
||||
// `GET /status/{id}`. Приём на контексте запроса терял бы такую запись молча —
|
||||
// решение владельца от 2026-08-13.
|
||||
func TestAcceptedRecordSurvivesSenderDisconnect(t *testing.T) {
|
||||
env := setupTestEnv(t, readableMetaViewer())
|
||||
|
||||
req := createMultipartRequest(t, "sample.m4a", []byte("аудио"))
|
||||
// Так выглядит ушедший отправитель: контекст запроса отменяется сервером,
|
||||
// когда соединение закрылось.
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
cancel()
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
env.serve(w, req)
|
||||
|
||||
require.Equal(t, http.StatusCreated, w.Result().StatusCode, "тело ответа: %s", w.Body.String())
|
||||
|
||||
jobs, err := env.app.FindAllRecords(migrations.JobsCollection)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, jobs, 1, "задача заведена, несмотря на ушедшего отправителя")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user