From 5e2d9400ac52fe9bbd9d00cc6a1e54233a2cd403 Mon Sep 17 00:00:00 2001 From: Jan Novak Date: Sat, 29 Aug 2026 19:05:15 +0200 Subject: [PATCH] 03-rate-limiter: implement token bucket Limiter with step-by-step comments Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GbnJj9ZqEx9nUvzkjdwfMq --- 03-rate-limiter/main.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/03-rate-limiter/main.go b/03-rate-limiter/main.go index e370f2e..75f1a6c 100644 --- a/03-rate-limiter/main.go +++ b/03-rate-limiter/main.go @@ -17,22 +17,51 @@ package main import ( "fmt" + "sync" "time" ) type Limiter struct { + // 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`. // TODO: implement me. 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. // TODO: implement me. 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 }