reliably queue MAX_DATA frames (#4844)

This commit is contained in:
Marten Seemann
2025-01-08 09:53:23 +08:00
committed by GitHub
parent 076db77a26
commit 5a6187c870
12 changed files with 154 additions and 52 deletions

View File

@@ -57,10 +57,12 @@ func (c *connectionFlowController) IncrementHighestReceived(increment protocol.B
return nil
}
func (c *connectionFlowController) AddBytesRead(n protocol.ByteCount) {
func (c *connectionFlowController) AddBytesRead(n protocol.ByteCount) (hasWindowUpdate bool) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.baseFlowController.addBytesRead(n)
c.mutex.Unlock()
return c.baseFlowController.hasWindowUpdate()
}
func (c *connectionFlowController) GetWindowUpdate(now time.Time) protocol.ByteCount {

View File

@@ -19,8 +19,9 @@ func TestConnectionFlowControlWindowUpdate(t *testing.T) {
&utils.RTTStats{},
utils.DefaultLogger,
)
require.False(t, fc.AddBytesRead(1))
require.Zero(t, fc.GetWindowUpdate(time.Now()))
fc.AddBytesRead(100)
require.True(t, fc.AddBytesRead(99))
require.Equal(t, protocol.ByteCount(200), fc.GetWindowUpdate(time.Now()))
}

View File

@@ -18,7 +18,7 @@ type flowController interface {
// A StreamFlowController is a flow controller for a QUIC stream.
type StreamFlowController interface {
flowController
AddBytesRead(protocol.ByteCount) (shouldQueueWindowUpdate bool)
AddBytesRead(protocol.ByteCount) (hasStreamWindowUpdate, hasConnWindowUpdate bool)
// UpdateHighestReceived is called when a new highest offset is received
// final has to be to true if this is the final offset of the stream,
// as contained in a STREAM frame with FIN bit, and the RESET_STREAM frame
@@ -32,7 +32,7 @@ type StreamFlowController interface {
// The ConnectionFlowController is the flow controller for the connection.
type ConnectionFlowController interface {
flowController
AddBytesRead(protocol.ByteCount)
AddBytesRead(protocol.ByteCount) (hasWindowUpdate bool)
Reset() error
IsNewlyBlocked() (bool, protocol.ByteCount)
}

View File

@@ -98,12 +98,12 @@ func (c *streamFlowController) UpdateHighestReceived(offset protocol.ByteCount,
return c.connection.IncrementHighestReceived(increment, now)
}
func (c *streamFlowController) AddBytesRead(n protocol.ByteCount) (shouldQueueWindowUpdate bool) {
func (c *streamFlowController) AddBytesRead(n protocol.ByteCount) (hasStreamWindowUpdate, hasConnWindowUpdate bool) {
c.mutex.Lock()
c.baseFlowController.addBytesRead(n)
shouldQueueWindowUpdate = c.shouldQueueWindowUpdate()
hasStreamWindowUpdate = c.shouldQueueWindowUpdate()
c.mutex.Unlock()
c.connection.AddBytesRead(n)
hasConnWindowUpdate = c.connection.AddBytesRead(n)
return
}

View File

@@ -192,16 +192,20 @@ func TestStreamWindowUpdate(t *testing.T) {
utils.DefaultLogger,
)
require.Zero(t, fc.GetWindowUpdate(time.Now()))
fc.AddBytesRead(24)
hasStreamWindowUpdate, _ := fc.AddBytesRead(24)
require.False(t, hasStreamWindowUpdate)
require.Zero(t, fc.GetWindowUpdate(time.Now()))
// the window is updated when it's 25% filled
fc.AddBytesRead(1)
hasStreamWindowUpdate, _ = fc.AddBytesRead(1)
require.True(t, hasStreamWindowUpdate)
require.Equal(t, protocol.ByteCount(125), fc.GetWindowUpdate(time.Now()))
fc.AddBytesRead(24)
hasStreamWindowUpdate, _ = fc.AddBytesRead(24)
require.False(t, hasStreamWindowUpdate)
require.Zero(t, fc.GetWindowUpdate(time.Now()))
// the window is updated when it's 25% filled
fc.AddBytesRead(1)
hasStreamWindowUpdate, _ = fc.AddBytesRead(1)
require.True(t, hasStreamWindowUpdate)
require.Equal(t, protocol.ByteCount(150), fc.GetWindowUpdate(time.Now()))
// Receive the final offset.
@@ -211,6 +215,31 @@ func TestStreamWindowUpdate(t *testing.T) {
require.Zero(t, fc.GetWindowUpdate(time.Now()))
}
func TestStreamConnectionWindowUpdate(t *testing.T) {
connFC := NewConnectionFlowController(
100,
protocol.MaxByteCount,
nil,
&utils.RTTStats{},
utils.DefaultLogger,
)
fc := NewStreamFlowController(
42,
connFC,
1000,
protocol.MaxByteCount,
protocol.MaxByteCount,
&utils.RTTStats{},
utils.DefaultLogger,
)
hasStreamWindowUpdate, hasConnWindowUpdate := fc.AddBytesRead(50)
require.False(t, hasStreamWindowUpdate)
require.Zero(t, fc.GetWindowUpdate(time.Now()))
require.True(t, hasConnWindowUpdate)
require.NotZero(t, connFC.GetWindowUpdate(time.Now()))
}
func TestStreamWindowAutoTuning(t *testing.T) {
// the RTT is 1 second
rttStats := &utils.RTTStats{}

View File

@@ -42,9 +42,11 @@ func (m *MockConnectionFlowController) EXPECT() *MockConnectionFlowControllerMoc
}
// AddBytesRead mocks base method.
func (m *MockConnectionFlowController) AddBytesRead(arg0 protocol.ByteCount) {
func (m *MockConnectionFlowController) AddBytesRead(arg0 protocol.ByteCount) bool {
m.ctrl.T.Helper()
m.ctrl.Call(m, "AddBytesRead", arg0)
ret := m.ctrl.Call(m, "AddBytesRead", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// AddBytesRead indicates an expected call of AddBytesRead.
@@ -60,19 +62,19 @@ type MockConnectionFlowControllerAddBytesReadCall struct {
}
// Return rewrite *gomock.Call.Return
func (c *MockConnectionFlowControllerAddBytesReadCall) Return() *MockConnectionFlowControllerAddBytesReadCall {
c.Call = c.Call.Return()
func (c *MockConnectionFlowControllerAddBytesReadCall) Return(hasWindowUpdate bool) *MockConnectionFlowControllerAddBytesReadCall {
c.Call = c.Call.Return(hasWindowUpdate)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockConnectionFlowControllerAddBytesReadCall) Do(f func(protocol.ByteCount)) *MockConnectionFlowControllerAddBytesReadCall {
func (c *MockConnectionFlowControllerAddBytesReadCall) Do(f func(protocol.ByteCount) bool) *MockConnectionFlowControllerAddBytesReadCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockConnectionFlowControllerAddBytesReadCall) DoAndReturn(f func(protocol.ByteCount)) *MockConnectionFlowControllerAddBytesReadCall {
func (c *MockConnectionFlowControllerAddBytesReadCall) DoAndReturn(f func(protocol.ByteCount) bool) *MockConnectionFlowControllerAddBytesReadCall {
c.Call = c.Call.DoAndReturn(f)
return c
}

View File

@@ -78,11 +78,12 @@ func (c *MockStreamFlowControllerAbandonCall) DoAndReturn(f func()) *MockStreamF
}
// AddBytesRead mocks base method.
func (m *MockStreamFlowController) AddBytesRead(arg0 protocol.ByteCount) bool {
func (m *MockStreamFlowController) AddBytesRead(arg0 protocol.ByteCount) (bool, bool) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AddBytesRead", arg0)
ret0, _ := ret[0].(bool)
return ret0
ret1, _ := ret[1].(bool)
return ret0, ret1
}
// AddBytesRead indicates an expected call of AddBytesRead.
@@ -98,19 +99,19 @@ type MockStreamFlowControllerAddBytesReadCall struct {
}
// Return rewrite *gomock.Call.Return
func (c *MockStreamFlowControllerAddBytesReadCall) Return(shouldQueueWindowUpdate bool) *MockStreamFlowControllerAddBytesReadCall {
c.Call = c.Call.Return(shouldQueueWindowUpdate)
func (c *MockStreamFlowControllerAddBytesReadCall) Return(hasStreamWindowUpdate, hasConnWindowUpdate bool) *MockStreamFlowControllerAddBytesReadCall {
c.Call = c.Call.Return(hasStreamWindowUpdate, hasConnWindowUpdate)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockStreamFlowControllerAddBytesReadCall) Do(f func(protocol.ByteCount) bool) *MockStreamFlowControllerAddBytesReadCall {
func (c *MockStreamFlowControllerAddBytesReadCall) Do(f func(protocol.ByteCount) (bool, bool)) *MockStreamFlowControllerAddBytesReadCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockStreamFlowControllerAddBytesReadCall) DoAndReturn(f func(protocol.ByteCount) bool) *MockStreamFlowControllerAddBytesReadCall {
func (c *MockStreamFlowControllerAddBytesReadCall) DoAndReturn(f func(protocol.ByteCount) (bool, bool)) *MockStreamFlowControllerAddBytesReadCall {
c.Call = c.Call.DoAndReturn(f)
return c
}