Как узнать причину отмены 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 стандартного способа передать причину не было. Разработчикам приходилось создавать собственные обертки над контекстом или использовать сторонние каналы для передачи информации об ошибке.

Ответ 18+ 🔞

Ах ты ж, ёпта, смотри-ка, какие штуки придумали в Го! С версии 1.20, блядь, можно наконец-то узнать, кто конкретно виноват, что контекст отменили. Раньше-то просто сигнал прилетал — типа, всё, пиздец, конец, а почему — нихуя не понятно. А теперь — пожалуйста, context.Cause(ctx) на тебе, разбирайся.

Суть, блядь, в чём:

  1. Берёшь и создаёшь контекст не просто так, а с возможностью вписать причину. Используешь context.WithCancelCause(parent). Это же не просто cancel(), а cancel(cause error) — туда ошибку можно запихнуть, как записку.
  2. В горутине, которая с этим контекстом работает, после того как получила по мозгам (<-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, если контекст ещё жив и здоров, как бык.
  • context.Canceled, если его отменили старым добрым способом, без причины, или тупо cancel(nil) вызвали.
  • context.DeadlineExceeded, если время вышло, дедлайн наступил — тут всё ясно, сам виноват, просрал время.
  • А вот если там твоя, пользовательская, ошибка — значит кто-то специально её туда и засунул, гадёныш, через cancel(err).

А до 1.20, блядь, приходилось извращаться, как умели: обёртки писать, свои каналы для ошибок городить — пиздец, а не жизнь. Теперь-то красота!