Как использовать контекст для ограничения времени ожидания в Go
Что проверяют
Умеете ли вы не просто вызвать context.WithTimeout, а протянуть контекст через весь путь запроса так, чтобы отмена реально останавливала работу. Следом обычно просят объяснить, что будет без cancel, и как отличить таймаут от отмены клиентом.
Как отвечать
- создание:
ctx, cancel := context.WithTimeout(parent, 2*time.Second)и сразуdefer cancel().WithDeadlineделает то же с абсолютным временем. Безcancelресурсы контекста висят до дедлайна,go vetловит это проверкойlostcancel; - родитель: в HTTP-хендлере берите
r.Context(), он отменяется при обрыве соединения. Дочерний дедлайн не может быть позже родительского, действует более ранний; - передача: контекст первым аргументом во все блокирующие вызовы:
http.NewRequestWithContext,db.QueryContext, клиенты gRPC и Redis. Прерывает операцию сама библиотека, когда видитctx.Done(); - своя долгая работа: в цикле или
selectслушайте<-ctx.Done()рядом с каналом результата. Канал результата делайте буферизованным, иначе горутина зависнет на отправке, когда её уже никто не ждёт; - разбор ошибки:
errors.Is(err, context.DeadlineExceeded)означает таймаут,context.Canceledотмену. С Go 1.21 естьcontext.WithTimeoutCauseиcontext.WithoutCancel: первый сохраняет причину дляcontext.Cause(ctx), второй нужен работе, которая должна пережить запрос, например записи аудита.
Полезно развести два механизма: http.Client{Timeout: ...} ограничивает один HTTP-обмен, а контекст задаёт общий бюджет всей цепочке вызовов, включая базу и соседние сервисы.
Где спотыкаются
Создают контекст с таймаутом, но внутри вызывают функции без контекста. Дедлайн наступает, а запрос в базу продолжает выполняться.
Думают, что отмена контекста убивает горутину. Остановить горутину снаружи нельзя, она должна сама прочитать ctx.Done() и выйти.
Хранят контекст в поле структуры или передают nil. Контекст передают параметром, а если его пока нет, используют context.TODO().