Ответ
Что такое Context Propagation?
Context Propagation (проброс или распространение контекста) — это паттерн передачи информации о запросе, его состоянии и сигналах управления (отмена, таймаут) по цепочке вызовов функций, особенно в асинхронных и распределенных системах.
В Go этот механизм реализован через стандартный пакет context и интерфейс context.Context.
Зачем он нужен?
Контекст решает три основные задачи:
- Отмена операций (Cancellation): Позволяет грациозно остановить цепочку горутин, если родительская операция была отменена (например, пользователь закрыл вкладку браузера).
- Контроль времени выполнения (Deadlines/Timeouts): Гарантирует, что операция не будет выполняться дольше заданного времени.
- Передача данных в рамках запроса (Request-scoped data): Позволяет передавать метаданные (ID запроса, токен аутентификации) вниз по стеку вызовов, не загрязняя сигнатуры функций.
Как это работает в Go?
context.Background(): Пустой корневой контекст, который используется как отправная точка для всех контекстов.context.WithCancel(parent): Создает дочерний контекст, который можно отменить, вызвав возвращаемую функциюcancel().context.WithTimeout(parent, duration): Создает дочерний контекст, который автоматически отменяется по истечении таймаута.context.WithValue(parent, key, value): Добавляет в контекст данные типа ключ-значение.
Пример:
// Создаем контекст с таймаутом в 2 секунды
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // Важно вызывать cancel, чтобы освободить ресурсы
// Передаем контекст в долгую операцию
result, err := longOperation(ctx)
if err != nil {
// Ошибка может быть вызвана таймаутом
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("Операция отменена по таймауту")
}
}
func longOperation(ctx context.Context) (string, error) {
select {
case <-time.After(3 * time.Second): // Имитация долгой работы
return "Результат", nil
case <-ctx.Done(): // Канал Done() закрывается при отмене или таймауте
return "", ctx.Err() // Возвращаем ошибку контекста
}
}
Ключевые принципы:
- Иммутабельность: Контекст неизменяем. Функции
WithCancel,WithTimeoutиWithValueвозвращают новый, дочерний контекст. - Явная передача: Контекст всегда передается как первый аргумент функции:
func DoSomething(ctx context.Context, ...). - Не для бизнес-логики: Не используйте
context.WithValueдля передачи обязательных параметров. Он предназначен для сквозных, request-scoped данных.
Ответ 18+ 🔞
А, контекст! Ну это ж классика, блядь, просто пиздец! Сейчас объясню на пальцах, а то некоторые, как мартышлюшки, в коде его суют куда попало, а потом охуевают, почему всё падает.
Смотри, представь себе: ты в баре заказываешь пиво и чипсы. Это твой запрос. А контекст — это, сука, такая записка от бармена официантке: «Этому лысому мудаку за столиком №3 — пиво «Балтика», чипсы со вкусом краба, и если он начнёт скандалить про политику — сразу вышвыривай нахуй, отмена».
Вот эта записка и есть context.Context. Она бегает по всей твоей программе, от функции к функции, и несёт три священных знания:
- Когда всё кончилось (Cancellation). Официантка (твоя горутина) слушает, не крикнул ли бармен «ВСЁ, ЗАКРЫВАЕМСЯ!». Крикнул — она сразу бросает поднос и идёт домой. Не ждёт, пока пиво станет тёплым. Это
ctx.Done(). - Сколько времени ждать (Deadline/Timeout). В записке приписка: «Если через 5 минут не допивает — выгоняй». Это
context.WithTimeout. Время вышло — официантка выкидывает твоё недопитое пиво в раковину и говорит «Извини, чувак, таймаут». - Что именно нести (Request-scoped data). В записке ещё написано: «Он у нас VIP, дай ему соломинку золотую». Это
context.WithValue. Любая функция в цепочке (официантка, повар, мойщик стаканов) может глянуть в записку и сказать: «А, так это тот самый лысый VIP! Надо золотую соломинку».
А теперь, блядь, главные правила, которые нарушают все распиздяи:
Правило первое, железобетонное: Контекст — он как девственность. Его нельзя изменить, блядь! Ты можешь только взять существующий (parent) и породить от него нового, улучшенного ребёнка с дополнительными условиями.
// Это твой папа-контекст, пустой и чистый
ctx := context.Background()
// А это его сынок, но с таймером на 2 секунды
ctxWithTimeout, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel() // И не забудь потом пристрелить сыночка, когда он не нужен!
Правило второе, как удар вилкой в глаз: Контекст всегда, блядь, ВСЕГДА идёт ПЕРВЫМ аргументом в функцию. Это как пароль «Мир-дружба-жвачка». Без него — не пущат.
// Правильно, как у взрослых:
func doSomeMagic(ctx context.Context, userId int) error {...}
// А так пишут полупидоры, которых потом увольняют:
func doSomeMagic(userId int, ctx context.Context) error {...} // ПИЗДЕЦ КОД
Правило третье, про данные: Не суй в контекст всё подряд, ёпта! Это не глобальный мешок для говна. Только для того, что действительно относится ко всему запросу: ID запроса, токен, метрики. Нельзя туда пихать database connection или, блядь, структуру пользователя. Это пиздопроебищно.
// Допустимо, если очень надо:
type ctxKey string
var requestIDKey ctxKey = "request_id"
ctx := context.WithValue(parentCtx, requestIDKey, "abc-123")
// А вот это — манда с ушами, не делай так:
ctx := context.WithValue(parentCtx, "entire_database_pool", dbPool) // ИДИ НАХУЙ
Вот смотри, живой пример, почему это охуенно:
func main() {
// Даём запросу ровно 2 секунды на жизнь
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel() // Чистим за собой, мы же не свиньи
// Кидаем контекст в чёрную дыру
result, err := callBlackHole(ctx)
if err != nil {
// Ошибка может быть потому, что таймаут сработал!
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("Бля, чёрная дыра думала дольше 2 секунд! Отмена!")
}
}
}
func callBlackHole(ctx context.Context) (string, error) {
// Она думает ровно 3 секунды
select {
case <-time.After(3 * time.Second):
return "Ответ от сингулярности", nil
case <-ctx.Done(): // А вот тут слушаем контекст!
// Если сработал таймаут или отмена, мы сваливаем отсюда
return "", ctx.Err() // Возвращаем честную причину: "дедлайн проёбан"
}
}
И что мы получим? Через 2 секунды программа скажет «Бля, таймаут!» и не будет тупо ждать третьей секунды от чёрной дыры. Всё грациозно отменится, ресурсы освободятся. Красота, блядь!
Короче, запомни: контекст — это не просто какая-то хуйня, это система управления твоим асинхронным бардаком. Используй с умом, не выёбывайся с WithValue, и будет тебе счастье. А если забудешь defer cancel() — готовься к утечкам горутин, они тебя сожрут, как сабаки сука.