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 <dmitshur@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
diff --git a/rate/rate.go b/rate/rate.go
index 93a798a..ec5f0cd 100644
--- a/rate/rate.go
+++ b/rate/rate.go
@@ -405,8 +405,15 @@
 	if limit <= 0 {
 		return InfDuration
 	}
-	seconds := tokens / float64(limit)
-	return time.Duration(float64(time.Second) * seconds)
+
+	duration := (tokens / float64(limit)) * float64(time.Second)
+
+	// Cap the duration to the maximum representable int64 value, to avoid overflow.
+	if duration > float64(math.MaxInt64) {
+		return InfDuration
+	}
+
+	return time.Duration(duration)
 }
 
 // tokensFromDuration is a unit conversion function from a time duration to the number of tokens
diff --git a/rate/rate_test.go b/rate/rate_test.go
index c9bc239..8b93903 100644
--- a/rate/rate_test.go
+++ b/rate/rate_test.go
@@ -638,3 +638,20 @@
 	// We set the limit to 10/s so expect to get another token in 100ms
 	runWait(t, tt, lim, wait{"wait-after-set-nonzero-after-zero", context.Background(), 1, 1, true})
 }
+
+// TestTinyLimit tests that a limiter does not allow more than burst, when the rate is tiny.
+// Prior to resolution of issue 71154, this test
+// would fail on amd64 due to overflow in durationFromTokens.
+func TestTinyLimit(t *testing.T) {
+	lim := NewLimiter(1e-10, 1)
+
+	// The limiter starts with 1 burst token, so the first request should succeed
+	if !lim.Allow() {
+		t.Errorf("Limit(1e-10, 1) want true when first used")
+	}
+
+	// The limiter should not have replenished the token bucket yet, so the second request should fail
+	if lim.Allow() {
+		t.Errorf("Limit(1e-10, 1) want false when already used")
+	}
+}