馃寪 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

fix: don't warn on routine client-disconnect write/flush errors - #2589

Merged
dunglas merged 4 commits into
mainfrom
issue-2588
Aug 4, 2026
Merged

fix: don't warn on routine client-disconnect write/flush errors#2589
dunglas merged 4 commits into
mainfrom
issue-2588

Conversation

@dunglas

@dunglas dunglas commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

While investigating #2588, found two related logging bugs around client disconnects.

go_sapi_flush logged every ResponseController.Flush() error under the same message:

the current responseWriter is not a flusher, if you are not using a custom build, please report this issue

That message is only accurate for http.ErrNotSupported. A plain disconnect/write error (e.g. an aborted HTTP/2 stream) got mislabeled the same way, misleading and able to mask the real cause when triaging issues shaped like #2588.

Also reconsidered the log level. go_ub_write's equivalent "write error" was logged at warn, so the new "flush error" branch was first matched to it. But a client disconnecting mid-response is routine, and both writes and flushes happen in a loop, so a single request can log this dozens or hundreds of times, one per write/flush call after the disconnect. Not warn-worthy.

Checked how the two other Caddy-family projects here handle this same class of error:

  • Caddy's reverse proxy streaming (streaming.go): "backConn write failed", "response flush", "streaming error" - all logged at debug.
  • Mercure's SSE hub (subscribe.go): "Failed to write comment" (per-write, in a loop) and "Connection closed by the client" are debug; only one-off lifecycle events like "Subscriber disconnected" are info.

Matched that convention:

  • go_ub_write's "write error" -> debug (was warn).
  • go_sapi_flush's "flush error" -> debug (routine disconnect), correctly separated from "not a flusher".
  • go_sapi_flush's "not a flusher" -> stays warn (real misconfiguration, not a per-request occurrence).

This only fixes logging, not the segfault. I was not able to reproduce the crash reported in #2588 (see comment on the issue).

Test plan

  • go build ./...
  • go vet ./...
  • go test -run "TestConnectionAbort|TestLog" ./...

go_sapi_flush logged every ResponseController.Flush() error with the
same "not a flusher" message, even when the writer clearly supports
Flush but the call failed for another reason (e.g. a client
disconnect). Only use that message for http.ErrNotSupported and log
the real error otherwise.
dunglas added 3 commits August 4, 2026 21:31
A client disconnecting mid-flush is routine and not actionable, unlike
the "not a flusher" misconfiguration case.
Same event as go_ub_write's "write error" (client disconnect), so log
it at the same level for consistency. Flatten the nested if/else into
early returns.
Caddy (streaming.go: "backConn write failed", "streaming error") and
Mercure (subscribe.go: "Failed to write comment", "Connection closed
by the client") both log repeated per-write streaming failures at
debug, reserving warn for one-off actionable problems. Match that:
a client disconnecting mid-response is routine and can repeat on
every write of a request, so it doesn't belong at warn. Only the
"not a flusher" misconfiguration case stays at warn.

Also avoid computing the message/level before knowing whether the
logger is even enabled for it.
@dunglas dunglas changed the title fix: don't mislabel flush errors as "not a flusher" fix: don't warn on routine client-disconnect write/flush errors Aug 4, 2026
@dunglas
dunglas marked this pull request as ready for review August 4, 2026 19:39
@dunglas
dunglas merged commit afb5e84 into main Aug 4, 2026
32 checks passed
@dunglas
dunglas deleted the issue-2588 branch August 4, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant