Перед тобой классический пример утечки goroutine, который долгое время было сложно отлавливать автоматически.
Проблема возникает, когда:
- используются небуферизированные каналы
- запускаются goroutine в цикле
- происходит ранний return по ошибке
В такой ситуации часть goroutine продолжает попытки записи в канал, но получателя уже нет — они зависают навсегда.
Раньше такие баги:
- проявлялись только под нагрузкой
- маскировались как рост памяти или странные тайминги
- находились уже в продакшене
В Go 1.26 runtime научился детектить такие утечки автоматически, если включено профилирование.
func fetchUsers(ids []int) ([]*User, error) {
ch := make(chan *User)
for _, id := range ids {
go func(id int) {
user := fetchUser(id)
ch <- user // блокируется навсегда при раннем return
}(id)
}
for range ids {
user := <-ch
if user == nil {
return nil, errors.New("failed") // LEAK: другие goroutine зависли
}
}
return nil, nil
}
Запуск с детекцией утечек goroutine
GOEXPERIMENT=goroutineleakprofile go run main.go
Проверка профиля
curl http://localhost:6060/debug/pprof/goroutineleak
https://www.youtube.com/shorts/Wvimjygw_ys