github.com/metacubex/quic-go@v0.44.1-0.20240520163451-20b689a59136/internal/flowcontrol/base_flow_controller.go (about) 1 package flowcontrol 2 3 import ( 4 "sync" 5 "time" 6 7 "github.com/metacubex/quic-go/internal/protocol" 8 "github.com/metacubex/quic-go/internal/utils" 9 ) 10 11 type baseFlowController struct { 12 // for sending data 13 bytesSent protocol.ByteCount 14 sendWindow protocol.ByteCount 15 lastBlockedAt protocol.ByteCount 16 17 // for receiving data 18 //nolint:structcheck // The mutex is used both by the stream and the connection flow controller 19 mutex sync.Mutex 20 bytesRead protocol.ByteCount 21 highestReceived protocol.ByteCount 22 receiveWindow protocol.ByteCount 23 receiveWindowSize protocol.ByteCount 24 maxReceiveWindowSize protocol.ByteCount 25 26 allowWindowIncrease func(size protocol.ByteCount) bool 27 28 epochStartTime time.Time 29 epochStartOffset protocol.ByteCount 30 rttStats *utils.RTTStats 31 32 logger utils.Logger 33 } 34 35 // IsNewlyBlocked says if it is newly blocked by flow control. 36 // For every offset, it only returns true once. 37 // If it is blocked, the offset is returned. 38 func (c *baseFlowController) IsNewlyBlocked() (bool, protocol.ByteCount) { 39 if c.sendWindowSize() != 0 || c.sendWindow == c.lastBlockedAt { 40 return false, 0 41 } 42 c.lastBlockedAt = c.sendWindow 43 return true, c.sendWindow 44 } 45 46 func (c *baseFlowController) AddBytesSent(n protocol.ByteCount) { 47 c.bytesSent += n 48 } 49 50 // UpdateSendWindow is called after receiving a MAX_{STREAM_}DATA frame. 51 func (c *baseFlowController) UpdateSendWindow(offset protocol.ByteCount) (updated bool) { 52 if offset > c.sendWindow { 53 c.sendWindow = offset 54 return true 55 } 56 return false 57 } 58 59 func (c *baseFlowController) sendWindowSize() protocol.ByteCount { 60 // this only happens during connection establishment, when data is sent before we receive the peer's transport parameters 61 if c.bytesSent > c.sendWindow { 62 return 0 63 } 64 return c.sendWindow - c.bytesSent 65 } 66 67 // needs to be called with locked mutex 68 func (c *baseFlowController) addBytesRead(n protocol.ByteCount) { 69 // pretend we sent a WindowUpdate when reading the first byte 70 // this way auto-tuning of the window size already works for the first WindowUpdate 71 if c.bytesRead == 0 { 72 c.startNewAutoTuningEpoch(time.Now()) 73 } 74 c.bytesRead += n 75 } 76 77 func (c *baseFlowController) hasWindowUpdate() bool { 78 bytesRemaining := c.receiveWindow - c.bytesRead 79 // update the window when more than the threshold was consumed 80 return bytesRemaining <= protocol.ByteCount(float64(c.receiveWindowSize)*(1-protocol.WindowUpdateThreshold)) 81 } 82 83 // getWindowUpdate updates the receive window, if necessary 84 // it returns the new offset 85 func (c *baseFlowController) getWindowUpdate() protocol.ByteCount { 86 if !c.hasWindowUpdate() { 87 return 0 88 } 89 90 c.maybeAdjustWindowSize() 91 c.receiveWindow = c.bytesRead + c.receiveWindowSize 92 return c.receiveWindow 93 } 94 95 // maybeAdjustWindowSize increases the receiveWindowSize if we're sending updates too often. 96 // For details about auto-tuning, see https://docs.google.com/document/d/1SExkMmGiz8VYzV3s9E35JQlJ73vhzCekKkDi85F1qCE/edit?usp=sharing. 97 func (c *baseFlowController) maybeAdjustWindowSize() { 98 bytesReadInEpoch := c.bytesRead - c.epochStartOffset 99 // don't do anything if less than half the window has been consumed 100 if bytesReadInEpoch <= c.receiveWindowSize/2 { 101 return 102 } 103 rtt := c.rttStats.SmoothedRTT() 104 if rtt == 0 { 105 return 106 } 107 108 fraction := float64(bytesReadInEpoch) / float64(c.receiveWindowSize) 109 now := time.Now() 110 if now.Sub(c.epochStartTime) < time.Duration(4*fraction*float64(rtt)) { 111 // window is consumed too fast, try to increase the window size 112 newSize := utils.Min(2*c.receiveWindowSize, c.maxReceiveWindowSize) 113 if newSize > c.receiveWindowSize && (c.allowWindowIncrease == nil || c.allowWindowIncrease(newSize-c.receiveWindowSize)) { 114 c.receiveWindowSize = newSize 115 } 116 } 117 c.startNewAutoTuningEpoch(now) 118 } 119 120 func (c *baseFlowController) startNewAutoTuningEpoch(now time.Time) { 121 c.epochStartTime = now 122 c.epochStartOffset = c.bytesRead 123 } 124 125 func (c *baseFlowController) checkFlowControlViolation() bool { 126 return c.highestReceived > c.receiveWindow 127 }