Контекст: В backend/internal/middleware/ratelimit.go:61 мьютекс блокируется без defer Unlock(). Если код между Lock и Unlock запаникует, мьютекс останется заблокированным навсегда.
// ratelimit.go:57-76 — ❌ нет defer
func (rl *RateLimiter) Middleware() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := GetClientIP(r)
rl.mu.Lock()
limiter, ok := rl.visitors[ip]
if !ok {
limiter = rate.NewLimiter(rl.rate, rl.burst)
rl.visitors[ip] = limiter
}
rl.lastSeen[ip] = time.Now()
rl.mu.Unlock() // ❌ если паника в GetClientIP, NewLimiter или другой строке — deadlock
if !limiter.Allow() {
http.Error(w, `{"error":"rate limit exceeded"}`, 429)
return
}
next.ServeHTTP(w, r)
})
}
}
// ✅ defer гарантирует разблокировку даже при панике
func (rl *RateLimiter) Middleware() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := GetClientIP(r)
rl.mu.Lock()
defer rl.mu.Unlock() // ✅ всегда разблокируется
limiter, ok := rl.visitors[ip]
if !ok {
limiter = rate.NewLimiter(rl.rate, rl.burst)
rl.visitors[ip] = limiter
}
rl.lastSeen[ip] = time.Now()
if !limiter.Allow() {
http.Error(w, `{"error":"rate limit exceeded"}`, 429)
return
}
next.ServeHTTP(w, r)
})
}
}
Почему Go-сообщество требует defer: мьютекс должен быть разблокирован в любом сценарии — нормальное завершение, return, паника. defer гарантирует это.
Единственный случай, когда defer не обязателен — если между Lock и Unlock нет ни одного вызова функции, который мог бы запаниковать (чистые присваивания). Но это хрупкое предположение.
Code review PhotoPlaces 2026-06. ratelimit.go:61.
#golang #concurrency #mutex #panic #best-practice #bug