congestion: avoid overflows when calculating pacer budget (#5390)

* congestion: prevent uint64 overflow in pacer

This should never happen for bandwidths even remotely possible in
real-world scenarios, but it’s better to be safe.

* congestion: add a benchmark test for the pacer

* add a comment
This commit is contained in:
Marten Seemann
2025-10-17 13:30:07 +08:00
committed by GitHub
parent 42b198b8d1
commit f07d6939d0
3 changed files with 64 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
package congestion
import (
"math"
"time"
"github.com/quic-go/quic-go/internal/monotime"
@@ -48,8 +49,13 @@ func (p *pacer) Budget(now monotime.Time) protocol.ByteCount {
if p.lastSentTime.IsZero() {
return p.maxBurstSize()
}
budget := p.budgetAtLastSent + (protocol.ByteCount(p.adjustedBandwidth())*protocol.ByteCount(now.Sub(p.lastSentTime).Nanoseconds()))/1e9
if budget < 0 { // protect against overflows
delta := now.Sub(p.lastSentTime)
var added protocol.ByteCount
if delta > 0 {
added = p.timeScaledBandwidth(uint64(delta.Nanoseconds()))
}
budget := p.budgetAtLastSent + added
if added > 0 && budget < p.budgetAtLastSent {
budget = protocol.MaxByteCount
}
return min(p.maxBurstSize(), budget)
@@ -57,11 +63,30 @@ func (p *pacer) Budget(now monotime.Time) protocol.ByteCount {
func (p *pacer) maxBurstSize() protocol.ByteCount {
return max(
protocol.ByteCount(uint64((protocol.MinPacingDelay+protocol.TimerGranularity).Nanoseconds())*p.adjustedBandwidth())/1e9,
p.timeScaledBandwidth(uint64((protocol.MinPacingDelay + protocol.TimerGranularity).Nanoseconds())),
maxBurstSizePackets*p.maxDatagramSize,
)
}
// timeScaledBandwidth calculates the number of bytes that may be sent within
// a given time interval (ns nanoseconds), based on the current bandwidth estimate.
// It caps the scaled value to the maximum allowed burst and handles overflows.
func (p *pacer) timeScaledBandwidth(ns uint64) protocol.ByteCount {
bw := p.adjustedBandwidth()
if bw == 0 {
return 0
}
const nsPerSecond = 1e9
maxBurst := maxBurstSizePackets * p.maxDatagramSize
var scaled protocol.ByteCount
if ns > math.MaxUint64/bw {
scaled = maxBurst
} else {
scaled = protocol.ByteCount(bw * ns / nsPerSecond)
}
return scaled
}
// TimeUntilSend returns when the next packet should be sent.
// It returns zero if a packet can be sent immediately.
func (p *pacer) TimeUntilSend() monotime.Time {