github.com/metacubex/quic-go@v0.44.1-0.20240520163451-20b689a59136/internal/congestion/pacer.go (about) 1 package congestion 2 3 import ( 4 "time" 5 6 "github.com/metacubex/quic-go/internal/protocol" 7 "github.com/metacubex/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 }