github.com/tumi8/quic-go@v0.37.4-tum/noninternal/flowcontrol/base_flow_controller.go (about) 1 package flowcontrol 2 3 import ( 4 "sync" 5 "time" 6 7 "github.com/tumi8/quic-go/noninternal/protocol" 8 "github.com/tumi8/quic-go/noninternal/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) { 52 if offset > c.sendWindow { 53 c.sendWindow = offset 54 } 55 } 56 57 func (c *baseFlowController) sendWindowSize() protocol.ByteCount { 58 // this only happens during connection establishment, when data is sent before we receive the peer's transport parameters 59 if c.bytesSent > c.sendWindow { 60 return 0 61 } 62 return c.sendWindow - c.bytesSent 63 } 64 65 // needs to be called with locked mutex 66 func (c *baseFlowController) addBytesRead(n protocol.ByteCount) { 67 // pretend we sent a WindowUpdate when reading the first byte 68 // this way auto-tuning of the window size already works for the first WindowUpdate 69 if c.bytesRead == 0 { 70 c.startNewAutoTuningEpoch(time.Now()) 71 } 72 c.bytesRead += n 73 } 74 75 func (c *baseFlowController) hasWindowUpdate() bool { 76 bytesRemaining := c.receiveWindow - c.bytesRead 77 // update the window when more than the threshold was consumed 78 return bytesRemaining <= protocol.ByteCount(float64(c.receiveWindowSize)*(1-protocol.WindowUpdateThreshold)) 79 } 80 81 // getWindowUpdate updates the receive window, if necessary 82 // it returns the new offset 83 func (c *baseFlowController) getWindowUpdate() protocol.ByteCount { 84 if !c.hasWindowUpdate() { 85 return 0 86 } 87 88 c.maybeAdjustWindowSize() 89 c.receiveWindow = c.bytesRead + c.receiveWindowSize 90 return c.receiveWindow 91 } 92 93 // maybeAdjustWindowSize increases the receiveWindowSize if we're sending updates too often. 94 // For details about auto-tuning, see https://docs.google.com/document/d/1SExkMmGiz8VYzV3s9E35JQlJ73vhzCekKkDi85F1qCE/edit?usp=sharing. 95 func (c *baseFlowController) maybeAdjustWindowSize() { 96 bytesReadInEpoch := c.bytesRead - c.epochStartOffset 97 // don't do anything if less than half the window has been consumed 98 if bytesReadInEpoch <= c.receiveWindowSize/2 { 99 return 100 } 101 rtt := c.rttStats.SmoothedRTT() 102 if rtt == 0 { 103 return 104 } 105 106 fraction := float64(bytesReadInEpoch) / float64(c.receiveWindowSize) 107 now := time.Now() 108 if now.Sub(c.epochStartTime) < time.Duration(4*fraction*float64(rtt)) { 109 // window is consumed too fast, try to increase the window size 110 newSize := utils.Min(2*c.receiveWindowSize, c.maxReceiveWindowSize) 111 if newSize > c.receiveWindowSize && (c.allowWindowIncrease == nil || c.allowWindowIncrease(newSize-c.receiveWindowSize)) { 112 c.receiveWindowSize = newSize 113 } 114 } 115 c.startNewAutoTuningEpoch(now) 116 } 117 118 func (c *baseFlowController) startNewAutoTuningEpoch(now time.Time) { 119 c.epochStartTime = now 120 c.epochStartOffset = c.bytesRead 121 } 122 123 func (c *baseFlowController) checkFlowControlViolation() bool { 124 return c.highestReceived > c.receiveWindow 125 }