Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion server/internal/operational/memory/limiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ func (s *Store) TakeRateLimitTokens(ctx context.Context, key string, limit int,
if !allowed {
result.RetryAfter = time.Duration(math.Ceil((float64(cost)-v.tokens)*millis/float64(limit))) * time.Millisecond
}
v.updated, v.expires = now, now.Add(time.Duration(period.Milliseconds()*2)*time.Millisecond)
// Like the PostgreSQL adapter, never move the refill timestamp backwards.
if now.After(v.updated) {
v.updated = now
}
v.expires = now.Add(time.Duration(period.Milliseconds()*2) * time.Millisecond)
s.buckets[strings.Clone(key)] = v
return result, nil
}
32 changes: 32 additions & 0 deletions server/internal/operational/memory/limiter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,35 @@ func TestClassifyTokenBucketRequestOversizedLimit(t *testing.T) {
t.Fatalf("classifyTokenBucketRequest with oversized limit = %s, want %s", got, tokenBucketRejectedParameter)
}
}

func TestTakeRateLimitTokensClockRegressionDoesNotRefillTwice(t *testing.T) {
now := time.Unix(1000, 0)
s, err := New(Config{Clock: func() time.Time { return now }})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = s.Close() })
take := func(cost int) bool {
t.Helper()
r, err := s.TakeRateLimitTokens(t.Context(), "subject", 10, 10*time.Second, cost)
if err != nil {
t.Fatal(err)
}
return r.Allowed
}
if !take(10) {
t.Fatal("initial full-bucket spend denied")
}
now = now.Add(5 * time.Second)
if !take(5) {
t.Fatal("refilled spend denied")
}
now = now.Add(-5 * time.Second)
if take(1) {
t.Fatal("empty bucket allowed a token after clock regression")
}
now = now.Add(5 * time.Second)
if take(1) {
t.Fatal("clock regression refilled the same elapsed interval twice")
}
}
Loading