03-rate-limiter: implement token bucket Limiter with step-by-step comments

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbnJj9ZqEx9nUvzkjdwfMq
This commit is contained in:
2026-08-29 19:05:15 +02:00
co-authored by Claude Fable 5
parent 1610cd69e0
commit 5e2d9400ac
+30 -1
View File
@@ -17,22 +17,51 @@ package main
import ( import (
"fmt" "fmt"
"sync"
"time" "time"
) )
type Limiter struct { type Limiter struct {
// TODO: fields (mutex, current tokens, max, rate, last refill time) // TODO: fields (mutex, current tokens, max, rate, last refill time)
mu sync.Mutex
tokens float64
max float64
rate float64
last time.Time
} }
// NewLimiter returns a limiter allowing `rate` ops/sec with bursts up to `burst`. // NewLimiter returns a limiter allowing `rate` ops/sec with bursts up to `burst`.
// TODO: implement me. // TODO: implement me.
func NewLimiter(rate float64, burst int) *Limiter { func NewLimiter(rate float64, burst int) *Limiter {
return &Limiter{} return &Limiter{tokens: float64(burst), rate: rate, max: float64(burst), last: time.Now()}
} }
// Allow reports whether one operation may proceed now. // Allow reports whether one operation may proceed now.
// TODO: implement me. // TODO: implement me.
func (l *Limiter) Allow() bool { func (l *Limiter) Allow() bool {
/*
1. Lock — refill + consume is a read-modify-write on two fields;
without the lock two goroutines can both see 1 token and both take it.
2. Lazy refill — tokens earned since the last call = elapsed * rate.
float64 so fractions (0.6 of a token) carry over instead of truncating to 0.
3. Cap at max — idle time must not build unlimited credit (burst is a ceiling).
4. Update last on EVERY call, granted or not — otherwise the same interval
is credited again on the next call.
5. If at least one whole token is available, take it and allow; else deny.
Non-blocking: never sleeps or waits.
*/
l.mu.Lock()
defer l.mu.Unlock()
l.tokens += l.rate * time.Since(l.last).Seconds()
if l.tokens > l.max {
l.tokens = l.max
}
l.last = time.Now()
if l.tokens >= 1 {
l.tokens--
return true
}
return false return false
} }