Repository navigation
fix: drain a GOAWAY with a streamed body in flight too - #962
Merged
benoitc merged 1 commit intoOct 3, 2026
Merged
Conversation
The drain so far closed everything at once when a streamed request or
response body was in flight. A streamed response needs nothing new: its
parked caller is answered like any other. A streamed request body the GOAWAY
refuses has nobody parked on it, since its caller is between send_body_chunk,
finish_send_body and start_response calls, so it stays tracked as refused,
the connection leaves streaming_body, and the caller's next call is answered
with {error, {goaway, _}}. An accepted one carries on as before and the
connection closes when its response has been read.
The frame-level server of the GOAWAY tests moves to hackney_h2_goaway_server,
with request bodies added, so both test modules share it.
Owner
|
Thanks @lennartschoch, merged. A small follow-up will tidy the refused-upload handling and the h2 send-error path. |
This was referenced Oct 3, 2026
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.
Follow-up to #961. That PR made hackney honour a GOAWAY's
last_stream_id: streams the server accepted are left to finish, only the ones above it fail with{goaway, _}. It left one case alone, where the connection still closed everything at once: a GOAWAY arriving while a streamed request or response body is in flight. This covers that case.hackney has two ways to stream a body over HTTP/2, and they need different handling.
Streaming the response is
hackney:connect/4followed byhackney:send_request/2, and then reading the body in pieces withstream_body/1. The caller sits insidesend_requestuntil the status and headers arrive. That's the same shape as a plain request, so nothing new was needed: with the guard gone, the stream either completes normally or, if the GOAWAY refused it, the parked caller gets{error, {goaway, _}}right away.Streaming the request body is
hackney:request/5withstreamin place of a body, thensend_body/2for each chunk,finish_send_body/1, andstart_response/1for the reply. Between those calls the caller is off doing something else, typically producing the next chunk. If the GOAWAY refuses that stream in one of those gaps, there is nobody waiting inside hackney to hand the error to. Dropping the stream quietly would make the caller's next call misbehave:send_bodywould push bytes onto a stream the server has already forgotten (h2still acceptssend_dataafter a GOAWAY, so the call would succeed), andstart_responsewould come back as{error, no_stream}, which reads like a bug in the caller.So a refused request stream stays in the stream table, marked
refused, and the connection moves fromstreaming_bodyback toconnected, since there is no body being sent any more. Whichever ofsend_body,finish_send_bodyorstart_responsethe caller makes next is answered with{error, {goaway, ErrorCode}}, and that ends the stream. If the caller dies first, the stream's existing monitor ends it instead. Once it was the last stream on the connection, the connection closes, the same way the rest of the drain does. A request stream the GOAWAY accepted needs nothing: the remaining chunks go out, the response comes back, and the connection closes after it has been read.Worth flagging: a caller that stays alive but never makes another call keeps the drained connection around until the server closes its end. That is also what happens today with an abandoned streamed request, so it isn't new, but it is the one place this PR waits on the caller.
rebar3 eunitis green, 1176 tests, xref and dialyzer clean. The six new ones intest/hackney_http2_goaway_streaming_tests.erlall fail without the change:send_body, onfinish_send_body, and onstart_response, while the plain request beside it completes;The frame-level server from #961's tests now lives in
test/hackney_h2_goaway_server.erl, with request bodies added, so both GOAWAY test modules use the same one. The drain tests are unchanged apart from calling it.