github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/transport/tuic/congestion/pacer.go (about)

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