Skip to content

fixed blocked write racing with close, reset or abort of the output stream - #103

Merged
ptrd merged 1 commit into
ptrd:write-after-close-and-abort-interruptfrom
WowzaBarry:fix/write-after-close-and-abort-interrupt
Nov 7, 2026
Merged

ptrd merged 1 commit into
ptrd:write-after-close-and-abort-interruptfrom
WowzaBarry:fix/write-after-close-and-abort-interrupt

Conversation

@WowzaBarry

Copy link
Copy Markdown
Contributor

A write that was waiting for send buffer space was not woken up by close(). When the sender drained the buffer, the write completed and appended its data after the end of stream marker. If the frame ending at the marker was sent as the final frame, finalFrameSent stopped flow control for the stream, and sending the leftover data threw a NullPointerException in FlowControl.getFlowControlLimit on the sender thread, aborting the whole connection. Otherwise the marker was taken as stream data and the FIN was never sent. We observed the NPE from a server application that closes a stream while another thread is blocked writing to it.

abort() and reset() could also miss a writer that had passed the stream state check but had not yet registered itself as the blocked writer. After an abort, such a writer waited forever; after a reset, its write completed even though the stream was reset.

Fix: SendBuffer.close() marks the buffer closed under the buffer lock and wakes a waiting writer, which then fails with an IOException ("output stream already closed"). Data is always added under the buffer lock, so it can never end up after the marker. interruptBlockedWriter() now also sets a flag that a writer checks after registering itself and before adding data, so a reset or abort that races the registration still fails the write. restart() (used when 0-RTT data is rejected) reopens the buffer.

Tests: QuicStreamImplTest covers a writer blocked on a full buffer while the stream is closed (this test reproduces the NPE before the fix), and abort or reset arriving while the writer is between the state check and its registration (forced by holding the buffer lock). SendBufferTest covers close while waiting, write after close, and an interrupt before the writer starts waiting. EarlyDataStreamTest covers rejected 0-RTT data that included the FIN.

Not covered: a write racing resetReliable() can still land after the reliable reset completes, because reset is set before the writer is interrupted; that is a separate ordering change.

…tream

A write waiting for send buffer space was not woken up by close(). When the
sender drained the buffer, the write completed and appended its data after
the end of stream marker. If the frame ending at the marker went out as the
final frame, flow control was stopped for the stream, and sending the data
that followed threw a NullPointerException in
FlowControl.getFlowControlLimit on the sender thread, which aborted the
connection; otherwise the marker was taken as data and the FIN was never
sent. Now close() marks the send buffer closed and wakes up the waiting
writer, which fails with an IOException. Data is added to the buffer under
the buffer lock, so it can never end up after the marker.

A writer that had passed the stream state check but had not yet registered
itself as blocked writer was missed by abort() and reset(): after an abort
it waited forever, after a reset its write completed. The send buffer now
remembers that writers are interrupted and checks this after registering,
so such a write fails as well.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ptrd
ptrd changed the base branch from master to write-after-close-and-abort-interrupt November 7, 2026 09:50
@ptrd
ptrd merged commit 3ba7865 into ptrd:write-after-close-and-abort-interrupt Nov 7, 2026
@ptrd

ptrd commented Nov 7, 2026

Copy link
Copy Markdown
Owner

Good catch. Thanks for the nice PR and explanations.

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.

2 participants