Что такое утечка горутин и как ее обнаружить
Что проверяют
Понимаете ли вы, что горутина не убирается сборщиком мусора. Если она навсегда заблокирована, её стек и всё, на что она ссылается, живут до конца процесса. Вопрос отсеивает тех, кто пишет go перед вызовом, не отвечая себе, кто и когда эту горутину остановит.
Как отвечать
Утечка это горутина, которая никогда не завершится, потому что вечно ждёт на канале, мьютексе или сетевом вызове без таймаута. Частые источники: отправка в небуферизованный канал, читатель которого уже ушёл по таймауту; чтение из канала, который никто не закроет; цикл без выхода по ctx.Done(); тикер без defer ticker.Stop(); незакрытое тело HTTP-ответа.
Лечится тем же, чем создаётся: у каждой горутины есть владелец, который её останавливает.
func worker(ctx context.Context, in <-chan Job) {
for {
select {
case <-ctx.Done():
return
case j, ok := <-in:
if !ok {
return
}
handle(j)
}
}
}Обнаружение: счётчик runtime.NumGoroutine() в метриках показывает медленный рост; профиль goroutine в pprof с полными стеками показывает, сколько горутин стоит на одной и той же строке; библиотека goleak ловит незавершённые горутины прямо в тестах пакета.
Где спотыкаются
Объясняют утечку тем, что «потерялась ссылка на горутину». Ссылок на горутины не бывает, утечка это всегда блокировка, которая никогда не разрешится.
Отдают результат в небуферизованный канал, а сами уходят по таймауту в select. Читателя больше нет, писатель висит вечно, и буфер на один элемент закрывает вопрос.
Прокидывают context в функцию и считают дело сделанным. Контекст ничего не прерывает сам, он только сигнализирует: без явной проверки ctx.Done() внутри долгого цикла горутина продолжит работать.