x/time/rate: correctly handle 0 limits

Decrementing the burst in the reserveN method will frequently lead to us
setting the burst to 0 which makes the limiter mostly unusable.

This code was originally added in https://go.dev/cl/323429 to fix #39984
but the implementation introduced a different bug. To avoid regressing
to the behaviour described in #39984, pre-fill the limiter to the burst
value in the constructor.

Fixes #68541

Change-Id: Iab3b85d548a44fcb2d058336e5bbf11b19ea67b1
Reviewed-on: https://go-review.googlesource.com/c/time/+/600876
Reviewed-by: Sameer Ajmani <sameer@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Sameer Ajmani <sameer@golang.org>
diff --git a/rate/rate.go b/rate/rate.go
index 8f6c7f4..93a798a 100644
--- a/rate/rate.go
+++ b/rate/rate.go
@@ -99,8 +99,9 @@
 // bursts of at most b tokens.
 func NewLimiter(r Limit, b int) *Limiter {
 	return &Limiter{
-		limit: r,
-		burst: b,
+		limit:  r,
+		burst:  b,
+		tokens: float64(b),
 	}
 }
 
@@ -344,18 +345,6 @@
 			tokens:    n,
 			timeToAct: t,
 		}
-	} else if lim.limit == 0 {
-		var ok bool
-		if lim.burst >= n {
-			ok = true
-			lim.burst -= n
-		}
-		return Reservation{
-			ok:        ok,
-			lim:       lim,
-			tokens:    lim.burst,
-			timeToAct: t,
-		}
 	}
 
 	t, tokens := lim.advance(t)
diff --git a/rate/rate_test.go b/rate/rate_test.go
index 48ebaef..c91fc8f 100644
--- a/rate/rate_test.go
+++ b/rate/rate_test.go
@@ -618,3 +618,23 @@
 		t.Errorf("Limit(0, 1) want false when already used")
 	}
 }
+
+func TestSetAfterZeroLimit(t *testing.T) {
+	lim := NewLimiter(0, 1)
+	// The limiter should start off full, so even though our rate limit is 0, our first request
+	// should be allowed…
+	if !lim.Allow() {
+		t.Errorf("Limit(0, 1) want true when first used")
+	}
+	// …the token bucket is not being replenished though, so the second request should not succeed
+	if lim.Allow() {
+		t.Errorf("Limit(0, 1) want false when already used")
+	}
+
+	lim.SetLimit(10)
+
+	tt := makeTestTime(t)
+
+	// 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})
+}