Ответ
Начиная с Go 1.20, появилась стандартная возможность узнать причину отмены контекста. Для этого используется функция context.Cause(ctx).
Основная идея:
- При создании контекста, который может быть отменен с указанием причины, используется
context.WithCancelCause(parent). - Эта функция возвращает
cancelфункцию типаfunc(cause error). При вызове этой функции вы передаете ошибку, которая и является причиной отмены. - В горутине, которая работает с этим контекстом, после получения сигнала
<-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) на тебе, разбирайся.
Суть, блядь, в чём:
- Берёшь и создаёшь контекст не просто так, а с возможностью вписать причину. Используешь
context.WithCancelCause(parent). Это же не простоcancel(), аcancel(cause error)— туда ошибку можно запихнуть, как записку. - В горутине, которая с этим контекстом работает, после того как получила по мозгам (
<-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, блядь, приходилось извращаться, как умели: обёртки писать, свои каналы для ошибок городить — пиздец, а не жизнь. Теперь-то красота!