Приём отвечает 200 до свёртки, свёртку ведёт фоновый воркер
- Очередью служит сама таблица: доставка ждёт свёртки в статусе `pending`, канал несёт только бит «есть работа». Переполнять нечего, падение процесса очередь не теряет, а подбор `pending` при старте — обычный проход воркера, а не отдельный код. Классификация исхода общая с пересборкой журнала. - Исход разбора начал отражать доставку, а не обстоятельства: отмена и занятость базы статус не меняют (иначе конкуренция за базу выводила бы доставку из очереди навсегда), паника свёртки больше не валит процесс, а учёт доставки идёт через транзакцию с повторами. - Длинный бюджет ответа выдан маршруту приёма, а не всему серверу: `write_timeout` в Go покрывает и чтение тела, и общий подъём снял бы защиту с остальных маршрутов.
This commit is contained in:
+43
-4
@@ -90,13 +90,33 @@ type Stats struct {
|
||||
UncoveredDropped int
|
||||
}
|
||||
|
||||
// ErrPanicked — свёртка паниковала. Доставка получает `failed`: тело в архиве, и
|
||||
// пересборка вернёт её, когда дефект будет исправлен.
|
||||
var ErrPanicked = errors.New("свёртка паниковала")
|
||||
|
||||
// Fold разбирает тело доставки и раскладывает точки по часовым объектам.
|
||||
//
|
||||
// Это единственный логирующий чекпоинт свёртки: транспорт и приём исход
|
||||
// разбора не логируют. Значения точек и имена устройств в лог не попадают —
|
||||
// данные о здоровье чувствительнее токенов.
|
||||
func (s *Service) Fold(ctx context.Context, deliveryID string) (Stats, error) {
|
||||
var stats Stats
|
||||
//
|
||||
// Паника перехватывается ЗДЕСЬ, у той же границы, что пишет исход разбора.
|
||||
// Пока свёртка шла внутри HTTP-обработчика, панику ловил middleware.Recoverer и
|
||||
// она стоила одного ответа; из фоновой горутины она валит процесс целиком, а
|
||||
// `restart: unless-stopped` поднимает его снова — и первый же проход берёт ту
|
||||
// же доставку, то есть дефект превращается в цикл перезапуска, при котором
|
||||
// приём не работает вовсе. Перехват у этой границы, а не у вызывающего,
|
||||
// оставляет писателя `parse_status` единственным.
|
||||
func (s *Service) Fold(ctx context.Context, deliveryID string) (stats Stats, err error) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
stats = Stats{}
|
||||
err = fmt.Errorf("%w: %v", ErrPanicked, r) //nolint:errorlint // причину раскрываем текстом, sentinel — для ветвления
|
||||
s.fail(ctx, deliveryID, err, nil)
|
||||
}()
|
||||
|
||||
d, err := s.store.DeliveryForParse(ctx, deliveryID)
|
||||
if err != nil {
|
||||
@@ -282,9 +302,28 @@ func (s *Service) finish(ctx context.Context, deliveryID string, out store.Parse
|
||||
return nil
|
||||
}
|
||||
|
||||
// fail отмечает доставку неразобранной. Тело остаётся в архиве, и её подберёт
|
||||
// пересборка — приём при этом не затрагивается: сохранили значит приняли.
|
||||
// fail записывает исход неудачной свёртки.
|
||||
//
|
||||
// Исход отражает ДОСТАВКУ, а не обстоятельства. Отмена снаружи и занятость базы
|
||||
// работой доставки не являются: они означают «не сделано», а не «не выходит».
|
||||
// Статус в этих случаях не трогается вовсе — доставка остаётся `pending` и
|
||||
// подбирается следующим проходом. Иначе конкуренция за базу (после разнесения
|
||||
// ответа и свёртки она штатная) выводила бы доставку из очереди навсегда:
|
||||
// `failed` возвращает только пересборка, то есть ручная операция с остановкой
|
||||
// сервиса.
|
||||
//
|
||||
// Всё прочее — непонятое содержимое, невыводимый слой, нечитаемое или слишком
|
||||
// большое тело, исчерпанный дедлайн — свойства самой доставки, и повторять их
|
||||
// бесполезно: статус `failed`, тело ждёт пересборки. Приём при этом не
|
||||
// затрагивается: сохранили значит приняли.
|
||||
func (s *Service) fail(ctx context.Context, deliveryID string, cause error, uncovered []string) {
|
||||
if store.Transient(cause) {
|
||||
// WARN, а не ERROR: пройдёт само, разбирать нечего. Строка нужна, чтобы
|
||||
// повтор не выглядел беспричинным.
|
||||
s.log.WarnContext(ctx, "delivery fold deferred", "error", cause, "delivery_id", deliveryID)
|
||||
return
|
||||
}
|
||||
|
||||
level := slog.LevelError
|
||||
switch {
|
||||
case errors.Is(cause, hae.ErrLayerUnknown):
|
||||
|
||||
Reference in New Issue
Block a user