Приём отвечает 200 до свёртки, свёртку ведёт фоновый воркер
- Очередью служит сама таблица: доставка ждёт свёртки в статусе `pending`, канал несёт только бит «есть работа». Переполнять нечего, падение процесса очередь не теряет, а подбор `pending` при старте — обычный проход воркера, а не отдельный код. Классификация исхода общая с пересборкой журнала. - Исход разбора начал отражать доставку, а не обстоятельства: отмена и занятость базы статус не меняют (иначе конкуренция за базу выводила бы доставку из очереди навсегда), паника свёртки больше не валит процесс, а учёт доставки идёт через транзакцию с повторами. - Длинный бюджет ответа выдан маршруту приёма, а не всему серверу: `write_timeout` в Go покрывает и чтение тела, и общий подъём снял бы защиту с остальных маршрутов.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.vakhrushev.me/av/healthlog/internal/ingest"
|
||||
)
|
||||
@@ -22,6 +23,8 @@ type ingestResponse struct {
|
||||
// Код ответа отражает ДОСТАВКУ, а не разбор: 200 означает «тело сохранено в
|
||||
// архив», и этого достаточно, потому что разобрать сохранённое можно всегда.
|
||||
func (a *api) handleIngest(w http.ResponseWriter, r *http.Request) {
|
||||
a.extendWriteDeadline(w, r)
|
||||
|
||||
body, err := readBody(w, r, a.maxBody)
|
||||
if err != nil {
|
||||
writeReadError(w, err)
|
||||
@@ -46,6 +49,31 @@ func (a *api) handleIngest(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// extendWriteDeadline даёт маршруту приёма собственный бюджет ответа.
|
||||
//
|
||||
// `WriteTimeout` сервера ставится в `readRequest`, то есть ДО вызова
|
||||
// обработчика, и потому покрывает не только запись ответа, но и чтение тела:
|
||||
// при `read_timeout` в пять минут и `write_timeout` в тридцать секунд загрузка
|
||||
// длиннее тридцати секунд обрывается, а `read_timeout` при этом обещает пять
|
||||
// минут. Обрывается молча — обработчик ошибки записи не видит, а `accessLog`
|
||||
// пишет `status_code=200`.
|
||||
//
|
||||
// Лечится это здесь, а не подъёмом общего таймаута: длинный бюджет нужен
|
||||
// одному маршруту, и подъём снял бы защиту от застрявшей записи со всех
|
||||
// остальных.
|
||||
//
|
||||
// Транспорт, не умеющий дедлайнов, отказом приёма не является: приём —
|
||||
// единственное место, где поток вообще существует, и терять доставку из-за
|
||||
// неподдержанной оптимизации нельзя.
|
||||
func (a *api) extendWriteDeadline(w http.ResponseWriter, r *http.Request) {
|
||||
if a.ingestBudget <= 0 {
|
||||
return
|
||||
}
|
||||
if err := http.NewResponseController(w).SetWriteDeadline(time.Now().Add(a.ingestBudget)); err != nil {
|
||||
a.log.DebugContext(r.Context(), "write deadline not set", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// metaFromHeaders достаёт то, что автоматизация Health Auto Export
|
||||
// рассказывает о себе своими заголовками. Именованные поля — те, по которым
|
||||
// ходят запросы; полный набор кладётся рядом, потому что документация HAE
|
||||
|
||||
Reference in New Issue
Block a user