Skip to content

drpcstream: mutex-free sendWindow flow-control primitive#84

Open
suj-krishnan wants to merge 2 commits into
mainfrom
sujatha/flow-control-send-window-chan
Open

drpcstream: mutex-free sendWindow flow-control primitive#84
suj-krishnan wants to merge 2 commits into
mainfrom
sujatha/flow-control-send-window-chan

Conversation

@suj-krishnan

@suj-krishnan suj-krishnan commented Jul 23, 2026

Copy link
Copy Markdown

Reworks the per-stream send-side flow-control primitive (sendWindow) into a lock-free credit balance, and adds the write-path liveness check that lets it stay simple.

sendWindow becomes a lock-free credit balance: an atomic balance, a size 1 buffered channel for grant wakeups, and a caller-supplied done channel for termination. There is at most one acquirer (serialized by the stream write lock) and one granter (the connection reader).

Termination is delegated to the done channel — the stream's send signal — so the window holds no error or closed state. terminate no longer calls into the window; setting sigs.send both wakes a blocked acquire and supplies its error via sigs.send.Err().

WriteFrame previously consulted its cancel signal only when parking on a full buffer; on the common non-blocking path it appended unconditionally. It now checks cancel.IsSet() before appending, so a data frame whose stream has
terminated is dropped at the write layer.IsSet is a plain atomic read, so the fast path stays allocation-free; terminal frames pass a nil cancel and are unaffected.

suj-krishnan and others added 2 commits July 23, 2026 14:52
Rework sendWindow into a lock-free credit balance in the style of
grpc-go's writeQuota: an atomic balance, a cap-1 buffered channel for
grant wakeups, and a caller-supplied done channel for termination. There
is at most one acquirer (serialized by the stream write lock) and one
granter (the connection reader), so no mutex is needed.

acquire debits credit atomically and, if that leaves a deficit, waits on
the channel; grant repays the balance and edge-signals the channel only
when the deficit clears. A cap-1 non-blocking send makes the wakeup
lost-wakeup-free without a condition variable.

Termination is delegated to the done channel -- the stream's send signal
-- so the window holds no error or closed state. terminate no longer
calls into the window; setting sigs.send both wakes a blocked acquire and
supplies its error (sigs.send.Err()), which matches the error a send
parked in WriteFrame or a later send reads.

acquire is a pure credit gate: with credit available it returns without
consulting done, so a send racing termination can be admitted. That is
harmless -- rawWriteLocked holds the stream write lock, so the frame is
ordered before the terminal frame, and the write path drops it when the
send signal is already set (companion change). Only a blocked acquire
observes done, which is what wakes a parked sender on termination.

Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
WriteFrame consulted its cancel signal only when it had to park on a full
buffer; on the common non-blocking path (buffer has room) it appended
unconditionally. A data frame whose send was canceled or the stream
terminated -- sigs.send set after the write path's own check but before
WriteFrame -- would therefore still reach the wire.

Check cancel at the top of the loop, before appending. IsSet is an
atomic read and does not force the signal's lazy channel allocation, so
the fast path stays allocation-free. Terminal frames pass a nil cancel
and are never affected.

This lets the send window stay a pure credit gate: acquire need not be
absorbing on its fast path, because the write path now drops a data
frame whose stream has terminated -- the drpc analogue of grpc-go's
loopyWriter checking live stream membership before writing. It also
closes the pre-existing race (independent of flow control) where a data
frame could be appended just after the send signal was set.

Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
@suj-krishnan
suj-krishnan marked this pull request as ready for review July 23, 2026 10:28
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