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  }