// Exercise 03 — Token Bucket Rate Limiter // // ASSIGNMENT // ---------- // Implement a token-bucket limiter: // // 1. NewLimiter(rate, burst): capacity `burst` tokens, refilled continuously // at `rate` tokens/second. Bucket starts FULL. // 2. Allow() consumes one token and returns true, or returns false if no // token is available. Must be non-blocking and safe for concurrent use. // 3. Refill must be LAZY — no background goroutine, no ticker. Compute the // accrued tokens from elapsed time inside Allow(). // 4. Tokens never exceed `burst` (idle time must not build infinite credit). // // Run the tests: go test -race ./03-rate-limiter/ 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{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 } func main() { l := NewLimiter(5, 3) // 5/sec, burst 3 for i := 0; i < 10; i++ { fmt.Printf("req %d: allowed=%v\n", i, l.Allow()) time.Sleep(100 * time.Millisecond) } }