Context: назначение и propagation
context.Context неизменно несёт три вещи: сигнал отмены через Done(), время окончания через Deadline() и request-scoped значения через Value(). Производный context наследует отмену, дедлайн и values родителя. Это дерево направлено от владельца операции к дочерним операциям; потомок не может отменить родителя.
Зачем это на интервью
В production один HTTP-запрос часто вызывает cache, БД и несколько RPC. Интервьюер ждёт, что кандидат передаст один контекст через весь путь, не потеряет client cancellation и не запустит работу, которая переживёт её владельца случайно.
Минимум для E4
- Принимать
ctx context.Contextпервым параметром, неnil; если API не имеет смысла без контекста, документировать это. - Передавать полученный
ctxдальше, а не заменять его наBackground/TODO. - Использовать
context.Background()только как root процесса/явно независимой операции,TODO()— временно при миграции. - Проверять cancellation в CPU-bound цикле и при отправке/получении channel.
- Передавать
r.Context()в код HTTP handler и контекст RPC в downstream API.
Углубление для E5/Senior
Propagation — часть контракта границы. Transport извлекает trace/auth metadata из входящего запроса по доверенным правилам, service передаёт ctx явно, а adapter использует API с суффиксом Context (QueryContext, Do, gRPC call). Для detached delivery E5 создаёт новую durable job с явными полями и новым process context, а не сохраняет request context после возврата handler.
Ключевые понятия
| Элемент | Значение | Правило |
|---|---|---|
| Root context | Background, неотменяемый корень | Создаёт main, тест или явная long-lived задача |
| Derived context | Потомок WithCancel/Timeout/... | Его creator вызывает cancel |
Done() | Channel, закрываемый при отмене | Наблюдают через select; не закрывают вручную |
Err() | Canceled или DeadlineExceeded после Done | Удобен для классификации результата |
| Propagation | Наследование вниз по дереву | Не заменяйте контекст между слоями |
Cooperative cancellation
package jobs
import (
"context"
"fmt"
)
func Sum(ctx context.Context, values []int) (int, error) {
total := 0
for _, v := range values {
select {
case <-ctx.Done():
return 0, fmt.Errorf("sum canceled: %w", ctx.Err())
default:
}
total += v
}
return total, nil
}default делает проверку неблокирующей. Для работы, которая ждёт result channel, cancellation должна быть отдельной веткой select; иначе worker может завершиться, но caller зависнет в receive. Не создавайте goroutine только ради проверки Done: это добавляет lifetime, который надо завершать.
Граница HTTP
package api
import (
"context"
"encoding/json"
"net/http"
)
type Finder interface {
Find(ctx context.Context, id string) (any, error)
}
func GetUser(f Finder) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, err := f.Find(r.Context(), r.PathValue("id"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(user)
}
}r.Context() отменяется, когда client connection закрыта, запрос отменён HTTP/2 или handler вернулся. Не запускайте из handler goroutine с этим ctx, если она должна жить дольше ответа: передайте в очередь данные задания и обработайте отдельно.
Типовые вопросы
- Почему
Contextпередают параметром, а не полем struct?- Его lifetime относится к конкретной операции. Поле делает переиспользование объекта опасным и скрывает, какой запрос владеет работой.
- Можно ли передать
nil?- Нет: API
contextтребует non-nil; вызывающий используетcontext.TODO()/Background()на нужной границе.
- Нет: API
- Отменяет ли child parent?
- Нет. Отмена родителя распространяется вниз; child управляет только своим поддеревом.
- Что будет, если handler вернул response?
- Его
r.Context()отменяется; downstream работа, которая всё ещё использует его, должна завершиться.
- Его
- Как отменить CPU-bound функцию?
- Добавить регулярные cooperative checks
ctx.Done()на разумной гранулярности; отмена не прерывает произвольную инструкцию.
- Добавить регулярные cooperative checks
- Почему нельзя сохранять
ctxдля retry завтра?- Его deadline/cancel и values принадлежат исходному запросу; сохраняют сериализованные данные job, не context.
Практика
- Добавьте
ctxв цепочку handler → use case → repository.- Критерии приёмки: тест отменяет root context и repository получает отмену; ни один слой не создаёт
Background; ошибки обёрнуты с операцией.
- Критерии приёмки: тест отменяет root context и repository получает отмену; ни один слой не создаёт
- Напишите pipeline generator → worker → sink.
- Критерии приёмки: каждый send/receive выбирает
ctx.Done; остановка consumer завершает producer; goroutine count стабилен в повторном тесте.
- Критерии приёмки: каждый send/receive выбирает
Частые ошибки и ловушки
- Принимать context не первым аргументом или прятать его в options struct.
- Использовать
Backgroundв repository «чтобы запрос точно записался»: это меняет business guarantee без решения владельца. - Полагать, что cancel прерывает third-party библиотеку, которая не принимает
ctx. - Передавать входящий context в async job после ответа.
Связанные темы
Context и жизненный цикл · Отмена и дедлайны · Значения Context · HTTP и веб
Источники
- Пакет
context— правила передачи и API. - Effective Go: Contexts — rationale и tree cancellation.
- Go
net/http.Request.Context— lifecycle request context. - Go
database/sqlContext — context-aware запросы к БД.