goroutine leaks no go 1.26

Detectando goroutine leaks no Go 1.26

Se você já trabalhou com Go em produção, provavelmente já enfrentou aquele cenário: goroutines que ficam presas para sempre, consumindo memória sem nunca terminar. O Go 1.26 trouxe uma ferramenta experimental que ataca esse problema direto na raiz: o goroutine leak profile.

O que são goroutine leaks?

Uma goroutine leak acontece quando uma goroutine fica bloqueada permanentemente — esperando em um channel, mutex ou condition variable que nunca vai ser desbloqueado. Na prática, é memória e recursos que ficam presos sem possibilidade de liberação.

O cenário mais clássico: você cria uma goroutine que escreve em um channel sem buffer, mas a função que deveria ler esse channel retorna antes por conta de um erro. A goroutine fica ali, bloqueada para sempre.

func processData(ctx context.Context) error {
    ch := make(chan result)

    go func() {
        // Essa goroutine vai ficar presa se ninguém ler do channel
        ch <- doHeavyWork()
    }()

    select {
    case <-ctx.Done():
        return ctx.Err() // retorna sem ler ch — goroutine vazou
    case r := <-ch:
        return r.err
    }
}

No exemplo acima, se o contexto for cancelado antes de doHeavyWork() terminar, a goroutine produtora fica bloqueada eternamente no ch <-. Isso é um leak.

Como o novo profile funciona

O goroutine leak profile usa a fase de marcação do garbage collector para identificar goroutines que estão bloqueadas em primitivas de concorrência — channels, mutexes, condition variables — que são inalcançáveis por qualquer goroutine em execução.

A lógica é simples: se nenhuma goroutine ativa consegue acessar o channel ou mutex no qual uma goroutine está bloqueada, essa goroutine nunca vai ser desbloqueada. Ou seja, é um leak.

O mais interessante é que essa detecção não adiciona overhead ao runtime enquanto não estiver sendo usada. Quando você solicita o profile, o runtime dispara um ciclo especial de GC focado na detecção de leaks.

Como habilitar

Por enquanto, o recurso é experimental. Para ativá-lo, você precisa compilar com a flag GOEXPERIMENT:

GOEXPERIMENT=goroutineleakprofile go build -o myapp ./cmd/myapp

Depois de compilado, o profile fica disponível de duas formas.

Via código com runtime/pprof

import "runtime/pprof"

profile := pprof.Lookup("goroutineleak")
if profile != nil {
    f, _ := os.Create("goroutine_leaks.prof")
    defer f.Close()
    profile.WriteTo(f, 0)
}

Via endpoint HTTP

Se você já usa net/http/pprof (e deveria), o endpoint fica disponível automaticamente:

GET /debug/pprof/goroutineleak

Basta acessar com go tool pprof:

go tool pprof http://localhost:6060/debug/pprof/goroutineleak

Na prática: encontrando leaks

Imagine que você tem um serviço em produção e suspeita de goroutine leaks. O fluxo é direto:

  1. Compile com GOEXPERIMENT=goroutineleakprofile
  2. Deploy normalmente
  3. Após algum tempo de execução, colete o profile via endpoint HTTP
  4. Analise com go tool pprof
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/goroutineleak

O output vai mostrar as goroutines que o runtime identificou como leaks, com o stack trace completo de onde elas foram criadas. Isso facilita muito encontrar a origem do problema.

Limitações

O profile tem uma limitação importante: ele não consegue detectar leaks causados por bloqueios em primitivas de concorrência que são alcançáveis via variáveis globais ou variáveis locais de goroutines que ainda estão rodando. Se o channel está armazenado em uma struct global, o runtime não tem como saber que ninguém vai ler dele.

Outro ponto: como é experimental, a API pode mudar. A expectativa é que o recurso seja habilitado por padrão no Go 1.27.

Resultados reais

A abordagem já foi validada em cenários reais pela equipe da Uber, que contribuiu com a implementação. Nos testes, o profile identificou entre 180 e 357 leaks distintos em mais de 3.000 suítes de teste, e encontrou 3 tipos diferentes de leak em um serviço de produção — com 252 ocorrências em apenas 24 horas.

Conclusão

Goroutine leaks são um problema silencioso que pode degradar a performance do seu serviço ao longo do tempo. Até agora, detectá-los dependia de ferramentas externas ou análise manual. Com o goroutine leak profile do Go 1.26, o próprio runtime cuida disso pra você — aproveitando o trabalho que o GC já faz.

Se você trabalha com Go em produção, vale muito a pena testar. Compile com GOEXPERIMENT=goroutineleakprofile, rode por algumas horas, e veja se o seu serviço tem goroutines esquecidas por aí.

Gostou do conteúdo?

  • ✅ Inscreva-se na newsletter para receber mais dicas práticas sobre Go diretamente no seu e-mail!
  • 🚀 Conheça a Imersão Golang e leve seus conhecimentos em Go para o próximo nível!

Faça parte da comunidade!

Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.

* indicates required

Deixe uma resposta