-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathratelimit.go
More file actions
131 lines (120 loc) · 2.49 KB
/
Copy pathratelimit.go
File metadata and controls
131 lines (120 loc) · 2.49 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
128
129
130
131
package multidns
import (
"sync"
"time"
)
// slidingCounter counts events over a 60-second window using one bucket per
// second. It is safe for concurrent use.
type slidingCounter struct {
mu sync.Mutex
buckets [60]int64
last int64 // unix second of the most recent bucket update
}
func (s *slidingCounter) add(now time.Time, n int64) {
s.mu.Lock()
defer s.mu.Unlock()
s.advance(now.Unix())
s.buckets[now.Unix()%60] += n
}
func (s *slidingCounter) sum(now time.Time) int64 {
s.mu.Lock()
defer s.mu.Unlock()
s.advance(now.Unix())
var total int64
for _, v := range s.buckets {
total += v
}
return total
}
func (s *slidingCounter) advance(nowSec int64) {
if s.last == 0 {
s.last = nowSec
return
}
gap := nowSec - s.last
if gap <= 0 {
return
}
if gap >= 60 {
s.buckets = [60]int64{}
} else {
for i := int64(1); i <= gap; i++ {
s.buckets[(s.last+i)%60] = 0
}
}
s.last = nowSec
}
// tokenBucket enforces an RPM cap. capPerMin == 0 means no cap (always
// permits). Permits are refilled continuously at capPerMin/60 per second up
// to a burst of capPerMin/4 (clamped between 1 and capPerMin).
type tokenBucket struct {
mu sync.Mutex
capPerMin int
tokens float64
burst float64
lastRefill time.Time
}
func newTokenBucket(capPerMin int) *tokenBucket {
tb := &tokenBucket{}
tb.setCap(capPerMin)
return tb
}
func (tb *tokenBucket) setCap(capPerMin int) {
tb.mu.Lock()
defer tb.mu.Unlock()
tb.capPerMin = capPerMin
if capPerMin <= 0 {
tb.tokens = 0
tb.burst = 0
return
}
burst := float64(capPerMin) / 4
if burst < 1 {
burst = 1
}
if burst > float64(capPerMin) {
burst = float64(capPerMin)
}
tb.burst = burst
if tb.tokens > burst {
tb.tokens = burst
}
if tb.tokens <= 0 {
tb.tokens = burst
}
}
func (tb *tokenBucket) cap() int {
tb.mu.Lock()
defer tb.mu.Unlock()
return tb.capPerMin
}
// tryAcquire returns true if a permit was available. When capPerMin == 0 it
// always returns true (no cap).
func (tb *tokenBucket) tryAcquire(now time.Time) bool {
tb.mu.Lock()
defer tb.mu.Unlock()
if tb.capPerMin <= 0 {
return true
}
tb.refillLocked(now)
if tb.tokens >= 1 {
tb.tokens--
return true
}
return false
}
func (tb *tokenBucket) refillLocked(now time.Time) {
if tb.lastRefill.IsZero() {
tb.lastRefill = now
return
}
elapsed := now.Sub(tb.lastRefill).Seconds()
if elapsed <= 0 {
return
}
tb.tokens += elapsed * (float64(tb.capPerMin) / 60)
if tb.tokens > tb.burst {
tb.tokens = tb.burst
}
tb.lastRefill = now
}