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  }