github.com/metacubex/quic-go@v0.44.1-0.20240520163451-20b689a59136/internal/flowcontrol/base_flow_controller_test.go (about) 1 package flowcontrol 2 3 import ( 4 "os" 5 "strconv" 6 "time" 7 8 "github.com/metacubex/quic-go/internal/protocol" 9 "github.com/metacubex/quic-go/internal/utils" 10 11 . "github.com/onsi/ginkgo/v2" 12 . "github.com/onsi/gomega" 13 ) 14 15 // on the CIs, the timing is a lot less precise, so scale every duration by this factor 16 // 17 //nolint:unparam 18 func scaleDuration(t time.Duration) time.Duration { 19 scaleFactor := 1 20 if f, err := strconv.Atoi(os.Getenv("TIMESCALE_FACTOR")); err == nil { // parsing "" errors, so this works fine if the env is not set 21 scaleFactor = f 22 } 23 Expect(scaleFactor).ToNot(BeZero()) 24 return time.Duration(scaleFactor) * t 25 } 26 27 var _ = Describe("Base Flow controller", func() { 28 var controller *baseFlowController 29 30 BeforeEach(func() { 31 controller = &baseFlowController{} 32 controller.rttStats = &utils.RTTStats{} 33 }) 34 35 Context("send flow control", func() { 36 It("adds bytes sent", func() { 37 controller.bytesSent = 5 38 controller.AddBytesSent(6) 39 Expect(controller.bytesSent).To(Equal(protocol.ByteCount(5 + 6))) 40 }) 41 42 It("gets the size of the remaining flow control window", func() { 43 controller.bytesSent = 5 44 controller.sendWindow = 12 45 Expect(controller.sendWindowSize()).To(Equal(protocol.ByteCount(12 - 5))) 46 }) 47 48 It("updates the size of the flow control window", func() { 49 controller.AddBytesSent(5) 50 controller.UpdateSendWindow(15) 51 Expect(controller.sendWindow).To(Equal(protocol.ByteCount(15))) 52 Expect(controller.sendWindowSize()).To(Equal(protocol.ByteCount(15 - 5))) 53 }) 54 55 It("says that the window size is 0 if we sent more than we were allowed to", func() { 56 controller.AddBytesSent(15) 57 controller.UpdateSendWindow(10) 58 Expect(controller.sendWindowSize()).To(BeZero()) 59 }) 60 61 It("does not decrease the flow control window", func() { 62 Expect(controller.UpdateSendWindow(20)).To(BeTrue()) 63 Expect(controller.sendWindowSize()).To(Equal(protocol.ByteCount(20))) 64 Expect(controller.UpdateSendWindow(10)).To(BeFalse()) 65 Expect(controller.sendWindowSize()).To(Equal(protocol.ByteCount(20))) 66 }) 67 68 It("says when it's blocked", func() { 69 controller.UpdateSendWindow(100) 70 Expect(controller.IsNewlyBlocked()).To(BeFalse()) 71 controller.AddBytesSent(100) 72 blocked, offset := controller.IsNewlyBlocked() 73 Expect(blocked).To(BeTrue()) 74 Expect(offset).To(Equal(protocol.ByteCount(100))) 75 }) 76 77 It("doesn't say that it's newly blocked multiple times for the same offset", func() { 78 controller.UpdateSendWindow(100) 79 controller.AddBytesSent(100) 80 newlyBlocked, offset := controller.IsNewlyBlocked() 81 Expect(newlyBlocked).To(BeTrue()) 82 Expect(offset).To(Equal(protocol.ByteCount(100))) 83 newlyBlocked, _ = controller.IsNewlyBlocked() 84 Expect(newlyBlocked).To(BeFalse()) 85 controller.UpdateSendWindow(150) 86 controller.AddBytesSent(150) 87 newlyBlocked, _ = controller.IsNewlyBlocked() 88 Expect(newlyBlocked).To(BeTrue()) 89 }) 90 }) 91 92 Context("receive flow control", func() { 93 var ( 94 receiveWindow protocol.ByteCount = 10000 95 receiveWindowSize protocol.ByteCount = 1000 96 ) 97 98 BeforeEach(func() { 99 controller.bytesRead = receiveWindow - receiveWindowSize 100 controller.receiveWindow = receiveWindow 101 controller.receiveWindowSize = receiveWindowSize 102 }) 103 104 It("adds bytes read", func() { 105 controller.bytesRead = 5 106 controller.addBytesRead(6) 107 Expect(controller.bytesRead).To(Equal(protocol.ByteCount(5 + 6))) 108 }) 109 110 It("triggers a window update when necessary", func() { 111 bytesConsumed := float64(receiveWindowSize)*protocol.WindowUpdateThreshold + 1 // consumed 1 byte more than the threshold 112 bytesRemaining := receiveWindowSize - protocol.ByteCount(bytesConsumed) 113 readPosition := receiveWindow - bytesRemaining 114 controller.bytesRead = readPosition 115 offset := controller.getWindowUpdate() 116 Expect(offset).To(Equal(readPosition + receiveWindowSize)) 117 Expect(controller.receiveWindow).To(Equal(readPosition + receiveWindowSize)) 118 }) 119 120 It("doesn't trigger a window update when not necessary", func() { 121 bytesConsumed := float64(receiveWindowSize)*protocol.WindowUpdateThreshold - 1 // consumed 1 byte less than the threshold 122 bytesRemaining := receiveWindowSize - protocol.ByteCount(bytesConsumed) 123 readPosition := receiveWindow - bytesRemaining 124 controller.bytesRead = readPosition 125 offset := controller.getWindowUpdate() 126 Expect(offset).To(BeZero()) 127 }) 128 129 Context("receive window size auto-tuning", func() { 130 var oldWindowSize protocol.ByteCount 131 132 BeforeEach(func() { 133 oldWindowSize = controller.receiveWindowSize 134 controller.maxReceiveWindowSize = 5000 135 }) 136 137 // update the congestion such that it returns a given value for the smoothed RTT 138 setRtt := func(t time.Duration) { 139 controller.rttStats.UpdateRTT(t, 0, time.Now()) 140 Expect(controller.rttStats.SmoothedRTT()).To(Equal(t)) // make sure it worked 141 } 142 143 It("doesn't increase the window size for a new stream", func() { 144 controller.maybeAdjustWindowSize() 145 Expect(controller.receiveWindowSize).To(Equal(oldWindowSize)) 146 }) 147 148 It("doesn't increase the window size when no RTT estimate is available", func() { 149 setRtt(0) 150 controller.startNewAutoTuningEpoch(time.Now()) 151 controller.addBytesRead(400) 152 offset := controller.getWindowUpdate() 153 Expect(offset).ToNot(BeZero()) // make sure a window update is sent 154 Expect(controller.receiveWindowSize).To(Equal(oldWindowSize)) 155 }) 156 157 It("increases the window size if read so fast that the window would be consumed in less than 4 RTTs", func() { 158 bytesRead := controller.bytesRead 159 rtt := scaleDuration(50 * time.Millisecond) 160 setRtt(rtt) 161 // consume more than 2/3 of the window... 162 dataRead := receiveWindowSize*2/3 + 1 163 // ... in 4*2/3 of the RTT 164 controller.epochStartOffset = controller.bytesRead 165 controller.epochStartTime = time.Now().Add(-rtt * 4 * 2 / 3) 166 controller.addBytesRead(dataRead) 167 offset := controller.getWindowUpdate() 168 Expect(offset).ToNot(BeZero()) 169 // check that the window size was increased 170 newWindowSize := controller.receiveWindowSize 171 Expect(newWindowSize).To(Equal(2 * oldWindowSize)) 172 // check that the new window size was used to increase the offset 173 Expect(offset).To(Equal(bytesRead + dataRead + newWindowSize)) 174 }) 175 176 It("doesn't increase the window size if data is read so fast that the window would be consumed in less than 4 RTTs, but less than half the window has been read", func() { 177 bytesRead := controller.bytesRead 178 rtt := scaleDuration(20 * time.Millisecond) 179 setRtt(rtt) 180 // consume more than 2/3 of the window... 181 dataRead := receiveWindowSize*1/3 + 1 182 // ... in 4*2/3 of the RTT 183 controller.epochStartOffset = controller.bytesRead 184 controller.epochStartTime = time.Now().Add(-rtt * 4 * 1 / 3) 185 controller.addBytesRead(dataRead) 186 offset := controller.getWindowUpdate() 187 Expect(offset).ToNot(BeZero()) 188 // check that the window size was not increased 189 newWindowSize := controller.receiveWindowSize 190 Expect(newWindowSize).To(Equal(oldWindowSize)) 191 // check that the new window size was used to increase the offset 192 Expect(offset).To(Equal(bytesRead + dataRead + newWindowSize)) 193 }) 194 195 It("doesn't increase the window size if read too slowly", func() { 196 bytesRead := controller.bytesRead 197 rtt := scaleDuration(20 * time.Millisecond) 198 setRtt(rtt) 199 // consume less than 2/3 of the window... 200 dataRead := receiveWindowSize*2/3 - 1 201 // ... in 4*2/3 of the RTT 202 controller.epochStartOffset = controller.bytesRead 203 controller.epochStartTime = time.Now().Add(-rtt * 4 * 2 / 3) 204 controller.addBytesRead(dataRead) 205 offset := controller.getWindowUpdate() 206 Expect(offset).ToNot(BeZero()) 207 // check that the window size was not increased 208 Expect(controller.receiveWindowSize).To(Equal(oldWindowSize)) 209 // check that the new window size was used to increase the offset 210 Expect(offset).To(Equal(bytesRead + dataRead + oldWindowSize)) 211 }) 212 213 It("doesn't increase the window size to a value higher than the maxReceiveWindowSize", func() { 214 resetEpoch := func() { 215 // make sure the next call to maybeAdjustWindowSize will increase the window 216 controller.epochStartTime = time.Now().Add(-time.Millisecond) 217 controller.epochStartOffset = controller.bytesRead 218 controller.addBytesRead(controller.receiveWindowSize/2 + 1) 219 } 220 setRtt(scaleDuration(20 * time.Millisecond)) 221 resetEpoch() 222 controller.maybeAdjustWindowSize() 223 Expect(controller.receiveWindowSize).To(Equal(2 * oldWindowSize)) // 2000 224 // because the lastWindowUpdateTime is updated by MaybeTriggerWindowUpdate(), we can just call maybeAdjustWindowSize() multiple times and get an increase of the window size every time 225 resetEpoch() 226 controller.maybeAdjustWindowSize() 227 Expect(controller.receiveWindowSize).To(Equal(2 * 2 * oldWindowSize)) // 4000 228 resetEpoch() 229 controller.maybeAdjustWindowSize() 230 Expect(controller.receiveWindowSize).To(Equal(controller.maxReceiveWindowSize)) // 5000 231 controller.maybeAdjustWindowSize() 232 Expect(controller.receiveWindowSize).To(Equal(controller.maxReceiveWindowSize)) // 5000 233 }) 234 }) 235 }) 236 })