-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy paththrottle.go
85 lines (66 loc) · 1.72 KB
/
throttle.go
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
package throttle
import (
"sync"
"time"
)
const windowSize = time.Second
// Throttler manages the execution of operations so that they don't exceed a specified rate limit.
type Throttler struct {
mu sync.Mutex
window time.Time
clock Clock
counter uint64
limit uint64
}
// New creates a new instance of Throttler with a specified limit.
func New(limit uint64, setters ...Option) *Throttler {
opts := buildOptions(setters)
return &Throttler{
limit: limit,
clock: opts.clock,
}
}
// Acquire blocks until the operation can be executed within the rate limit.
func (t *Throttler) Acquire() {
t.mu.Lock()
t.advance()
t.mu.Unlock()
}
// advance updates the throttler state, advancing the window or incrementing the counter as necessary.
func (t *Throttler) advance() {
// pass through
if t.limit == 0 {
return
}
clock := t.clock
now := clock.Now()
// if this is the first operation, initialize the window
if t.window.IsZero() {
t.window = now
}
windowDur := now.Sub(t.window)
// if the current window has expired
if windowDur > windowSize {
// start a new window
t.reset(now)
return
}
nextCount := t.counter + 1
// if adding another operation doesn't exceed the limit
if t.limit >= nextCount {
// increment the counter
t.counter = nextCount
return
}
sleepDur := windowSize - windowDur
// if the limit is reached, wait until the current window expires
// we use an optional clock offset to account for clock skew.
clock.Sleep(sleepDur)
// after sleeping, reset to a new window starting now
t.reset(clock.Now())
}
// reset starts a new window from the specified start time and resets the operation counter.
func (t *Throttler) reset(window time.Time) {
t.window = window
t.counter = 1
}