Skip to content

Commit 2c6c5a2

Browse files
tomcouplandgopherbot
authored andcommitted
rate: prevent overflows when calculating durationFromTokens
Currently, there is a conversion from float64 to int64 when returning the duration needed to accumulate the required number of tokens. When limiters are set with low limits, i.e. 1e-10, the duration needed is greater than math.MaxInt64. As per the language specifications, in these scenarios the outcome is implementation determined. This results in overflows on `amd64`, resulting in no wait, effectively jamming the limiter open. Here we add a check for this scenario, returning InfDuration if the desired duration is greater than math.MaxInt64. Fixes golang/go#71154 Change-Id: I775aab80fcc8563a59aa399844a64ef70b9eb76a Reviewed-on: https://go-review.googlesource.com/c/time/+/641336 Reviewed-by: Dmitri Shuralyov <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 1ce61fe commit 2c6c5a2

File tree

2 files changed

+26
-2
lines changed

2 files changed

+26
-2
lines changed

rate/rate.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -405,8 +405,15 @@ func (limit Limit) durationFromTokens(tokens float64) time.Duration {
405405
if limit <= 0 {
406406
return InfDuration
407407
}
408-
seconds := tokens / float64(limit)
409-
return time.Duration(float64(time.Second) * seconds)
408+
409+
duration := (tokens / float64(limit)) * float64(time.Second)
410+
411+
// Cap the duration to the maximum representable int64 value, to avoid overflow.
412+
if duration > float64(math.MaxInt64) {
413+
return InfDuration
414+
}
415+
416+
return time.Duration(duration)
410417
}
411418

412419
// tokensFromDuration is a unit conversion function from a time duration to the number of tokens

rate/rate_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -638,3 +638,20 @@ func TestSetAfterZeroLimit(t *testing.T) {
638638
// We set the limit to 10/s so expect to get another token in 100ms
639639
runWait(t, tt, lim, wait{"wait-after-set-nonzero-after-zero", context.Background(), 1, 1, true})
640640
}
641+
642+
// TestTinyLimit tests that a limiter does not allow more than burst, when the rate is tiny.
643+
// Prior to resolution of issue 71154, this test
644+
// would fail on amd64 due to overflow in durationFromTokens.
645+
func TestTinyLimit(t *testing.T) {
646+
lim := NewLimiter(1e-10, 1)
647+
648+
// The limiter starts with 1 burst token, so the first request should succeed
649+
if !lim.Allow() {
650+
t.Errorf("Limit(1e-10, 1) want true when first used")
651+
}
652+
653+
// The limiter should not have replenished the token bucket yet, so the second request should fail
654+
if lim.Allow() {
655+
t.Errorf("Limit(1e-10, 1) want false when already used")
656+
}
657+
}

0 commit comments

Comments
 (0)