Repository navigation
fix: a caller's QUIC half-close keeps the provider's reply (Refs #145) - #146
Merged
Merged
Conversation
A caller of a client_stream sends its STREAM_END and then closes its QUIC write direction -- the usual half-close, and what macula-go does. The station link read the transport's FIN (peer_send_shutdown) as the stream's death and ended every session carried on it, so the provider's STREAM_REPLY was never written: its handler was aborted first, and the routing the reply would have gone out on was already gone. The caller saw its stream end with no frame at all (found by the release A0 cross-SDK interop, DESIGN_E2E_SEALED_BY_DEFAULT section 8.1, program 3). A FIN says that no more of the peer's frames will arrive on that stream; it says nothing about a session that may still write. The link now tells each session the stream carries that the peer finished sending, and the session decides: one that has already had the peer's last frame (an end, an error, a reply) has everything it was waiting for and goes on writing, and one still waiting for that frame ends as a lost link ends it rather than waiting for ever. Tests: a served handler that answers only after it has read the caller's end still writes its STREAM_REPLY once the caller's FIN is in, and a caller session that has had the provider's last frame still sends on its stream after the FIN. Both fail without the fix. Co-Authored-By: Claude Code <noreply@anthropic.com>
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.
What was wrong
macula_station_link:quic_event/2answered a peer's{quic, peer_send_shutdown, Stream, _}by ending every session the stream carried. That event is the transport's FIN — the peer's write-side half-close — and macula-go's caller sends it straight after its STREAM_END frame. The provider's session was aborted while its handler was still about to answer, and the routing a reply needs went with it, so the STREAM_REPLY the handler set afterwards was never written. The caller saw its stream end with no frame at all (found by the release A0 cross-SDK interop,DESIGN_E2E_SEALED_BY_DEFAULT§8.1, program 3; macula#145).The fix
A FIN says only that no more of the peer's frames will arrive on that stream. It says nothing about a session that may still write.
macula_station_link: the FIN is passed to each session the stream carries ({stream_read_closed, Sid, peer_send_shutdown}) instead of ending it. A stream carrying no session still closes there, as before.macula_stream: a session that has already had the peer's last frame (an end, an error, a reply) has everything it was waiting for and goes on writing. One still waiting for that frame will never get it and ends as a lost link ends it — the samedisconnectederror as before — so the "a reader never waits for ever" guarantee the removed clause existed for still holds.stream_closed(a reset, a lost relay) keeps ending sessions at once: there nothing can be read or written.Evidence
a_callers_quic_half_close_after_its_stream_end_keeps_the_replyfails with no write at all, anda_session_that_had_the_peers_last_frame_writes_on_after_its_quic_finfails with{error, send_closed}. With it, both pass and the two stream modules are 52/52.titan/43-revendor,MACULA_A0=1 MACULA_LOCAL_ERL=1 scripts/interop/sealed.sh pq_pure(andpq_hybrid) printsa0 clear client stream: replayed "c1,c2,c3", <nil>against this branch and failsthe copy was not replayed in order/the stream was lost before its end: EOFagainstf1aea93b.Refs #145
🤖 Generated with Claude Code