-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrlsw.go
179 lines (140 loc) · 4.47 KB
/
rlsw.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
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package rlsw
import (
"errors"
"sync"
"time"
)
// Limiter is a sliding window rate limiter that allows for a certain number of requests per duration.
type Limiter struct {
mu sync.Mutex
timestamps []time.Time
limit int
window time.Duration
maxWaitTime time.Duration
}
func NewRateLimiter(limit int, duration time.Duration) *Limiter {
return &Limiter{
timestamps: make([]time.Time, 0),
limit: limit,
window: duration,
}
}
func (r *Limiter) GetLimit() int {
return r.limit
}
func (r *Limiter) SetLimit(limit int) {
r.mu.Lock()
defer r.mu.Unlock()
r.limit = limit
}
func (r *Limiter) GetWindow() time.Duration {
return r.window
}
func (r *Limiter) SetWindow(window time.Duration) {
r.mu.Lock()
defer r.mu.Unlock()
r.window = window
}
// Clears the expired timestamps. Does not Lock or Unlock Mutex, never call on it's own.
func (r *Limiter) clearExpired(now time.Time) {
for len(r.timestamps) > 0 && now.Sub(r.timestamps[0]) > r.window {
r.timestamps = r.timestamps[1:]
}
}
func (r *Limiter) addTime(timestamp time.Time) {
r.timestamps = append(r.timestamps, timestamp)
}
func (r *Limiter) getWaitTime(now time.Time) time.Duration {
return r.window - now.Sub(r.timestamps[0])
}
func (r *Limiter) removeOldest() {
r.timestamps = r.timestamps[1:]
}
// Allow returns true if the window has space for another request and appends a timestamp to the window.
func (r *Limiter) Allow() bool {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
r.clearExpired(now)
if len(r.timestamps) >= r.limit {
return false
}
r.addTime(now)
return true
}
// Schedule() removes any expired timestamps, then returns the duration to wait before another request should be allowed.
//
// If the request is allowed, it will append the current timestamp to the window.
//
// If the request is not allowed, it will append the current timestamp + the wait time to the timestamps, then remove the oldest timestamp, even if it's not expired.
// This allows you to concurrently call Schedule() and ensure each request waits the appropriate amount of time.
func (r *Limiter) Schedule() time.Duration {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
r.clearExpired(now)
if len(r.timestamps) >= r.limit {
waitTime := r.getWaitTime(now)
r.addTime(now.Add(waitTime)) // Append the timestamp with the future time that the wait time with expire at.
r.removeOldest() // Remove the oldest timestamp, this way, the next request will need to wait longer.
return waitTime
}
r.addTime(now)
return 0
}
// Wait calls time.Sleep(r.Schedule()). This blocks until the rate limiter allows another request. If blocked, it schedules the time in the future on the timestamps, and removes the oldest timestamp.
// This way, the next request will need to wait longer.
func (r *Limiter) Wait() {
time.Sleep(r.Schedule())
}
// The problem with this is that if used with go routines, concurrent requests to GetWaitTime() will return the same or close to the wait WaitTime
// This won't be accurate if there is a time gap between the oldest time and the next available time.
// func (r *Limiter) Wait_Old() {
// time.Sleep(r.GetWaitTime())
// r.addTime(time.Now())
// }
// Clears expired timestamps, then gets the current wait time and returns it without appending to the timestamps. Returns 0 if there is no wait.
func (r *Limiter) GetWaitTime() time.Duration {
r.mu.Lock()
defer r.mu.Unlock()
now := time.Now()
r.clearExpired(now)
if len(r.timestamps) >= r.limit {
return r.getWaitTime(now)
}
return 0
}
// Clears the expired timestamps. Uses a mutex to lock and unlock, safe to call manually.
//
// Not normally needed, since Allow(), Schedule(), and Wait() all clear the expired timestamps.
func (r *Limiter) Clear() {
r.mu.Lock()
defer r.mu.Unlock()
r.timestamps = make([]time.Time, 0)
}
// Clears any expired timestamps, then returns the current len of r.timestamps
func (r *Limiter) TimeStampCount() int {
r.mu.Lock()
defer r.mu.Unlock()
r.clearExpired(time.Now())
return len(r.timestamps)
}
func (r *Limiter) WaitWithLimit(waitLimit time.Duration) error {
if r.GetWaitTime() > waitLimit {
return errors.New("Wait time exceeds limit")
}
r.Wait()
return nil
}
func (r *Limiter) SetMaxWaitTime(waitLimit time.Duration) {
r.mu.Lock()
defer r.mu.Unlock()
r.maxWaitTime = waitLimit
}
func (r *Limiter) WaitWithInternalLimit() error {
if r.maxWaitTime != 0 && r.GetWaitTime() > r.maxWaitTime {
return errors.New("Wait time exceeds limit")
}
r.Wait()
return nil
}