-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
congestion: implemented dynamic packet pacing based on network condit…
…ions
- Loading branch information
1 parent
bb265e9
commit 9658cea
Showing
3 changed files
with
83 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,69 @@ | ||
package congestion | ||
|
||
import "time" | ||
import ( | ||
"time" | ||
|
||
const bytesPerToken = 512 | ||
"github.com/cooldogedev/spectral/internal/protocol" | ||
) | ||
|
||
const ( | ||
minPacingDelay = time.Millisecond * 2 | ||
minBurstSize = 2 | ||
maxBurstSize = 10 | ||
) | ||
|
||
type Pacer struct { | ||
capacity int | ||
tokens int | ||
interval time.Duration | ||
lastRefill time.Time | ||
capacity uint64 | ||
tokens uint64 | ||
window uint64 | ||
rate float64 | ||
rtt time.Duration | ||
prev time.Time | ||
} | ||
|
||
func NewPacer(interval time.Duration, capacity int) *Pacer { | ||
return &Pacer{ | ||
capacity: capacity, | ||
tokens: capacity, | ||
interval: interval, | ||
lastRefill: time.Now(), | ||
} | ||
func NewPacer() *Pacer { | ||
return &Pacer{prev: time.Now()} | ||
} | ||
|
||
func (p *Pacer) Consume(bytes int) time.Duration { | ||
now := time.Now() | ||
elapsed := now.Sub(p.lastRefill) | ||
if elapsed >= p.interval { | ||
p.tokens = min(p.tokens+int(elapsed/p.interval), p.capacity) | ||
p.lastRefill = now | ||
func (p *Pacer) Delay(rtt time.Duration, bytes uint64, window uint64) time.Duration { | ||
if window != p.window || rtt != p.rtt { | ||
p.capacity = optimalCapacity(rtt, window) | ||
p.tokens = min(p.tokens, p.capacity) | ||
p.window = window | ||
p.rate = float64(window) / max(rtt.Seconds(), 1) | ||
p.rtt = rtt | ||
} | ||
|
||
tokensNeeded := (bytes + bytesPerToken - 1) / bytesPerToken | ||
if p.tokens >= tokensNeeded { | ||
p.tokens -= tokensNeeded | ||
if p.tokens >= bytes { | ||
return 0 | ||
} | ||
return time.Duration(tokensNeeded-p.tokens) * p.interval | ||
|
||
now := time.Now() | ||
newTokens := p.rate * now.Sub(p.prev).Seconds() | ||
p.tokens = min(p.tokens+uint64(newTokens), p.capacity) | ||
p.prev = now | ||
if p.tokens >= bytes { | ||
return 0 | ||
} | ||
delay := time.Duration(float64(bytes-p.tokens) / p.rate * float64(time.Second)) | ||
return max(delay, minPacingDelay) | ||
} | ||
|
||
func (p *Pacer) OnSend(bytes uint64) { | ||
p.tokens = max(p.tokens-bytes, 0) | ||
} | ||
|
||
func optimalCapacity(rtt time.Duration, window uint64) uint64 { | ||
rttNs := max(rtt.Nanoseconds(), 1) | ||
capacity := (window * uint64(minPacingDelay.Nanoseconds())) / uint64(rttNs) | ||
return clamp(capacity, minBurstSize*protocol.MaxPacketSize, maxBurstSize*protocol.MaxPacketSize) | ||
} | ||
|
||
func (p *Pacer) SetInterval(interval time.Duration) { | ||
if interval > 0 { | ||
p.interval = interval | ||
func clamp(value, min, max uint64) uint64 { | ||
if value < min { | ||
return min | ||
} else if value > max { | ||
return max | ||
} | ||
return value | ||
} |