Skip to content

Commit 9364ca3

Browse files
authored
Fix an issue with maxSlack boundary updates. (#124)
Fixes #119 The solution is a copy from #120, but follows the testing framework that we have - I did not want us to have a real `Sleep` in tests. I'm not exactly thrilled by the testing setup (especially the milliseconds) or the clock itself, but I'm not willing to totally give up on it like #120 proposes. I also wanted ALL implementations of the ratelimiter to be tested, not just the currently selected. Might follow up with some testing cleanups and/or clock migration.
1 parent 60908a3 commit 9364ca3

File tree

2 files changed

+30
-3
lines changed

2 files changed

+30
-3
lines changed

limiter_atomic_int64.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@
2121
package ratelimit // import "go.uber.org/ratelimit"
2222

2323
import (
24-
"time"
25-
2624
"sync/atomic"
25+
"time"
2726
)
2827

2928
type atomicInt64Limiter struct {
@@ -69,7 +68,7 @@ func (t *atomicInt64Limiter) Take() time.Time {
6968
case timeOfNextPermissionIssue == 0 || (t.maxSlack == 0 && now-timeOfNextPermissionIssue > int64(t.perRequest)):
7069
// if this is our first call or t.maxSlack == 0 we need to shrink issue time to now
7170
newTimeOfNextPermissionIssue = now
72-
case t.maxSlack > 0 && now-timeOfNextPermissionIssue > int64(t.maxSlack):
71+
case t.maxSlack > 0 && now-timeOfNextPermissionIssue > int64(t.maxSlack)+int64(t.perRequest):
7372
// a lot of nanoseconds passed since the last Take call
7473
// we will limit max accumulated time to maxSlack
7574
newTimeOfNextPermissionIssue = now - int64(t.maxSlack)

ratelimit_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
type testRunner interface {
1515
// createLimiter builds a limiter with given options.
1616
createLimiter(int, ...Option) Limiter
17+
// takeOnceAfter attempts to Take at a specific time.
18+
takeOnceAfter(time.Duration, Limiter)
1719
// startTaking tries to Take() on passed in limiters in a loop/goroutine.
1820
startTaking(rls ...Limiter)
1921
// assertCountAt asserts the limiters have Taken() a number of times at the given time.
@@ -112,6 +114,16 @@ func (r *runnerImpl) startTaking(rls ...Limiter) {
112114
})
113115
}
114116

117+
// takeOnceAfter attempts to Take at a specific time.
118+
func (r *runnerImpl) takeOnceAfter(d time.Duration, rl Limiter) {
119+
r.wg.Add(1)
120+
r.afterFunc(d, func() {
121+
rl.Take()
122+
r.count.Inc()
123+
r.wg.Done()
124+
})
125+
}
126+
115127
// assertCountAt asserts the limiters have Taken() a number of times at a given time.
116128
func (r *runnerImpl) assertCountAt(d time.Duration, count int) {
117129
r.wg.Add(1)
@@ -269,6 +281,22 @@ func TestInitial(t *testing.T) {
269281
}
270282
}
271283

284+
func TestMaxSlack(t *testing.T) {
285+
t.Parallel()
286+
runTest(t, func(r testRunner) {
287+
rl := r.createLimiter(1, WithSlack(1))
288+
289+
r.takeOnceAfter(time.Nanosecond, rl)
290+
r.takeOnceAfter(2*time.Second+1*time.Nanosecond, rl)
291+
r.takeOnceAfter(2*time.Second+2*time.Nanosecond, rl)
292+
r.takeOnceAfter(2*time.Second+3*time.Nanosecond, rl)
293+
r.takeOnceAfter(2*time.Second+4*time.Nanosecond, rl)
294+
295+
r.assertCountAt(3*time.Second, 3)
296+
r.assertCountAt(10*time.Second, 5)
297+
})
298+
}
299+
272300
func TestSlack(t *testing.T) {
273301
t.Parallel()
274302
// To simulate slack, we combine two limiters.

0 commit comments

Comments
 (0)