Ответ
В Go 1.21 был добавлен механизм для указания и распространения причины (cause) отмены контекста. Ранее ctx.Err() мог вернуть только context.Canceled или context.DeadlineExceeded, не давая информации о том, почему произошла отмена.
Теперь можно передать конкретную ошибку, которая будет являться причиной отмены. Для этого были добавлены три новые функции:
-
WithCancelCause(parent Context) (ctx Context, cancel func(cause error))Создает контекст, который можно отменить, передав причину в функциюcancel. -
WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)АналогWithDeadline, но позволяет сразу указать причину, которая будет использована при истечении дедлайна. -
WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)АналогWithTimeout, но с возможностью указать причину для таймаута.
Для получения этой причины используется новая функция context.Cause(ctx) error.
Пример:
package main
import (
"context"
"errors"
"fmt"
"time"
)
func main() {
// Задаем кастомную ошибку как причину
processingErr := errors.New("processing took too long")
// Создаем контекст с таймаутом и причиной
ctx, cancel := context.WithTimeoutCause(
context.Background(),
time.Second,
processingErr,
)
defer cancel()
// Имитируем операцию, которая длится дольше таймаута
select {
case <-time.After(2 * time.Second):
fmt.Println("operation completed successfully")
case <-ctx.Done():
// ctx.Err() вернет стандартную ошибку
fmt.Printf("Context done. Error: %vn", ctx.Err()) // -> context deadline exceeded
// context.Cause() вернет нашу кастомную ошибку
cause := context.Cause(ctx)
fmt.Printf("Cause of cancellation: %vn", cause) // -> processing took too long
// Мы можем проверить, что причина - это именно наша ошибка
if errors.Is(cause, processingErr) {
fmt.Println("The cause is the expected processing error.")
}
}
}
Практическая польза:
- Улучшенная отладка и логирование: Вместо общего сообщения об отмене можно логировать конкретную причину, что упрощает анализ проблем.
- Гранулярная обработка ошибок: Появляется возможность по-разному реагировать на разные причины отмены. Например, отмена по таймауту может требовать повторной попытки, а отмена из-за ошибки валидации — немедленного прекращения.
- Взаимодействие в распределенных системах: При отмене запроса в микросервисной архитектуре можно передать осмысленную ошибку через цепочку вызовов, а не просто сигнал отмены.
Ответ 18+ 🔞
Да ты посмотри, что эти ребята из Go придумали! В версии 1.21, блядь, подвезли фичу, про которую все давно орали. Раньше контекст, сука, отменялся, и всё, что ты мог выцепить — это context.Canceled или context.DeadlineExceeded. А хули случилось-то? Почему отмена? Кто виноват? Пиздец, темнота, как в жопе у крота!
А теперь, ёпта, можно прицепить к отмене причину, конкретную ошибку! Наконец-то, блядь, свет в конце тоннеля! Добавили три новые функции, смотри:
-
WithCancelCause(parent Context) (ctx Context, cancel func(cause error))Создаёт контекст, который можно отменить, и в функциюcancelтеперь можно впихнуть причину, типаcancel(errors.New("пошёл нахуй, сервер сгорел")). -
WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)То же самое, чтоWithDeadline, но сразу говоришь, какую ошибку вернёт, когда время выйдет. Не "дедлайн превышен", а "дедлайн превышен, потому что ты, мудила, в БД 10 секунд запрос гонял". -
WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)Ну это, ясень пень, аналогWithTimeout, но тоже с причиной.
А чтобы эту причину потом выковырять, есть новая функция context.Cause(ctx) error. Вот так вот, блядь, элегантно!
Смотри, как это в деле, на живом примере:
package main
import (
"context"
"errors"
"fmt"
"time"
)
func main() {
// Придумываем свою, душевную ошибку-причину
processingErr := errors.New("processing took too long")
// Создаём контекст с таймаутом и сразу говорим, по какой причине он сработает
ctx, cancel := context.WithTimeoutCause(
context.Background(),
time.Second,
processingErr, // Вот она, родная!
)
defer cancel()
// Допустим, у нас тут операция, которая зависла на два часа
select {
case <-time.After(2 * time.Second):
fmt.Println("operation completed successfully")
case <-ctx.Done():
// Старый добрый ctx.Err() вернёт стандартное говно
fmt.Printf("Context done. Error: %vn", ctx.Err()) // -> context deadline exceeded
// А вот новый красавчик context.Cause() докопается до сути!
cause := context.Cause(ctx)
fmt.Printf("Cause of cancellation: %vn", cause) // -> processing took too long
// И мы можем проверить, что это именно наша ошибка, а не какая-то левая!
if errors.Is(cause, processingErr) {
fmt.Println("The cause is the expected processing error.")
}
}
}
И зачем это всё, спросишь?
- Логирование и отладка: Теперь в логах будет не просто "ой, всё", а конкретно "ой, всё, потому что сеть легла". Красота, блядь!
- Умная обработка: Можно по-разному реагировать на разные причины. Отмена из-за таймаута — перезапросик сделать. Отмена потому что пользователь нахуй послал — всё, закругляемся.
- Микросервисы: В цепочке вызовов можно протащить внятную ошибку через все контексты, а не просто сухой сигнал отмены. Все будут в курсе, кто тут, блядь, виноват.
В общем, фича — огонь. Наконец-то контексты заговорили человеческим языком, а не мычанием!