drpcstream: gate data-frame sends on the per-stream send window#71
Open
suj-krishnan wants to merge 2 commits into
Open
drpcstream: gate data-frame sends on the per-stream send window#71suj-krishnan wants to merge 2 commits into
suj-krishnan wants to merge 2 commits into
Conversation
suj-krishnan
force-pushed
the
sujatha/flow-control-send-window
branch
from
July 13, 2026 07:00
50c697f to
a4ad9e8
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
from
July 13, 2026 07:00
58a5fed to
30ffb52
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-window
branch
from
July 13, 2026 07:06
a4ad9e8 to
6ff7baf
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
from
July 13, 2026 07:06
30ffb52 to
9b6d87b
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-window
branch
from
July 13, 2026 08:41
6ff7baf to
7fb0331
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
from
July 13, 2026 08:41
9b6d87b to
2512021
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-window
branch
from
July 13, 2026 08:47
7fb0331 to
9c3bc23
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
from
July 13, 2026 08:47
2512021 to
c086e23
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-window
branch
from
July 13, 2026 08:55
9c3bc23 to
efc77b4
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
from
July 13, 2026 08:55
c086e23 to
b14cb1f
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-window
branch
from
July 13, 2026 08:59
efc77b4 to
d88576b
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
from
July 13, 2026 08:59
b14cb1f to
6594c14
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-window
branch
7 times, most recently
from
July 13, 2026 12:29
b1f5883 to
4d08d0b
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
4 times, most recently
from
July 14, 2026 08:46
fe9d197 to
228866a
Compare
suj-krishnan
marked this pull request as ready for review
July 14, 2026 09:51
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
from
July 14, 2026 10:00
228866a to
de7b9dc
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
2 times, most recently
from
July 14, 2026 10:25
fcfa2e4 to
10b863f
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-window
branch
from
July 15, 2026 11:03
4d08d0b to
deefd7b
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
2 times, most recently
from
July 16, 2026 09:08
7d03098 to
336c61c
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-window
branch
from
July 16, 2026 09:08
deefd7b to
af4331b
Compare
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
from
July 16, 2026 10:16
336c61c to
ef342ce
Compare
…or/CloseSend Close, SendError, and CloseSend used to hold s.mu while waiting for the write lock. The writer holding that lock can be blocked for a long time (transport backpressure today; send credit once flow control lands), and holding s.mu while waiting wedges every path that needs it -- Cancel, terminate, and the connection reader delivering inbound terminal frames. A mixed order is also an ABBA deadlock waiting to happen between any two of these paths. All three now release s.mu, wait for the write lock, then re-lock and re-check stream state before proceeding (returning nil if another path terminated meanwhile, matching their existing no-op contracts). The stream's lock order is uniform: s.write before s.mu; never wait on s.write while holding s.mu. The regression test parks a send on a full write buffer, queues CloseSend behind it, and verifies Cancel still unwedges everything -- with the old ordering, Cancel blocks on s.mu held by CloseSend and the parked send is never freed. Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
Wire the sendWindow credit gate into rawWriteLocked: a KindMessage frame now acquires len(frame) bytes of per-stream send credit before it is handed to the writer. Control frames (invoke/metadata) bypass the gate, and write stats are recorded only after credit is acquired, next to the WriteFrame they describe. The window is opt-in: a stream has no send window by default, so data writes stay ungated (unlimited) and behavior is unchanged until one is installed. terminate closes the window with the send-side error (sigs.send is first-wins, holding io.EOF when a cancel/error path pre-set it), so a send parked on credit returns the same error as one parked in WriteFrame or a later send. A send parked on credit is freed only by terminate (grants, ctx cancel, or window close), so Close and SendError close the send window before touching the write lock, mirroring SendCancel's signal-first ordering; otherwise they would stall behind the parked send waiting on a grant a slow consumer may never send. CloseSend deliberately does not preempt: it is a graceful half-close, waiting (without s.mu, per the previous commit) for a parked send to complete once credit arrives. Per-stream only; the connection-level window and the enablement path that installs the window come in later commits. Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
suj-krishnan
force-pushed
the
sujatha/flow-control-send-gate
branch
from
July 17, 2026 05:38
ef342ce to
0d5cf31
Compare
shubhamdhama
left a comment
There was a problem hiding this comment.
These are the comments for the first commit.
| defer s.checkFinished() | ||
|
|
||
| // Wait for the write lock without holding s.mu (see Close). | ||
| // Wait for the write lock without holding s.mu, matching CloseSend's |
There was a problem hiding this comment.
This comment is related to the other PR.
Comment on lines
550
to
+562
| s.mu.Lock() | ||
| if s.sigs.term.IsSet() { | ||
| s.mu.Unlock() | ||
| return nil | ||
| } | ||
|
|
||
| // Close the send window before taking the write lock so a send parked on | ||
| // credit wakes and releases the lock (same ordering as Close). io.EOF to | ||
| // match the sigs.send error set below, so parked and later sends agree. | ||
| if s.sendw != nil { | ||
| s.sendw.close(io.EOF) | ||
| } | ||
| s.mu.Unlock() |
There was a problem hiding this comment.
This doesn't need to be wrapped in s.mu locks. Both are concurrency safe.
Comment on lines
623
to
+635
| s.mu.Lock() | ||
| if s.sigs.term.IsSet() { | ||
| s.mu.Unlock() | ||
| return nil | ||
| } | ||
|
|
||
| // Close the send window before taking the write lock so a send parked on | ||
| // credit wakes and releases the lock, instead of stalling the close on a | ||
| // grant that may never come (mirrors SendCancel's signal-first ordering). | ||
| if s.sendw != nil { | ||
| s.sendw.close(termClosed) | ||
| } | ||
| s.mu.Unlock() |
There was a problem hiding this comment.
Same here: This doesn't need to be wrapped in s.mu locks. Both are concurrency safe.
| s.mu.Unlock() | ||
| return nil | ||
| } | ||
| s.mu.Unlock() |
There was a problem hiding this comment.
This one too: This doesn't need to be wrapped in s.mu locks. This is concurrency safe.
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.
Two commits:
1.
drpcstream: take the write lock without holding s.mu— a standalone locking change:Close/SendError/CloseSendreleases.mu, wait fors.write, then re-lock and re-check state. Holdings.muwhile waiting deadlocks every path that needs it (Cancel, terminate, the connection reader) behind a slow writer, and a mixed order is an ABBA deadlock. Uniform rule:s.writebefores.mu; never wait ons.writeholdings.mu. Regression test uses transport backpressure (no flow control involved).2.
drpcstream: gate data-frame sends on the per-stream send window— wires thesendWindowcredit gate into the stream write path:rawWriteLocked, aKindMessageframe acquireslen(frame)bytes of per-stream send credit before it is handed to the writer.terminatecloses the window with the send-side error (sigs.send.Err()— first-wins, soio.EOFwhen a cancel/error path pre-set it): a send parked on credit returns the same error as one parked inMuxWriter.WriteFrameor a later send.SendError's pre-close usesio.EOFandClose's usestermClosedfor the same parked/unparked agreement.CloseandSendErrorclose the send window before touching the write lock (signal-first, likeSendCancel), so a credit-parked send wakes and releases the lock instead of stalling them on a grant that may never come.Preemption matrix for the four callers that take the write lock:
SendCancelCloseSendErrorCloseSend