- Очередью служит сама таблица: доставка ждёт свёртки в статусе `pending`, канал несёт только бит «есть работа». Переполнять нечего, падение процесса очередь не теряет, а подбор `pending` при старте — обычный проход воркера, а не отдельный код. Классификация исхода общая с пересборкой журнала. - Исход разбора начал отражать доставку, а не обстоятельства: отмена и занятость базы статус не меняют (иначе конкуренция за базу выводила бы доставку из очереди навсегда), паника свёртки больше не валит процесс, а учёт доставки идёт через транзакцию с повторами. - Длинный бюджет ответа выдан маршруту приёма, а не всему серверу: `write_timeout` в Go покрывает и чтение тела, и общий подъём снял бы защиту с остальных маршрутов.
41 lines
2.5 KiB
Go
41 lines
2.5 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
)
|
|
|
|
// ErrNotFound — записи нет. Граничную ошибку драйвера (sql.ErrNoRows)
|
|
// транслируем в доменную здесь же, у источника, чтобы выше по коду не торчал
|
|
// database/sql.
|
|
var ErrNotFound = errors.New("запись не найдена")
|
|
|
|
// ErrBusy — база занята, и повторы транзакции этого не пересидели.
|
|
//
|
|
// Доменная ошибка, а не код драйвера: на неё ветвится свёртка. Отказ по
|
|
// занятости не является свойством доставки — работа просто не сделана, и
|
|
// доставка обязана остаться в очереди. Без этого различения конкуренция за
|
|
// базу выводила бы доставку из очереди навсегда.
|
|
var ErrBusy = errors.New("база занята")
|
|
|
|
// Transient отвечает, вызван ли отказ ОБСТОЯТЕЛЬСТВАМИ, а не данными.
|
|
//
|
|
// Ровно два случая: работу прекратили снаружи и база оказалась занята дольше,
|
|
// чем длятся повторы транзакции. Оба означают «не сделано», а не «не выходит»,
|
|
// поэтому работа обязана остаться к повторению.
|
|
//
|
|
// Определение живёт здесь, в одном месте, и его читают двое: тот, кто пишет
|
|
// исход разбора доставки, и тот, кто классифицирует этот исход в счётчики. Две
|
|
// копии правила разошлись бы, и доставка одновременно осталась бы в очереди и
|
|
// числилась отказавшей.
|
|
//
|
|
// Дедлайн самой операции сюда НЕ входит: не уложившаяся в бюджет работа не
|
|
// уложится в него и в следующий раз, а бесконечный повтор заведомо
|
|
// безнадёжного — это очередь, которая не движется.
|
|
func Transient(err error) bool {
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
|
return false
|
|
}
|
|
return errors.Is(err, context.Canceled) || errors.Is(err, ErrBusy)
|
|
}
|