Что такое `context propagation` и как этот механизм реализован в Go?

Ответ

Что такое Context Propagation?

Context Propagation (проброс или распространение контекста) — это паттерн передачи информации о запросе, его состоянии и сигналах управления (отмена, таймаут) по цепочке вызовов функций, особенно в асинхронных и распределенных системах.

В Go этот механизм реализован через стандартный пакет context и интерфейс context.Context.

Зачем он нужен?

Контекст решает три основные задачи:

  1. Отмена операций (Cancellation): Позволяет грациозно остановить цепочку горутин, если родительская операция была отменена (например, пользователь закрыл вкладку браузера).
  2. Контроль времени выполнения (Deadlines/Timeouts): Гарантирует, что операция не будет выполняться дольше заданного времени.
  3. Передача данных в рамках запроса (Request-scoped data): Позволяет передавать метаданные (ID запроса, токен аутентификации) вниз по стеку вызовов, не загрязняя сигнатуры функций.

Как это работает в Go?

  • context.Background(): Пустой корневой контекст, который используется как отправная точка для всех контекстов.
  • context.WithCancel(parent): Создает дочерний контекст, который можно отменить, вызвав возвращаемую функцию cancel().
  • context.WithTimeout(parent, duration): Создает дочерний контекст, который автоматически отменяется по истечении таймаута.
  • context.WithValue(parent, key, value): Добавляет в контекст данные типа ключ-значение.

Пример:

// Создаем контекст с таймаутом в 2 секунды
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // Важно вызывать cancel, чтобы освободить ресурсы

// Передаем контекст в долгую операцию
result, err := longOperation(ctx)
if err != nil {
    // Ошибка может быть вызвана таймаутом
    if errors.Is(err, context.DeadlineExceeded) {
        fmt.Println("Операция отменена по таймауту")
    }
}

func longOperation(ctx context.Context) (string, error) {
    select {
    case <-time.After(3 * time.Second): // Имитация долгой работы
        return "Результат", nil
    case <-ctx.Done(): // Канал Done() закрывается при отмене или таймауте
        return "", ctx.Err() // Возвращаем ошибку контекста
    }
}

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

  • Иммутабельность: Контекст неизменяем. Функции WithCancel, WithTimeout и WithValue возвращают новый, дочерний контекст.
  • Явная передача: Контекст всегда передается как первый аргумент функции: func DoSomething(ctx context.Context, ...).
  • Не для бизнес-логики: Не используйте context.WithValue для передачи обязательных параметров. Он предназначен для сквозных, request-scoped данных.