Можно ли получить доступ к значениям контекста go после отмены?

# #go

Вопрос:

Контексты обучения в go. Я попробовал отменить контекст, который пытается получить доступ к значению, заданному в контексте после отмены. К моему удивлению, это сработало:

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

func test(ctx context.Context, cancelFunc context.CancelFunc){
    intervalTicker := time.NewTicker(time.Second * 2).C
    expiryTicker := time.NewTicker(time.Second * 5).C
    for {
        select {
        case <-ctx.Done():
            fmt.Println(ctx.Err())
            return
        case <-intervalTicker:
            fmt.Println("interval")
        case <-expiryTicker:
            fmt.Println("expiry")
            func() {
                defer cancelFunc()
                fmt.Println("Calling context cancel")
            }()
            return
        }
    }
}

func main() {
    type key string
    var contextKey key
    parent := context.WithValue(context.TODO(), contextKey, "V1")
    ctx, cancelFunc := context.WithCancel(parent)
    test(ctx, cancelFunc)

    fmt.Println(ctx.Value(contextKey))
}
 

Поскольку я передаю один и тот же дочерний контекст и функцию отмены в тестовую функцию, я ожидал бы, что контекст будет отменен, а значение будет недоступно. Разве это не так?

Комментарии:

1. Вы доказали, что это не так — ваш вопрос отвечает сам за себя. Не могли бы вы уточнить, о чем вы спрашиваете?

2. @Адриан, я хотел бы уточнить. Для меня казалось естественным, что после отмены контекста значения контекста больше не существуют. Однако я согласен с приведенным ниже объяснением.

Ответ №1:

Из context.WithCancel документации

Канал выполнения возвращенного контекста закрывается при вызове возвращенной функции отмены или при закрытии канала выполнения родительского контекста, в зависимости от того, что произойдет раньше.

Из context.Context документации

Готово возвращает канал, который закрыт, когда работа, выполненная от имени этого контекста, должна быть отменена.

Отмена контекста не должна означать ничего вроде «уничтожить этот контекст» или «сделать этот контекст более непригодным для использования». Это делается исключительно для того, чтобы дать понять пользователям контекста, что работа должна быть отменена. Этот сигнал не является волшебным и должен быть явно проверен.

Рассмотрим эту ситуацию:

 select {
case <-ctx.Done():
    return
default:
    value := ctx.Value("something")
    doSomething(value)
}
 

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

 select {
case <-ctx.Done():
    return
default:
    // OH NO! Even though we just checked and it was ok,
    // some other goroutine called cancel() right at this moment!
    value := ctx.Value("something")
    // Now "value" is going to be invalid.
    doSomething(value)
}
 

Существующая модель контекста полезна, поскольку она позволяет рабочей программе проверять состояние контекста только на определенных контрольных точках, которые являются наиболее безопасными или удобными, и не беспокоиться об этом в других случаях.