Расскажите о `context.WithTimeout`: назначение, пример использования и важные нюансы.

Ответ

Пакет context в Go предназначен для управления отменой операций, передачей данных в рамках запроса и установкой дедлайнов. Функция context.WithTimeout — это один из ключевых инструментов для контроля времени выполнения операций.

Назначение:
context.WithTimeout создает дочерний контекст, который автоматически отменяется по истечении заданного таймаута. Это критически важно для предотвращения "зависания" горутин при выполнении долгих операций, таких как:

  • Сетевые запросы (HTTP, gRPC)
  • Обращения к базе данных
  • Любые другие блокирующие вызовы

Пример использования:

package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // Создаем родительский контекст
    parentCtx := context.Background()
    // Создаем дочерний контекст с таймаутом в 50 миллисекунд
    ctx, cancel := context.WithTimeout(parentCtx, 50*time.Millisecond)
    // defer cancel() гарантирует, что ресурсы контекста будут освобождены,
    // даже если операция завершится до истечения таймаута.
    defer cancel()

    select {
    case <-time.After(100 * time.Millisecond): // Эта операция заведомо дольше таймаута
        fmt.Println("Операция успешно завершена (не должно произойти)")
    case <-ctx.Done(): // Канал Done() закрывается при отмене контекста
        // ctx.Err() объясняет причину отмены
        fmt.Println("Операция прервана по таймауту:", ctx.Err()) // Выведет: context deadline exceeded
    }
}

Ключевые практики и нюансы:

  1. Обязательный вызов cancel(): Всегда вызывайте функцию cancel, возвращаемую context.WithTimeout, чтобы освободить связанные с контекстом ресурсы. Лучшая практика — использовать defer cancel() сразу после создания контекста.
  2. Проверка ctx.Err(): После получения сигнала из ctx.Done(), проверьте ctx.Err(), чтобы узнать причину отмены. Для WithTimeout это будет context.DeadlineExceeded.
  3. Передача контекста: Контекст следует передавать как первый аргумент в функции, которые могут быть долго выполняющимися. Это общепринятое соглашение в Go.
  4. Иерархия контекстов: Отмена родительского контекста каскадно отменяет все его дочерние контексты. Таймаут дочернего контекста не может превышать дедлайн родительского.