Перейти к содержанию

Goroutine Best Practices Guide

ЦЕЛЬ ДОКУМЕНТА

Этот guide содержит proven практики для безопасного использования goroutines в проекте Saga, основанные на реальном аудите кодовой базы и выявленных safe patterns.

ПРОВЕРЕННЫЕ SAFE PATTERNS

1. 🔄 WaitGroup Coordination Pattern (РЕКОМЕНДУЕТСЯ)

Использование: Parallel execution с known number of goroutines

// ✅ ПРАВИЛЬНО - используется в tests/performance/load/boundary/test.go
func processParallelOperations(operations []Operation) {
    var wg sync.WaitGroup
    results := make(chan Result, len(operations))

    for i, op := range operations {
        wg.Add(1)
        go func(index int, operation Operation) {
            defer wg.Done() // ОБЯЗАТЕЛЬНО в начале

            result := operation.Execute()
            results <- result
        }(i, op)
    }

    wg.Wait()
    close(results)
}

Ключевые принципы:

  • wg.Add(1) ДО горутины
  • defer wg.Done() ПЕРВОЕ в горутине
  • wg.Wait() блокирует до завершения всех
  • ✅ Buffered channel с правильным размером

2. ⏰ Context-Aware Timeout Pattern (PRODUCTION)

Использование: Long-running operations с timeout protection

// ✅ ПРАВИЛЬНО - используется в blockchain/service/deposit_service.go
func scanBlockchainHistory(ctx context.Context, fromBlock, toBlock uint64) error {
    go func() {
        scanCtx, scanCancel := context.WithTimeout(context.Background(), 30*time.Minute)
        defer scanCancel() // ОБЯЗАТЕЛЬНО для cleanup

        defer func() {
            // State cleanup после завершения
            updateScanProgress(false, "Сканирование завершено")
        }()

        err := performActualScan(scanCtx, fromBlock, toBlock)
        if err != nil {
            logError("scan failed", err)
        }
    }()

    return nil
}

Ключевые принципы:

  • context.WithTimeout() для protection от зависания
  • defer cancel() для cleanup context
  • ✅ Multiple defer blocks для proper state management
  • ✅ Error handling внутри горутины

3. 📡 Channel Communication Pattern (TESTS)

Использование: Результаты из concurrent operations

// ✅ ПРАВИЛЬНО - используется в tests/integration/error/scenarios/test.go
func concurrentRequests(requestCount int) []Result {
    results := make(chan Result, requestCount) // Buffered!

    for i := 0; i < requestCount; i++ {
        go func(requestIndex int) {
            result := performRequest(requestIndex)
            results <- result // НЕ блокируется благодаря buffer
        }(i)
    }

    // Собираем результаты
    var allResults []Result
    for i := 0; i < requestCount; i++ {
        allResults = append(allResults, <-results)
    }

    return allResults
}

Ключевые принципы:

  • ✅ Buffered channel размером = количеству горутин
  • ✅ Finite execution - горутины завершаются естественно
  • ✅ Результаты всегда читаются из канала

4. 🛡️ Defensive I/O Pattern (DATABASE)

Использование: Защита от зависания I/O операций

// ✅ ПРАВИЛЬНО - используется в shared/testing/real_database_manager.go
func closeWithTimeout(resource io.Closer) error {
    done := make(chan error, 1) // Buffered для non-blocking

    go func() {
        done <- resource.Close() // Может зависнуть
    }()

    select {
    case err := <-done:
        return err
    case <-time.After(10 * time.Second):
        return fmt.Errorf("resource close timeout")
    }
}

Ключевые принципы:

  • ✅ Buffered channel size 1 для non-blocking
  • select + time.After() для timeout
  • ✅ Горутина завершается независимо от timeout

5. 🏥 Health Check Pattern (SERVICES)

Использование: Parallel health checks в production

// ✅ ПРАВИЛЬНО - используется в shared/services/unified/health_service.go
func runHealthChecks(checkers []HealthChecker) []HealthResult {
    var wg sync.WaitGroup
    results := make(chan HealthResult, len(checkers))

    for _, checker := range checkers {
        wg.Add(1)
        go func(c HealthChecker) {
            defer wg.Done()
            results <- c.Check(ctx) // Context с timeout
        }(checker)
    }

    // Closer горутина
    go func() {
        wg.Wait()
        close(results) // Закрываем только после всех
    }()

    // Собираем результаты
    var allResults []HealthResult
    for result := range results { // range останавливается при close
        allResults = append(allResults, result)
    }

    return allResults
}

Ключевые принципы:

  • ✅ Separate "closer" горутина для channel management
  • ✅ Channel закрывается ТОЛЬКО после wg.Wait()
  • range автоматически stops при close channel

ОПАСНЫЕ ANTI-PATTERNS (ИЗБЕГАТЬ)

1. 🚫 Infinite Loop Without Context

// ❌ ОПАСНО - может leak навсегда
go func() {
    for {
        doWork() // НЕТ способа остановить
        time.Sleep(time.Second)
    }
}()

❌ Проблемы:

  • Нет способа остановить горутину
  • Memory leak при shutdown
  • Blocked resources

✅ ИСПРАВЛЕНИЕ:

go func() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return // Graceful shutdown
        case <-ticker.C:
            doWork()
        }
    }
}()

2. 🚫 Blocking Channel Operations

