github.com/sagernet/quic-go@v0.43.1-beta.1/internal/congestion/hybrid_slow_start.go (about)

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