Nunca lances una goroutine sin dueño
La fuga invisible en los servicios Go generados por IA es una goroutine lanzada sin dueño. A los asistentes de IA les encanta la palabra clave go: es una sola palabra, compila y la demo funciona. Pero las goroutines no se recolectan como basura. El artículo de pipelines del propio equipo de Go es directo: las goroutines consumen memoria y recursos del runtime, y las referencias en sus stacks impiden que el recolector libere esos datos; las goroutines no se recolectan — deben terminar por sí mismas. Una goroutine sin señal de cancelación y sin un modelo de workers acotado nunca termina. Se fuga en silencio — unos pocos KB de stack cada una, más todo lo que su stack referencia — hasta que tu servicio se cae.
Por qué el Go generado por IA se fuga
El código generado optimiza para "compila y pasa el camino feliz". El patrón clásico parece inofensivo:
go func() {
for msg := range ch {
process(msg)
}
}()
¿Quién cierra ch? ¿Quién espera a esta goroutine al apagar el servicio? ¿Qué la detiene si la petición del llamador se cancela? Si no puedes responder las tres preguntas leyendo el punto de lanzamiento, tienes una fuga. Bajo carga, los emisores bloqueados se acumulan detrás de un receptor que dejó de leer, y cada uno retiene su mensaje en memoria para siempre.
El contrato de propiedad
Cada goroutine necesita un dueño cuyo ciclo de vida sea visible donde se lanza. Tres mecanismos lo cubren:
| Pregunta | Mecanismo | Regla práctica |
|---|---|---|
| ¿Cómo se detiene? | Cancelación con context.Context | Pasa ctx; haz select sobre ctx.Done() en cada bucle bloqueante |
| ¿Quién cierra el canal? | close del lado emisor | Solo la goroutine que envía por un canal lo cierra — nunca el receptor |
| ¿Quién la espera? | sync.WaitGroup | wg.Add(1) antes de go, defer wg.Done() adentro, wg.Wait() al apagar |
Todo junto, una goroutine con dueño se lee así:
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case msg, ok := <-ch:
if !ok {
return
}
process(msg)
}
}
}()
La cancelación, la propiedad del canal y la limpieza son evidentes en el punto de lanzamiento — exactamente lo que hace revisable al código generado.
Señales de alarma en una revisión
- Canales cerrados por receptores. Cerrar un canal es tarea del emisor; si el receptor lo cierra, el siguiente envío provoca un panic. Si un receptor "limpia" un canal, la propiedad está invertida.
- Canales compartidos sin reglas de propiedad. Un canal pasado a tres goroutines donde cualquiera podría cerrarlo, o ninguna lo hace, terminará en panic o en fuga.
goa secas, sinctxniWaitGroup. Lanzar y olvidar es una fuga con retraso. La guía de estilo de Go de Uber lo nombra directamente: no lances goroutines sin seguimiento, espera a que terminen y nunca crees goroutines eninit().panicpara errores normales. El código generado a veces usapanicante una búsqueda fallida o una entrada inválida. El panic es para estados verdaderamente irrecuperables; los fallos normales devuelven unerrorpara que el dueño decida. Un panic dentro de una goroutine sin dueño tumba el proceso entero — no hay recuperación a distancia.
Trucos avanzados
- Acota tus workers. En vez de una goroutine por elemento, lanza un pool fijo de N workers leyendo de un canal, todos bajo un mismo WaitGroup. La memoria pasa a ser O(N), no O(tráfico).
- Pide la propiedad en el prompt. Dile a tu asistente: "Cada goroutine debe hacer select sobre ctx.Done(), solo los emisores cierran canales, y el apagado debe hacer wg.Wait()." El código generado sigue el contrato que declaras.
- Prueba la fuga. Compara
runtime.NumGoroutine()antes y después de una prueba de carga, o usagoleak(el detector de fugas de Uber) en tus tests — hace fallar cualquier test que deje goroutines vivas. - Vigílalo en producción. El perfil
/debug/pprof/goroutinemuestra el stack de cada goroutine viva. Un conteo que solo sube es tu prueba definitiva.
Recursos
- Go Concurrency Patterns: Pipelines and cancellation — The Go Blog
- Paquete context — biblioteca estándar de Go
- sync.WaitGroup — biblioteca estándar de Go
- Don't fire-and-forget goroutines — Uber Go Style Guide
- Don't Panic — Uber Go Style Guide
- goleak — detector de fugas de goroutines
¿Construyes una funcionalidad con IA? Yeda AI diseña, audita y lanza sistemas LLM de producción.