// ❌ ОПАСНО - может hang навсегда
ch := make(chan Data) // Unbuffered!
go func() {
    ch <- data // Блокируется если нет receiver
}()

❌ Проблемы:

  • Горутина может зависнуть навсегда
  • Sender блокируется без receiver

✅ ИСПРАВЛЕНИЕ:

ch := make(chan Data, 1) // Buffered
go func() {
    select {
    case ch <- data:
        // Success
    case <-time.After(5 * time.Second):
        // Timeout handling
    }
}()

3. 🚫 Resource Leaks

// ❌ ОПАСНО - resource не закрывается
go func() {
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        return // LEAK! conn не закрыт
    }

    // Работа с connection
    // НЕТ cleanup при panic или error
}()

❌ Проблемы:

  • Connection leak при early return
  • Нет cleanup при panic
  • Resource exhaustion

✅ ИСПРАВЛЕНИЕ:

go func() {
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        return
    }
    defer conn.Close() // ОБЯЗАТЕЛЬНО cleanup

    // Безопасная работа с connection
}()

CHECKLIST ДЛЯ CODE REVIEW

Перед созданием новой горутины проверь:

Lifecycle Management:

  • Termination strategy - как горутина завершается?
  • Context cancellation - можно ли остановить извне?
  • Timeout protection - есть ли защита от зависания?
  • Resource cleanup - освобождаются ли ресурсы?

Communication:

  • Channel sizing - buffered channels правильного размера?
  • Channel closure - кто и когда закрывает каналы?
  • Synchronization - используется ли WaitGroup для coordination?
  • Error handling - как передаются ошибки?

Testing:

  • Test isolation - не влияют ли горутины на другие тесты?
  • Deterministic behavior - предсказуемы ли результаты?
  • Cleanup in tests - есть ли teardown для горутин?

Monitoring:

  • Logging - логируются ли start/stop events?
  • Metrics - отслеживается ли количество горутин?
  • Debug information - достаточно ли данных для troubleshooting?

🏭 PRODUCTION MONITORING

Встроенная система мониторинга

В cmd/saga/main.go реализована автоматическая система мониторинга:

// Production-ready мониторинг в main.go
func startGoroutineMonitoring(cfg config.ConfigInterface) {
    threshold := 1000 // Настраиваемый порог
    interval := 30 * time.Second

    go func() {
        ticker := time.NewTicker(interval)
        defer ticker.Stop()

        for range ticker.C {
            count := runtime.NumGoroutine()

            // Regular monitoring
            sagaServerLogger.InfoStructured("Goroutine monitoring",
                "count", count, "threshold", threshold)

            // Alert на превышение
            if count > threshold {
                sagaServerLogger.WarnStructured("High goroutine count detected",
                    "count", count, "threshold", threshold)
            }
        }
    }()
}

Интерпретация метрик

🟢 Нормальные значения:

  • Startup: 5-20 горутин
  • Idle: 10-50 горутин
  • Load: 50-500 горутин

⚠️ Предупреждающие значения:

  • 500-1000: Высокая нагрузка, мониторить
  • 1000+: Возможный leak, требует investigation

🚨 Критические значения:

  • 5000+: Серьезный leak, immediate action required
  • Growing trend: Постоянный рост = guaranteed leak

🛠️ DEBUGGING TOOLS

Runtime diagnostics

# Проверить количество горутин в runtime
go tool pprof http://localhost:6060/debug/pprof/goroutine

# Stack traces всех горутин
curl http://localhost:6060/debug/pprof/goroutine?debug=1

# Goroutine blocking profile
go tool pprof http://localhost:6060/debug/pprof/block

Code analysis

# Найти все горутины в коде
grep -rn 'go func(' ./backend/ --include="*.go"

# Потенциально опасные patterns
grep -rn 'go func()' ./backend/ --include="*.go"  # Без параметров
grep -rn 'for.*{' ./backend/ -A 10 | grep 'go func'  # В циклах

ДОПОЛНИТЕЛЬНЫЕ РЕСУРСЫ

Go Documentation

📖 Best Practices

Static Analysis Tools

  • go vet - встроенный analyzer
  • golangci-lint - comprehensive linting
  • deadcode - dead code detection

🏆 ЗАКЛЮЧЕНИЕ

Команда Saga демонстрирует excellent knowledge Go concurrency patterns. Все найденные горутины в production коде используют safe practices и не представляют риска memory leaks.

Ключевые принципы успеха:

  1. Consistent patterns - команда использует проверенные решения
  2. Context awareness - timeout protection где нужно
  3. Resource cleanup - proper defer blocks
  4. Test isolation - горутины в тестах безопасны

Следуй этому guide для всех новых горутин!


Связанные документы

  • Backend Architecture: Backend Architecture - понимание архитектуры где используются эти паттерны
  • Testing Guide: Testing Documentation - тестирование concurrent кода
  • Performance Monitoring: Monitoring System - production мониторинг горутин
  • Go 2025 Patterns: CLAUDE.md раздел "⚡ GO 2025 МОДЕРНИЗАЦИЯ" - современные практики Go включая concurrency

Документ основан на real audit данных проекта Saga, проведенном 2025-09-22 Все примеры кода взяты из актуальной кодовой базы и проверены на production readiness


📋 Метаданные

Версия: 2.4.82

Обновлено: 2025-10-21

Статус: Published