Handle client-cancelled shape requests as disconnects instead of 500s - #4791
Merged
Conversation
Client-cancelled live long-polls were the single dominant source of error spans on /v1/shape: when a client resets the HTTP/2 stream mid-poll, Bandit only surfaces the reset lazily — as a Bandit.TransportError raised from send_resp once the long-poll timeout fires — and the generic exception path then mapped it to a 500 nobody ever received, while the handler process and its admission permit stayed occupied for the full timeout after the client had already gone. Two complementary changes: - Abort the long poll early: the wait-for-changes receive now also matches Bandit's rst_stream notification message, so a cancelled request completes (status 499, empty body, listener cleanup) the moment the client resets the stream, releasing the process and admission permit immediately. - Classify send-time transport failures: a Bandit.TransportError with error: :closed caught around the plug pipeline is now accounted for as a client disconnect — status 499 and a `shape_req.client_disconnected` span attribute, with no recorded exception and no unreadable error body — and is reraised for Bandit to close the stream quietly, instead of being reported as a 500 with a recorded exception. Fixes #4790 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C4Hiytggpfq8UqvFnshh5r
❌ 1 Tests Failed:
View the top 2 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
Contributor
|
This PR has been released! 🚀 The following packages include changes from this PR:
Thanks for contributing to Electric! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4790
Two complementary changes:
Abort the long poll the moment the cancel arrives.
wait_for_live_change/2now also matches Bandit's{:bandit, {:rst_stream, _}}notification and completes the request immediately — status 499, empty body, change-listener cleanup — releasing the process and admission permit at cancel time. The message format is Bandit-private (Bandit.HTTP2.Stream.deliver_rst_stream/2); the receive clause carries a comment pinning that. HTTP/1 has no equivalent early signal and is covered by the second change only.Classify send-time transport failures as client disconnects. A
%Bandit.TransportError{error: :closed}caught around the plug pipeline is now accounted for as status 499 (nginx's "client closed request" convention) with ashape_req.client_disconnectedspan attribute — norecord_exception, no unreadable error body — and is reraised for Bandit to close the stream through its native quiet path (log_client_closures: falseby default). This mirrors how the chunked streaming path already treats{:error, "closed"}as a quiet halt.The
[:electric, :plug, :serve_shape]telemetry event still fires for these requests (with status 499), so cancellation volume stays measurable while dropping out of the 5xx error signal.🤖 Generated with Claude Code
https://claude.ai/code/session_01C4Hiytggpfq8UqvFnshh5r