Skip to content

Commit 02eb0f3

Browse files
committed
quic: avoid deadlock when updating inbound conn-level flow control
handleStreamBytesReadOffLoop sends a message to the conn indicating that we need to send a MAX_DATA update. Calling this with a stream's gate locked can lead to a deadlock, when the conn's loop is processing an inbound frame for the same stream: The conn can't acquire the stream's ingate, and the gate won't be unlocked until the conn processes another event from its queue. Move the handleStreamBytesReadOffLoop calls out of the gate. No test in this CL, but a following CL contains a test which reliably exercises the condition. For golang/go#58547 Change-Id: Ic98888947f67408a4a1f6f4a3aaf68c3a2fe8e7f Reviewed-on: https://go-review.googlesource.com/c/net/+/527580 Reviewed-by: Jonathan Amsterdam <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 57bce0e commit 02eb0f3

File tree

3 files changed

+11
-5
lines changed

3 files changed

+11
-5
lines changed

internal/quic/conn_flow.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ func (c *Conn) inflowInit() {
4747
//
4848
// This is called indirectly by the user, via Read or CloseRead.
4949
func (c *Conn) handleStreamBytesReadOffLoop(n int64) {
50+
if n == 0 {
51+
return
52+
}
5053
if c.shouldUpdateFlowControl(c.streams.inflow.credit.Add(n)) {
5154
// We should send a MAX_DATA update to the peer.
5255
// Record this on the Conn's main loop.

internal/quic/conn_flow_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ func TestConnInflowMultipleStreams(t *testing.T) {
180180
max: 128 + 32 + 1 + 1 + 1,
181181
})
182182

183+
tc.ignoreFrame(frameTypeStopSending)
183184
streams[2].CloseRead()
184185
tc.wantFrame("closed stream triggers another MAX_DATA update",
185186
packetType1RTT, debugFrameMaxData{

internal/quic/stream.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,11 +181,13 @@ func (s *Stream) ReadContext(ctx context.Context, b []byte) (n int, err error) {
181181
if s.IsWriteOnly() {
182182
return 0, errors.New("read from write-only stream")
183183
}
184-
// Wait until data is available.
185184
if err := s.ingate.waitAndLock(ctx, s.conn.testHooks); err != nil {
186185
return 0, err
187186
}
188-
defer s.inUnlock()
187+
defer func() {
188+
s.inUnlock()
189+
s.conn.handleStreamBytesReadOffLoop(int64(n)) // must be done with ingate unlocked
190+
}()
189191
if s.inresetcode != -1 {
190192
return 0, fmt.Errorf("stream reset by peer: %w", StreamErrorCode(s.inresetcode))
191193
}
@@ -205,7 +207,6 @@ func (s *Stream) ReadContext(ctx context.Context, b []byte) (n int, err error) {
205207
start := s.in.start
206208
end := start + int64(len(b))
207209
s.in.copy(start, b)
208-
s.conn.handleStreamBytesReadOffLoop(int64(len(b)))
209210
s.in.discardBefore(end)
210211
if s.insize == -1 || s.insize > s.inwin {
211212
if shouldUpdateFlowControl(s.inmaxbuf, s.in.start+s.inmaxbuf-s.inwin) {
@@ -334,7 +335,6 @@ func (s *Stream) CloseRead() {
334335
return
335336
}
336337
s.ingate.lock()
337-
defer s.inUnlock()
338338
if s.inset.isrange(0, s.insize) || s.inresetcode != -1 {
339339
// We've already received all data from the peer,
340340
// so there's no need to send STOP_SENDING.
@@ -343,8 +343,10 @@ func (s *Stream) CloseRead() {
343343
} else {
344344
s.inclosed.set()
345345
}
346-
s.conn.handleStreamBytesReadOffLoop(s.in.end - s.in.start)
346+
discarded := s.in.end - s.in.start
347347
s.in.discardBefore(s.in.end)
348+
s.inUnlock()
349+
s.conn.handleStreamBytesReadOffLoop(discarded) // must be done with ingate unlocked
348350
}
349351

350352
// CloseWrite aborts writes on the stream.

0 commit comments

Comments
 (0)