Skip to content

fix: a caller's QUIC half-close keeps the provider's reply (Refs #145) - #146

Merged
rgfaber merged 1 commit into
mainfrom
ceres/145-half-close
Oct 28, 2026
Merged

rgfaber merged 1 commit into
mainfrom
ceres/145-half-close

Conversation

@rgfaber

@rgfaber rgfaber commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

What was wrong

macula_station_link:quic_event/2 answered 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 same disconnected error 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

  • Erlang, without the fix: a_callers_quic_half_close_after_its_stream_end_keeps_the_reply fails with no write at all, and a_session_that_had_the_peers_last_frame_writes_on_after_its_quic_fin fails with {error, send_closed}. With it, both pass and the two stream modules are 52/52.
  • Cross-SDK, both profiles: in macula-go at titan/43-revendor, MACULA_A0=1 MACULA_LOCAL_ERL=1 scripts/interop/sealed.sh pq_pure (and pq_hybrid) prints a0 clear client stream: replayed "c1,c2,c3", <nil> against this branch and fails the copy was not replayed in order / the stream was lost before its end: EOF against f1aea93b.

Refs #145

🤖 Generated with Claude Code

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>
@rgfaber
rgfaber merged commit ce067c4 into main Oct 28, 2026
28 checks passed
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