Skip to content

Commit f436d48

Browse files
committed
drpcmanager: test that context cancellation wakes a parked sender
Add an integration test for the production wake path of a sender parked on flow-control credit. A flow-control-enabled client sends a message larger than its window to a server that has no flow control (so it never returns credit); the send parks, and cancelling the RPC context wakes it. This exercises manageStream -> Cancel -> terminate -> sendWindow.close end to end, which the unit tests previously covered only in pieces (the primitive's ctx path, and stream-level Cancel). Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com> drpcmanager: end-to-end test for flow-control enablement The manager already threads drpcstream.Options through newStream, so enabling FlowControl in Options.Stream installs windows on every stream the manager creates (client and server) with no additional wiring. Add an end-to-end test over a real net.Pipe connection between two
1 parent 6fc345c commit f436d48

1 file changed

Lines changed: 61 additions & 0 deletions

File tree

drpcmanager/flow_control_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net"
1111
"sync/atomic"
1212
"testing"
13+
"time"
1314

1415
"github.com/zeebo/assert"
1516

@@ -80,6 +81,66 @@ func TestManager_FlowControlEndToEnd(t *testing.T) {
8081
ctx.Wait()
8182
}
8283

84+
// Cancelling an RPC's context wakes a sender parked on flow-control credit. The
85+
// client has flow control with a small window; the server does not, so it never
86+
// returns credit and the client's oversized send parks. This exercises the
87+
// production wake path: manageStream sees the cancellation and calls
88+
// Cancel -> terminate -> sendWindow.close.
89+
func TestManager_FlowControlContextCancelWakesParkedSend(t *testing.T) {
90+
tr := drpctest.NewTracker(t)
91+
defer tr.Close()
92+
93+
cconn, sconn := net.Pipe()
94+
defer func() { _ = cconn.Close() }()
95+
defer func() { _ = sconn.Close() }()
96+
97+
cman := NewWithOptions(cconn, Client, fcManagerOptions(128<<10, 1<<20, 64<<10))
98+
defer func() { _ = cman.Close() }()
99+
// Server has no flow control, so it never returns credit.
100+
sman := New(sconn, Server)
101+
defer func() { _ = sman.Close() }()
102+
103+
// Accept the stream and drain; the message never completes (the sender
104+
// parks mid-message), so RawRecv just blocks until teardown.
105+
tr.Run(func(ctx context.Context) {
106+
stream, _, err := sman.NewServerStream(ctx)
107+
if err != nil {
108+
return
109+
}
110+
defer func() { _ = stream.Close() }()
111+
for {
112+
if _, err := stream.RawRecv(); err != nil {
113+
return
114+
}
115+
}
116+
})
117+
118+
streamCtx, cancel := context.WithCancel(context.Background())
119+
stream, err := cman.NewClientStream(streamCtx, "rpc", drpc.CompressionNone)
120+
assert.NoError(t, err)
121+
assert.NoError(t, stream.RawWrite(drpcwire.KindInvoke, []byte("rpc")))
122+
123+
// A 512 KiB message exceeds the 128 KiB window; once the initial credit is
124+
// spent the send parks waiting for grants that never come.
125+
done := make(chan error, 1)
126+
go func() { done <- stream.RawWrite(drpcwire.KindMessage, make([]byte, 512<<10)) }()
127+
128+
select {
129+
case <-done:
130+
t.Fatal("send returned before it could park on credit")
131+
case <-time.After(50 * time.Millisecond):
132+
}
133+
134+
cancel()
135+
136+
select {
137+
case err := <-done:
138+
assert.Error(t, err)
139+
case <-time.After(time.Second):
140+
t.Fatal("parked send did not wake on context cancellation")
141+
}
142+
}
143+
83144
// sniffConn tees every byte read off the connection into w, where a frame
84145
// parser classifies it.
85146
type sniffConn struct {

0 commit comments

Comments
 (0)