package common import ( "time" "github.com/apernet/quic-go/congestion" ) const ( maxBurstPackets = 10 maxBurstPacingDelayMultiplier = 4 ) // Pacer implements a token bucket pacing algorithm. type Pacer struct { budgetAtLastSent congestion.ByteCount maxDatagramSize congestion.ByteCount lastSentTime time.Time getBandwidth func() congestion.ByteCount // in bytes/s } func NewPacer(getBandwidth func() congestion.ByteCount) *Pacer { p := &Pacer{ budgetAtLastSent: maxBurstPackets * congestion.InitialPacketSizeIPv4, maxDatagramSize: congestion.InitialPacketSizeIPv4, getBandwidth: getBandwidth, } return p } func (p *Pacer) SentPacket(sendTime time.Time, size congestion.ByteCount) { budget := p.Budget(sendTime) if size > budget { p.budgetAtLastSent = 0 } else { p.budgetAtLastSent = budget - size } p.lastSentTime = sendTime } func (p *Pacer) Budget(now time.Time) congestion.ByteCount { if p.lastSentTime.IsZero() { return p.maxBurstSize() } budget := p.budgetAtLastSent + (p.getBandwidth()*congestion.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9 if budget < 0 { // protect against overflows budget = congestion.ByteCount(1<<62 - 1) } return min(p.maxBurstSize(), budget) } func (p *Pacer) maxBurstSize() congestion.ByteCount { return max( congestion.ByteCount((maxBurstPacingDelayMultiplier*congestion.MinPacingDelay).Nanoseconds())*p.getBandwidth()/1e9, maxBurstPackets*p.maxDatagramSize, ) } // TimeUntilSend returns when the next packet should be sent. // It returns the zero value of time.Time if a packet can be sent immediately. func (p *Pacer) TimeUntilSend() time.Time { if p.budgetAtLastSent >= p.maxDatagramSize { return time.Time{} } diff := 1e9 * uint64(p.maxDatagramSize-p.budgetAtLastSent) bw := uint64(p.getBandwidth()) // We might need to round up this value. // Otherwise, we might have a budget (slightly) smaller than the datagram size when the timer expires. d := diff / bw // this is effectively a math.Ceil, but using only integer math if diff%bw > 0 { d++ } return p.lastSentTime.Add(max(congestion.MinPacingDelay, time.Duration(d)*time.Nanosecond)) } func (p *Pacer) SetMaxDatagramSize(s congestion.ByteCount) { p.maxDatagramSize = s }