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

     1  package flowcontrol
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/MerlinKodo/quic-go/internal/protocol"
     8  	"github.com/MerlinKodo/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) {
    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  }