Какие нововведения, связанные с отменой контекста (cancellation), появились в пакете `context` в Go 1.21? Расскажите о новых функциях и их практической пользе.

Ответ

В Go 1.21 был добавлен механизм для указания и распространения причины (cause) отмены контекста. Ранее ctx.Err() мог вернуть только context.Canceled или context.DeadlineExceeded, не давая информации о том, почему произошла отмена.

Теперь можно передать конкретную ошибку, которая будет являться причиной отмены. Для этого были добавлены три новые функции:


  1. WithCancelCause(parent Context) (ctx Context, cancel func(cause error))

    Создает контекст, который можно отменить, передав причину в функцию cancel.



  2. WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)

    Аналог WithDeadline, но позволяет сразу указать причину, которая будет использована при истечении дедлайна.



  3. 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.")
        }
    }
}

Практическая польза:

  • Улучшенная отладка и логирование: Вместо общего сообщения об отмене можно логировать конкретную причину, что упрощает анализ проблем.
  • Гранулярная обработка ошибок: Появляется возможность по-разному реагировать на разные причины отмены. Например, отмена по таймауту может требовать повторной попытки, а отмена из-за ошибки валидации — немедленного прекращения.
  • Взаимодействие в распределенных системах: При отмене запроса в микросервисной архитектуре можно передать осмысленную ошибку через цепочку вызовов, а не просто сигнал отмены.