Как в Go проверить, что контекст (context) был отменен?

Ответ

В Go проверка отмены контекста является ключевой частью написания корректного конкурентного кода. Существует несколько способов это сделать.

1. Блокирующая проверка через select (основной способ)

Самый идиоматичный способ — использовать select для ожидания сигнала из канала ctx.Done(). Этот канал закрывается, когда контекст отменяется.

func operation(ctx context.Context) {
    select {
    case <-ctx.Done():
        // Контекст был отменен, нужно завершить работу.
        // ctx.Err() вернет причину отмены.
        fmt.Println("Operation canceled:", ctx.Err()) // -> "context canceled" или "context deadline exceeded"
        return
    case <-time.After(2 * time.Second): // Имитация долгой работы
        fmt.Println("Operation completed successfully")
    }
}

func main() {
    // Контекст, который отменится через 1 секунду
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel() // Хорошая практика - вызывать cancel, даже если контекст с таймаутом

    operation(ctx)
}

2. Неблокирующая проверка

Если вам нужно просто проверить статус контекста, не блокируя выполнение, можно использовать select с default веткой.

func isContextDone(ctx context.Context) bool {
    select {
    case <-ctx.Done():
        return true
    default:
        return false
    }
}

3. Проверка ошибки ctx.Err()

Метод ctx.Err() напрямую возвращает причину отмены.

  • Он вернет nil, если контекст еще не отменен.
  • Он вернет context.Canceled, если была вызвана функция cancel().
  • Он вернет context.DeadlineExceeded, если истек таймаут или дедлайн.

Этот метод полезен для проверки состояния контекста после того, как какая-то блокирующая операция завершилась с ошибкой.

// ... какая-то операция завершилась с ошибкой `err` ...
if err != nil {
    // Проверяем, не была ли ошибка вызвана отменой контекста
    if ctx.Err() == context.Canceled {
        log.Println("Operation failed because context was canceled by caller")
    } else if ctx.Err() == context.DeadlineExceeded {
        log.Println("Operation failed because it hit the deadline")
    }
}