openlang

httptest Server.Close виснет, и тест не выдаёт сообщения об ошибке

Возможно ли, что httptest.Server.Close виснет на Go 1.23 с keep-alive? Запускаю go test -timeout 5s ./..., и новый тест ни проходит, ни падает. Он просто висит до таймаута, потом я получаю panic: test timed out after 5s и дамп горутин, а текста assertion нет нигде, в quickfix тоже.

В дампе горутина теста находится в httptest.(*Server).Close, ждёт в sync.WaitGroup.Wait, вызвана из моего отложенного srv.Close(). Горутина хендлера в chan receive, а третья в select внутри persistConn.roundTrip.

-count=1, -race и DisableKeepAlives ничего не меняют. JSON-тест в том же файле проходит.

gate := make(chan struct{})
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	<-gate // gateway holds the ack
	w.WriteHeader(http.StatusAccepted)
}))
defer srv.Close()

c := NewClient(srv.URL, log.New(io.Discard, "", 0))
errc := make(chan error, 1)
go func() { errc <- c.Send(context.Background(), testBatch()) }()

time.Sleep(100 * time.Millisecond)
if got := c.Pending(); got != 1 {
	t.Fatalf("Pending() = %d while ack is held, want 1", got)
}

close(gate)

Почему здесь блокируется Close и куда делось моё сообщение об ошибке?

Переведено с английскогоПоказать оригинал

Разговор продолжается 3

Следуя доводам
  1. Ссылка на ответ

    Close ждёт незавершённые запросы, это есть в доках: https://pkg.go.dev/net/http/httptest#Server.Close

    ваш хендлер висит на <-gate, так что keep-alive тут ни при чём. если Fatalf срабатывает, defer запускает Close раньше, чем случится close(gate). вот и виснет. сделайте defer close(gate) сразу после создания и запускайте go test -v 2>&1 | pbcopy, чтобы забрать вывод.

    Переведено с английскогоПоказать оригинал
  2. Ссылка на ответ

    А, то есть Fatalf всё-таки срабатывает, и Close потом ждёт мой собственный хендлер. Понятно, я пялился в дамп и не связал одно с другим. Спасибо.

    Про порядок: defer выполняются в порядке LIFO, так что defer close(gate), поставленный после defer srv.Close(), отработает раньше него, хорошо. Но мой тест ещё и явно вызывает close(gate), чтобы отпустить ack, а потом читает errc. Это будет двойной close и паника на успешном пути. Думаю, самый чистый вариант — обернуть close в sync.Once и вызывать в обоих местах. Или есть паттерн проще?

    Второй вопрос: пропавшее сообщение — это просто буферизация? С ./... и без -v я так понимаю, go test держит вывод до конца пакета, а паника по таймауту убивает его раньше. Может ли -v стримить строку Fatalf до зависания? Попробую. pbcopy у меня нет, так что перенаправлю в файл и открою в nvim ))

    Переведено с английскогоПоказать оригинал
  3. Ссылка на ответ

    Ваш Fatalf запускает отложенный srv.Close(), пока хендлер всё ещё висит на <-gate. Close ждёт этот хендлер, так что это почти дедлок. Отпускайте gate первым, до того как может сработать Close. Сообщение теряется, потому что паника по таймауту убивает процесс раньше, чем буферизованный вывод сбрасывается. Попробуйте -v.

    Переведено с английскогоПоказать оригинал

Другие стороны того же вопроса

  1. -WhatIf-скрипт очистки показывает 13 просроченных архивов, а у меня всего 9 файлов

    чищу старые бэкап-архивы на своём ноуте с fedora (pwsh 7.4, emacs, и да, это превращается в демо для моего класса). скрипт на…

    Ответы 2Последняя активность powershell
  2. Мой init поменял fill-column ровно в одном буфере

    (setq fill-column 72) годами лежал у меня в init, и только сейчас выяснилось, что M-q в свежем буфере по-прежнему переносит н…

    Ответы 3Последняя активность emacs-lisp
  3. SilentlyContinue как On Error Resume Next с другим написанием

    После унаследованных рабочих книг дошло дело до унаследованных скриптов PowerShell, и там нашёлся старый знакомый в новом пал…

    Ответы 0Последняя активность powershell