github.com/daeuniverse/quic-go@v0.0.0-20240413031024-943f218e0810/internal/congestion/hybrid_slow_start.go (about)

     1  package congestion
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/daeuniverse/quic-go/internal/protocol"
     7  )
     8  
     9  // Note(pwestin): the magic clamping numbers come from the original code in
    10  // tcp_cubic.c.
    11  const hybridStartLowWindow = protocol.ByteCount(16)
    12  
    13  // Number of delay samples for detecting the increase of delay.
    14  const hybridStartMinSamples = uint32(8)
    15  
    16  // Exit slow start if the min rtt has increased by more than 1/8th.
    17  const hybridStartDelayFactorExp = 3 // 2^3 = 8
    18  // The original paper specifies 2 and 8ms, but those have changed over time.
    19  const (
    20  	hybridStartDelayMinThresholdUs = int64(4000)
    21  	hybridStartDelayMaxThresholdUs = int64(16000)
    22  )
    23  
    24  // HybridSlowStart implements the TCP hybrid slow start algorithm
    25  type HybridSlowStart struct {
    26  	endPacketNumber      protocol.PacketNumber
    27  	lastSentPacketNumber protocol.PacketNumber
    28  	started              bool
    29  	currentMinRTT        time.Duration
    30  	rttSampleCount       uint32
    31  	hystartFound         bool
    32  }
    33  
    34  // StartReceiveRound is called for the start of each receive round (burst) in the slow start phase.
    35  func (s *HybridSlowStart) StartReceiveRound(lastSent protocol.PacketNumber) {
    36  	s.endPacketNumber = lastSent
    37  	s.currentMinRTT = 0
    38  	s.rttSampleCount = 0
    39  	s.started = true
    40  }
    41  
    42  // IsEndOfRound returns true if this ack is the last packet number of our current slow start round.
    43  func (s *HybridSlowStart) IsEndOfRound(ack protocol.PacketNumber) bool {
    44  	return s.endPacketNumber < ack
    45  }
    46  
    47  // ShouldExitSlowStart should be called on every new ack frame, since a new
    48  // RTT measurement can be made then.
    49  // rtt: the RTT for this ack packet.
    50  // minRTT: is the lowest delay (RTT) we have seen during the session.
    51  // congestionWindow: the congestion window in packets.
    52  func (s *HybridSlowStart) ShouldExitSlowStart(latestRTT time.Duration, minRTT time.Duration, congestionWindow protocol.ByteCount) bool {
    53  	if !s.started {
    54  		// Time to start the hybrid slow start.
    55  		s.StartReceiveRound(s.lastSentPacketNumber)
    56  	}
    57  	if s.hystartFound {
    58  		return true
    59  	}
    60  	// Second detection parameter - delay increase detection.
    61  	// Compare the minimum delay (s.currentMinRTT) of the current
    62  	// burst of packets relative to the minimum delay during the session.
    63  	// Note: we only look at the first few(8) packets in each burst, since we
    64  	// only want to compare the lowest RTT of the burst relative to previous
    65  	// bursts.
    66  	s.rttSampleCount++
    67  	if s.rttSampleCount <= hybridStartMinSamples {
    68  		if s.currentMinRTT == 0 || s.currentMinRTT > latestRTT {
    69  			s.currentMinRTT = latestRTT
    70  		}
    71  	}
    72  	// We only need to check this once per round.
    73  	if s.rttSampleCount == hybridStartMinSamples {
    74  		// Divide minRTT by 8 to get a rtt increase threshold for exiting.
    75  		minRTTincreaseThresholdUs := int64(minRTT / time.Microsecond >> hybridStartDelayFactorExp)
    76  		// Ensure the rtt threshold is never less than 2ms or more than 16ms.
    77  		minRTTincreaseThresholdUs = min(minRTTincreaseThresholdUs, hybridStartDelayMaxThresholdUs)
    78  		minRTTincreaseThreshold := time.Duration(max(minRTTincreaseThresholdUs, hybridStartDelayMinThresholdUs)) * time.Microsecond
    79  
    80  		if s.currentMinRTT > (minRTT + minRTTincreaseThreshold) {
    81  			s.hystartFound = true
    82  		}
    83  	}
    84  	// Exit from slow start if the cwnd is greater than 16 and
    85  	// increasing delay is found.
    86  	return congestionWindow >= hybridStartLowWindow && s.hystartFound
    87  }
    88  
    89  // OnPacketSent is called when a packet was sent
    90  func (s *HybridSlowStart) OnPacketSent(packetNumber protocol.PacketNumber) {
    91  	s.lastSentPacketNumber = packetNumber
    92  }
    93  
    94  // OnPacketAcked gets invoked after ShouldExitSlowStart, so it's best to end
    95  // the round when the final packet of the burst is received and start it on
    96  // the next incoming ack.
    97  func (s *HybridSlowStart) OnPacketAcked(ackedPacketNumber protocol.PacketNumber) {
    98  	if s.IsEndOfRound(ackedPacketNumber) {
    99  		s.started = false
   100  	}
   101  }
   102  
   103  // Started returns true if started
   104  func (s *HybridSlowStart) Started() bool {
   105  	return s.started
   106  }
   107  
   108  // Restart the slow start phase
   109  func (s *HybridSlowStart) Restart() {
   110  	s.started = false
   111  	s.hystartFound = false
   112  }