-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathaccept_rate_limiter.go
More file actions
127 lines (115 loc) · 2.72 KB
/
Copy pathaccept_rate_limiter.go
File metadata and controls
127 lines (115 loc) · 2.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package main
import (
"context"
"sync"
"time"
)
// acceptRateLimiter is a simple token-bucket limiter for new TCP accepts. It
// enforces an average rate (tokens added per second) with a small burst
// capacity so short spikes are allowed without overwhelming the node.
type acceptRateLimiter struct {
rate float64 // tokens per second
burst float64 // maximum tokens
tokens float64 // current tokens
last time.Time // last refill time
mu sync.Mutex
}
func newAcceptRateLimiter(maxPerSecond, burst int) *acceptRateLimiter {
if maxPerSecond <= 0 {
return nil
}
rate := float64(maxPerSecond)
burstSize := float64(burst)
if burstSize <= 0 {
burstSize = rate
}
// By default we allow up to one second's worth of connections to
// arrive in a short spike; operators can raise/lower burst via
// config to suit their hardware.
return &acceptRateLimiter{
rate: rate,
burst: burstSize,
tokens: burstSize,
last: time.Now(),
}
}
// updateRate dynamically updates the rate and burst capacity of the limiter.
// This is used to transition from reconnection mode to steady-state mode.
func (l *acceptRateLimiter) updateRate(newRate, newBurst int) {
if l == nil {
return
}
l.mu.Lock()
defer l.mu.Unlock()
l.rate = float64(newRate)
l.burst = float64(newBurst)
if l.tokens > l.burst {
l.tokens = l.burst
}
}
// wait blocks as needed so that no more than "rate" accepts occur on average,
// with up to "burst" accepts allowed in a short spike. It respects ctx
// cancellation so shutdown is not delayed by the limiter.
func (l *acceptRateLimiter) wait(ctx context.Context) bool {
if l == nil {
return true
}
l.mu.Lock()
if l.rate <= 0 {
l.mu.Unlock()
return true
}
now := time.Now()
if l.last.IsZero() {
l.last = now
}
elapsed := now.Sub(l.last).Seconds()
if elapsed > 0 {
l.tokens += elapsed * l.rate
if l.tokens > l.burst {
l.tokens = l.burst
}
l.last = now
}
if l.tokens >= 1 {
l.tokens -= 1
l.mu.Unlock()
return true
}
// Reserve a token so concurrent waiters can't all pass after sleeping.
l.tokens -= 1
need := -l.tokens
rate := l.rate
l.mu.Unlock()
wait := time.Duration(need / rate * float64(time.Second))
if wait <= 0 {
wait = time.Millisecond
}
timer := time.NewTimer(wait)
defer timer.Stop()
select {
case <-ctx.Done():
// Undo the reservation so canceled waits don't depress the bucket.
l.mu.Lock()
now := time.Now()
if l.last.IsZero() {
l.last = now
}
elapsed := now.Sub(l.last).Seconds()
if elapsed > 0 {
l.tokens += elapsed * l.rate
if l.tokens > l.burst {
l.tokens = l.burst
}
l.last = now
}
l.tokens += 1
if l.tokens > l.burst {
l.tokens = l.burst
}
l.mu.Unlock()
return false
case <-timer.C:
}
return true
}