Почему важно отменять запросы в Go
Что проверяют
Понимаете ли, что отмена в Go кооперативная, и умеете ли протаскивать context.Context через все слои. Для серверной разработки это базовый навык, и его проверяют почти всегда.
Как отвечать
Запрос порождает работу: походы в базу, вызовы соседних сервисов, горутины. Если инициатор ушёл, работа никому не нужна, но сама она не остановится. Отмена нужна, чтобы вернуть соединения в пул, не жечь квоты внешних API и не копить горутины, которые ждут ответа в никуда. На нагрузке это разница между деградацией и лавиной.
Механизм один: context.Context первым параметром, внутри проверка ctx.Done() и причина в ctx.Err().
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, q, id)В HTTP-обработчике контекст уже готов: r.Context() закрывается, когда клиент разорвал соединение. Его передают вниз без изменений либо сужают своим таймаутом.
Важно проговорить, что отмена кооперативная. Контекст не убивает горутину, он только сообщает. Код обязан либо читать ctx.Done() в select, либо вызывать функции с суффиксом Context, которые делают это за вас.
Где спотыкаются
Забывают defer cancel(). Дочерняя запись и таймер держатся в родительском контексте до наступления дедлайна, и на потоке запросов это видно как ровный рост памяти. Такую ошибку находит go vet, достаточно включить его в проверку.
Ждут, что вызов отмены прервёт любой код. Цикл расчёта, вызов библиотеки без поддержки контекста или запрос, уже ушедший в сеть, доработают до конца. Точки проверки в длинных циклах расставляют руками.
Тянут контекст запроса в фоновую задачу, которую надо доделать после ответа. Обработчик вернулся, контекст закрылся, запись оборвалась на середине. Для такой работы берут контекст без отмены родителя или отдельный, но обязательно со своим таймаутом.