Как узнать причину отмены context.Context?

Ответ

Начиная с Go 1.20, появилась стандартная возможность узнать причину отмены контекста. Для этого используется функция context.Cause(ctx).

Основная идея:

  1. При создании контекста, который может быть отменен с указанием причины, используется context.WithCancelCause(parent).
  2. Эта функция возвращает cancel функцию типа func(cause error). При вызове этой функции вы передаете ошибку, которая и является причиной отмены.
  3. В горутине, которая работает с этим контекстом, после получения сигнала <-ctx.Done(), можно вызвать context.Cause(ctx) для получения этой ошибки.

Пример:

package main

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

func main() {
    // Создаем контекст с возможностью указания причины отмены
    ctx, cancel := context.WithCancelCause(context.Background())

    // Запускаем горутину, которая будет отменена
    go func(ctx context.Context) {
        select {
        case <-ctx.Done():
            // Контекст отменен, узнаем причину
            err := context.Cause(ctx)
            fmt.Printf("Работа прервана, причина: %vn", err)
        }
    }(ctx)

    time.Sleep(100 * time.Millisecond)

    // Отменяем контекст, передавая конкретную ошибку
    cancel(fmt.Errorf("операция больше не нужна"))

    time.Sleep(100 * time.Millisecond) // Даем время на вывод
}
// Вывод: Работа прервана, причина: операция больше не нужна

Возможные значения context.Cause(ctx):

  • nil, если контекст еще не отменен (ctx.Err() == nil).
  • context.Canceled, если контекст был отменен через cancel() без указания причины (например, из context.WithCancel()) или cancel(nil).
  • context.DeadlineExceeded, если контекст был отменен по таймауту (WithTimeout) или дедлайну (WithDeadline).
  • Пользовательская ошибка (error), если контекст был отменен вызовом cancel(err) с не-nil ошибкой.

До Go 1.20 стандартного способа передать причину не было. Разработчикам приходилось создавать собственные обертки над контекстом или использовать сторонние каналы для передачи информации об ошибке.