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.

Leia mais »

Como fazer graceful shutdown correto em um server HTTP

Embora esse tema não seja abordado em exemplos simples de APIs, ele é muito importante. Encerrar um servidor HTTP de forma abrupta, acaba por fechar todas as conexões que ele tem abertas da mesma forma. Em outras palavras, não tratar desligamentos via SIGINT ou SIGTERM de uma API, pode acabar gerando grandes problemas.

Para evitar tais problemas, vou mostrar nesse post como tratar tais sinais e fazer um graceful shutdown do seu servidor HTTP.

Servidor HTTP

Para começar, vamos escrever uma rota que retornará um “hello world”. Vou utilizar o package go-chi para definir a rota não por ser necessário, mas para facilitar o entendimento em aplicações do mundo real.

r := chi.NewRouter()

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello World"))
})
Leia mais »
photo of gray faucet

Como resolver memory leaks em maps

Uma das formas mais comuns de se fazer cache em aplicações Go é utilizando um map. Se você já fez isso, deve ter notado um aumento gradual no consumo de memória, e que normalmente após um restart da máquina ou pod volta ao “normal”.

Isso acontece devido a forma como o map funciona. Por isso, antes de ver o que podemos fazer para resolver esse tipo de problema, vamos entender melhor o map.

Para exemplificar o problema, vamos considerar uma variável do tipo map[int][128]byte, que será “carregada” com 1 milhão de elementos e que na sequência serão removidas.

package main

import (
	"fmt"
	"runtime"
)

func main() {
	n := 1_000_000
	m := make(map[int][128]byte)
	printAlloc()

	for i := 0; i < n; i++ {
		m[i] = [128]byte{}
	}
	printAlloc()

	for i := 0; i < n; i++ {
		delete(m, i)
	}

	runtime.GC()
	printAlloc()
	runtime.KeepAlive(m)
}

func printAlloc() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("%d KB\\n", m.Alloc/1024)
}
Leia mais »