github.com/kelleygo/clashcore@v1.0.2/transport/tuic/congestion/pacer.go (about) 1 package congestion 2 3 import ( 4 "math" 5 "time" 6 7 "github.com/metacubex/quic-go/congestion" 8 ) 9 10 const initialMaxDatagramSize = congestion.ByteCount(1252) 11 const MinPacingDelay = time.Millisecond 12 const TimerGranularity = time.Millisecond 13 const maxBurstSizePackets = 10 14 15 // The pacer implements a token bucket pacing algorithm. 16 type pacer struct { 17 budgetAtLastSent congestion.ByteCount 18 maxDatagramSize congestion.ByteCount 19 lastSentTime time.Time 20 getAdjustedBandwidth func() uint64 // in bytes/s 21 } 22 23 func newPacer(getBandwidth func() Bandwidth) *pacer { 24 p := &pacer{ 25 maxDatagramSize: initialMaxDatagramSize, 26 getAdjustedBandwidth: func() uint64 { 27 // Bandwidth is in bits/s. We need the value in bytes/s. 28 bw := uint64(getBandwidth() / BytesPerSecond) 29 // Use a slightly higher value than the actual measured bandwidth. 30 // RTT variations then won't result in under-utilization of the congestion window. 31 // Ultimately, this will result in sending packets as acknowledgments are received rather than when timers fire, 32 // provided the congestion window is fully utilized and acknowledgments arrive at regular intervals. 33 return bw * 5 / 4 34 }, 35 } 36 p.budgetAtLastSent = p.maxBurstSize() 37 return p 38 } 39 40 func (p *pacer) SentPacket(sendTime time.Time, size congestion.ByteCount) { 41 budget := p.Budget(sendTime) 42 if size > budget { 43 p.budgetAtLastSent = 0 44 } else { 45 p.budgetAtLastSent = budget - size 46 } 47 p.lastSentTime = sendTime 48 } 49 50 func (p *pacer) Budget(now time.Time) congestion.ByteCount { 51 if p.lastSentTime.IsZero() { 52 return p.maxBurstSize() 53 } 54 budget := p.budgetAtLastSent + (congestion.ByteCount(p.getAdjustedBandwidth())*congestion.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9 55 return Min(p.maxBurstSize(), budget) 56 } 57 58 func (p *pacer) maxBurstSize() congestion.ByteCount { 59 return Max( 60 congestion.ByteCount(uint64((MinPacingDelay+TimerGranularity).Nanoseconds())*p.getAdjustedBandwidth())/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 return p.lastSentTime.Add(Max( 72 MinPacingDelay, 73 time.Duration(math.Ceil(float64(p.maxDatagramSize-p.budgetAtLastSent)*1e9/float64(p.getAdjustedBandwidth())))*time.Nanosecond, 74 )) 75 } 76 77 func (p *pacer) SetMaxDatagramSize(s congestion.ByteCount) { 78 p.maxDatagramSize = s 79 }