github.com/MerlinKodo/quic-go@v0.39.2/internal/flowcontrol/base_flow_controller_test.go (about)

     1  package flowcontrol
     2  
     3  import (
     4  	"os"
     5  	"strconv"
     6  	"time"
     7  
     8  	"github.com/MerlinKodo/quic-go/internal/protocol"
     9  	"github.com/MerlinKodo/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  			controller.UpdateSendWindow(20)
    63  			Expect(controller.sendWindowSize()).To(Equal(protocol.ByteCount(20)))
    64  			controller.UpdateSendWindow(10)
    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  })