2024-01-03 21:57:33 +00:00
|
|
|
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
2022-03-10 09:44:48 +00:00
|
|
|
package vnet
|
|
|
|
|
|
|
|
import (
|
2024-01-03 21:57:33 +00:00
|
|
|
"math"
|
2022-03-10 09:44:48 +00:00
|
|
|
"sync"
|
|
|
|
"time"
|
2024-01-03 21:57:33 +00:00
|
|
|
|
|
|
|
"github.com/pion/logging"
|
2022-03-10 09:44:48 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// Bit is a single bit
|
|
|
|
Bit = 1
|
|
|
|
// KBit is a kilobit
|
|
|
|
KBit = 1000 * Bit
|
|
|
|
// MBit is a Megabit
|
|
|
|
MBit = 1000 * KBit
|
|
|
|
)
|
|
|
|
|
|
|
|
// TokenBucketFilter implements a token bucket rate limit algorithm.
|
|
|
|
type TokenBucketFilter struct {
|
|
|
|
NIC
|
2024-01-03 21:57:33 +00:00
|
|
|
currentTokensInBucket float64
|
2022-03-10 09:44:48 +00:00
|
|
|
c chan Chunk
|
|
|
|
queue *chunkQueue
|
|
|
|
queueSize int // in bytes
|
|
|
|
|
2024-01-03 21:57:33 +00:00
|
|
|
mutex sync.Mutex
|
|
|
|
rate int
|
|
|
|
maxBurst int
|
|
|
|
minRefillDuration time.Duration
|
2022-03-10 09:44:48 +00:00
|
|
|
|
|
|
|
wg sync.WaitGroup
|
|
|
|
done chan struct{}
|
2024-01-03 21:57:33 +00:00
|
|
|
|
|
|
|
log logging.LeveledLogger
|
2022-03-10 09:44:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// TBFOption is the option type to configure a TokenBucketFilter
|
|
|
|
type TBFOption func(*TokenBucketFilter) TBFOption
|
|
|
|
|
|
|
|
// TBFQueueSizeInBytes sets the max number of bytes waiting in the queue. Can
|
|
|
|
// only be set in constructor before using the TBF.
|
|
|
|
func TBFQueueSizeInBytes(bytes int) TBFOption {
|
|
|
|
return func(t *TokenBucketFilter) TBFOption {
|
|
|
|
prev := t.queueSize
|
|
|
|
t.queueSize = bytes
|
|
|
|
return TBFQueueSizeInBytes(prev)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-03 21:57:33 +00:00
|
|
|
// TBFRate sets the bit rate of a TokenBucketFilter
|
2022-03-10 09:44:48 +00:00
|
|
|
func TBFRate(rate int) TBFOption {
|
|
|
|
return func(t *TokenBucketFilter) TBFOption {
|
|
|
|
t.mutex.Lock()
|
|
|
|
defer t.mutex.Unlock()
|
|
|
|
previous := t.rate
|
|
|
|
t.rate = rate
|
|
|
|
return TBFRate(previous)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TBFMaxBurst sets the bucket size of the token bucket filter. This is the
|
|
|
|
// maximum size that can instantly leave the filter, if the bucket is full.
|
|
|
|
func TBFMaxBurst(size int) TBFOption {
|
|
|
|
return func(t *TokenBucketFilter) TBFOption {
|
|
|
|
t.mutex.Lock()
|
|
|
|
defer t.mutex.Unlock()
|
|
|
|
previous := t.maxBurst
|
|
|
|
t.maxBurst = size
|
|
|
|
return TBFMaxBurst(previous)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set updates a setting on the token bucket filter
|
|
|
|
func (t *TokenBucketFilter) Set(opts ...TBFOption) (previous TBFOption) {
|
|
|
|
for _, opt := range opts {
|
|
|
|
previous = opt(t)
|
|
|
|
}
|
|
|
|
return previous
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewTokenBucketFilter creates and starts a new TokenBucketFilter
|
|
|
|
func NewTokenBucketFilter(n NIC, opts ...TBFOption) (*TokenBucketFilter, error) {
|
|
|
|
tbf := &TokenBucketFilter{
|
|
|
|
NIC: n,
|
|
|
|
currentTokensInBucket: 0,
|
|
|
|
c: make(chan Chunk),
|
|
|
|
queue: nil,
|
|
|
|
queueSize: 50000,
|
|
|
|
mutex: sync.Mutex{},
|
|
|
|
rate: 1 * MBit,
|
2024-01-03 21:57:33 +00:00
|
|
|
maxBurst: 8 * KBit,
|
|
|
|
minRefillDuration: 100 * time.Millisecond,
|
2022-03-10 09:44:48 +00:00
|
|
|
wg: sync.WaitGroup{},
|
|
|
|
done: make(chan struct{}),
|
2024-01-03 21:57:33 +00:00
|
|
|
log: logging.NewDefaultLoggerFactory().NewLogger("tbf"),
|
2022-03-10 09:44:48 +00:00
|
|
|
}
|
|
|
|
tbf.Set(opts...)
|
|
|
|
tbf.queue = newChunkQueue(0, tbf.queueSize)
|
|
|
|
tbf.wg.Add(1)
|
|
|
|
go tbf.run()
|
|
|
|
return tbf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TokenBucketFilter) onInboundChunk(c Chunk) {
|
|
|
|
t.c <- c
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *TokenBucketFilter) run() {
|
|
|
|
defer t.wg.Done()
|
2024-01-03 21:57:33 +00:00
|
|
|
|
|
|
|
t.refillTokens(t.minRefillDuration)
|
|
|
|
lastRefill := time.Now()
|
2022-03-10 09:44:48 +00:00
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-t.done:
|
|
|
|
t.drainQueue()
|
|
|
|
return
|
|
|
|
case chunk := <-t.c:
|
2024-01-03 21:57:33 +00:00
|
|
|
if time.Since(lastRefill) > t.minRefillDuration {
|
|
|
|
t.refillTokens(time.Since(lastRefill))
|
|
|
|
lastRefill = time.Now()
|
|
|
|
}
|
2022-03-10 09:44:48 +00:00
|
|
|
t.queue.push(chunk)
|
|
|
|
t.drainQueue()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-03 21:57:33 +00:00
|
|
|
func (t *TokenBucketFilter) refillTokens(dt time.Duration) {
|
|
|
|
m := 1000.0 / float64(dt.Milliseconds())
|
|
|
|
add := (float64(t.rate) / m) / 8.0
|
|
|
|
t.mutex.Lock()
|
|
|
|
defer t.mutex.Unlock()
|
|
|
|
t.currentTokensInBucket = math.Min(float64(t.maxBurst), t.currentTokensInBucket+add)
|
|
|
|
t.log.Tracef("add=(%v / %v) / 8 = %v, currentTokensInBucket=%v, maxBurst=%v", t.rate, m, add, t.currentTokensInBucket, t.maxBurst)
|
|
|
|
}
|
|
|
|
|
2022-03-10 09:44:48 +00:00
|
|
|
func (t *TokenBucketFilter) drainQueue() {
|
|
|
|
for {
|
|
|
|
next := t.queue.peek()
|
|
|
|
if next == nil {
|
|
|
|
break
|
|
|
|
}
|
2024-01-03 21:57:33 +00:00
|
|
|
tokens := float64(len(next.UserData()))
|
2022-03-10 09:44:48 +00:00
|
|
|
if t.currentTokensInBucket < tokens {
|
2024-01-03 21:57:33 +00:00
|
|
|
t.log.Tracef("currentTokensInBucket=%v, tokens=%v, stop drain", t.currentTokensInBucket, tokens)
|
2022-03-10 09:44:48 +00:00
|
|
|
break
|
|
|
|
}
|
2024-01-03 21:57:33 +00:00
|
|
|
t.log.Tracef("currentTokensInBucket=%v, tokens=%v, pop chunk", t.currentTokensInBucket, tokens)
|
2022-03-10 09:44:48 +00:00
|
|
|
t.queue.pop()
|
|
|
|
t.NIC.onInboundChunk(next)
|
|
|
|
t.currentTokensInBucket -= tokens
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes and stops the token bucket filter queue
|
|
|
|
func (t *TokenBucketFilter) Close() error {
|
|
|
|
close(t.done)
|
|
|
|
t.wg.Wait()
|
|
|
|
return nil
|
|
|
|
}
|