atomic-mutex-defer-panic.md 3.0 KB

Mutex без defer — deadlock при панике

Контекст: В 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 нет ни одного вызова функции, который мог бы запаниковать (чистые присваивания). Но это хрупкое предположение.

Связанные заметки

  • [[backend-rate-limiting]] — где находится проблемный код
  • [[MOC-backend-patterns]] — таблица антипаттернов

Источник

Code review PhotoPlaces 2026-06. ratelimit.go:61.

Теги

#golang #concurrency #mutex #panic #best-practice #bug