github.com/sagernet/quic-go@v0.43.1-beta.1/internal/congestion/pacer.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  const maxBurstSizePackets = 10
    11  
    12  // The pacer implements a token bucket pacing algorithm.
    13  type pacer struct {
    14  	budgetAtLastSent  protocol.ByteCount
    15  	maxDatagramSize   protocol.ByteCount
    16  	lastSentTime      time.Time
    17  	adjustedBandwidth func() uint64 // in bytes/s
    18  }
    19  
    20  func newPacer(getBandwidth func() Bandwidth) *pacer {
    21  	p := &pacer{
    22  		maxDatagramSize: initialMaxDatagramSize,
    23  		adjustedBandwidth: func() uint64 {
    24  			// Bandwidth is in bits/s. We need the value in bytes/s.
    25  			bw := uint64(getBandwidth() / BytesPerSecond)
    26  			// Use a slightly higher value than the actual measured bandwidth.
    27  			// RTT variations then won't result in under-utilization of the congestion window.
    28  			// Ultimately, this will result in sending packets as acknowledgments are received rather than when timers fire,
    29  			// provided the congestion window is fully utilized and acknowledgments arrive at regular intervals.
    30  			return bw * 5 / 4
    31  		},
    32  	}
    33  	p.budgetAtLastSent = p.maxBurstSize()
    34  	return p
    35  }
    36  
    37  func (p *pacer) SentPacket(sendTime time.Time, size protocol.ByteCount) {
    38  	budget := p.Budget(sendTime)
    39  	if size >= budget {
    40  		p.budgetAtLastSent = 0
    41  	} else {
    42  		p.budgetAtLastSent = budget - size
    43  	}
    44  	p.lastSentTime = sendTime
    45  }
    46  
    47  func (p *pacer) Budget(now time.Time) protocol.ByteCount {
    48  	if p.lastSentTime.IsZero() {
    49  		return p.maxBurstSize()
    50  	}
    51  	budget := p.budgetAtLastSent + (protocol.ByteCount(p.adjustedBandwidth())*protocol.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9
    52  	if budget < 0 { // protect against overflows
    53  		budget = protocol.MaxByteCount
    54  	}
    55  	return utils.Min(p.maxBurstSize(), budget)
    56  }
    57  
    58  func (p *pacer) maxBurstSize() protocol.ByteCount {
    59  	return utils.Max(
    60  		protocol.ByteCount(uint64((protocol.MinPacingDelay+protocol.TimerGranularity).Nanoseconds())*p.adjustedBandwidth())/1e9,
    61  		maxBurstSizePackets*p.maxDatagramSize,
    62  	)
    63  }
    64  
    65  // TimeUntilSend returns when the next packet should be sent.
    66  // It returns the zero value of time.Time if a packet can be sent immediately.
    67  func (p *pacer) TimeUntilSend() time.Time {
    68  	if p.budgetAtLastSent >= p.maxDatagramSize {
    69  		return time.Time{}
    70  	}
    71  	diff := 1e9 * uint64(p.maxDatagramSize-p.budgetAtLastSent)
    72  	bw := p.adjustedBandwidth()
    73  	// We might need to round up this value.
    74  	// Otherwise, we might have a budget (slightly) smaller than the datagram size when the timer expires.
    75  	d := diff / bw
    76  	// this is effectively a math.Ceil, but using only integer math
    77  	if diff%bw > 0 {
    78  		d++
    79  	}
    80  	return p.lastSentTime.Add(utils.Max(protocol.MinPacingDelay, time.Duration(d)*time.Nanosecond))
    81  }
    82  
    83  func (p *pacer) SetMaxDatagramSize(s protocol.ByteCount) {
    84  	p.maxDatagramSize = s
    85  }