Утечки горутин в Go 1.24+
Вы конечно и так в курсе, но на всякий случай:
Утечка происходит, если одна или несколько горутин навсегда заблокировались на канале (или другом примитиве синхронизации), но другие горутины и программа в целом при этом продолжают работать.
Утрированный пример утечки:
func work() chan int {
ch := make(chan int)
go func() {
ch <- 42
ch <- 13 // (!) утечка
}()
return ch
}
func main() {
<-work()
// ...
}
Традиционно Go не очень-то помогал в поиске утечек. Обнаружить их можно было разве что пристально разглядывая профиль или трассировку с продакшена, а в тестах приходилось использовать сторонний пакет goleak от Убера.
Сейчас это меняется.
Сначала в Go 1.24 добавили пакет synctest, который прекрасно справляется с поиском утечек при тестировании. Об этом почему-то никто не говорит — наверно, потому что не проходили мой курс по многозадачности 😁
func Test(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
<-work()
synctest.Wait()
})
}
panic: main bubble goroutine has exited but blocked goroutines remain
goroutine 37 [chan send (durable), synctest bubble 1]:
main.work.func1()
leak_test.go:12 +0x3c
created by main.work in goroutine 36
leak_test.go:10 +0x6c
Как видите, тест и ругнулся, и точно указал, где возникла утечка.
А теперь в Go 1.26 подвезут новый pprof-профиль под названием goroutineleak — он надежно (без ложных срабатываний) обнаруживает утечки в продакшене (как вы понимаете, synctest-то в продакшене не запустишь).
func main() {
prof := pprof.Lookup("goroutineleak")
defer func() {
time.Sleep(50 * time.Millisecond)
prof.WriteTo(os.Stdout, 2)
}()
<-work()
}
goroutine 3 [chan send (leaked)]:
main.work.func1()
leak.go:12 +0x3c
created by main.work in goroutine 1
leak.go:10 +0x74
Как видите, указал ровно на ту же утечку, что synctest.
В общем, рекомендую оба инструмента. За подробностями велкам в статью:
Detecting goroutine leaks in modern Go
★ Подписывайтесь на канал и проходите курсы.