Утечки горутин
Следом всплыло второе: клиент отменил загрузку, а её воркер продолжал жить в фоне. Через неделю в метриках «Полки» насчитали сотни забытых горутин. Тикет про утечку повесили на Мишу.
горутина, которая не может завершиться, — утечка: она держит свой стек вечно; ловят такие утечки сравнением runtime.NumGoroutine до и после работы.
Как горутина утекает
Обычно она блокируется навсегда — на канале (сами каналы разбираются в следующем модуле,), который никто не закроет, на мьютексе, который никто не отпустит, в бесконечном цикле без выхода.
<!-- wp:code-run/run {"code":"ch := make(chan int)\ngo func() {\nГорутина не занимает процессор, пока спит, — но она не завершится никогда. Каждая утёкшая горутина держит свой стек, и под нагрузкой такие утечки складываются в медленно растущую память. Так и росли невидимые воркеры отменённых загрузок в «Полке».
Как ловить
runtime.NumGoroutine возвращает текущее число горутин. Паттерн диагностики простой: запускаешь работу, ждёшь завершения, сравниваешь счётчик до и после:
before := runtime.NumGoroutine()
doWork()
time.Sleep(100 * time.Millisecond) // дать «хвостам» проявиться
after := runtime.NumGoroutine()
fmt.Println("утекло:", after-before)
Если счётчик не вернулся к исходному — кто-то остался висеть.
Почему утечки коварны
Утечка горутины не падает и не видна в профиле CPU — утёкшая горутина спит. Симптомы проявляются косвенно: память потихоньку растёт, а в /debug/pprof/goroutine копится парк «ждущих». Поэтому счётчик горутин — дешёвая метрика, которую стоит держать на графике у любого долгоживущего сервиса. Сергей именно её и вывел на дашборд «Полки» после этого разбора.
Вы узнаете:
- как утекают через небуферизованные каналы и
time.After - как горутины правильно ждут
ctx.Done()
Утёкшая горутина = вечная блокировка = растущая память. Диагностика: runtime.NumGoroutine до/после, метрика на графике.
10 000 ₽ · 60 минут · онлайн