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