Ctrl+C en Go: captura de SIGINT y limpieza con «defer» bajo el capó

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

¿Se puede capturar Ctrl+C en Go y ejecutar una limpieza como defer? Análisis bajo el capó

\n\n

¿Alguna vez ha ejecutado un servidor en Go, presionado Ctrl+C y pensado: «¿Y si antes de salir pudiera cerrar ordenadamente las conexiones a la BD, guardar la caché o simplemente decir "¡Adiós!"?» La pregunta es tan popular en Stack Overflow (¡miles de visitas!) que decidimos analizarla en detalle. Hoy no solo daremos una receta lista, sino que miraremos bajo el capó: cómo Go maneja las señales, qué es signal.Notify y por qué defer no tiene mucho que ver aquí.

\n\n

¿Listos? ¡Allá vamos!

\n\n

El problema: Ctrl+C mata el programa al instante

\n\n

Por defecto, cuando presiona Ctrl+C en la terminal, el SO envía al proceso la señal SIGINT. El programa en Go, si no hace nada, termina abruptamente: todas las gorutinas se interrumpen, las funciones defer no se ejecutan. Es como desconectar el cable de la corriente: la computadora se apaga, pero los datos pueden perderse.

\n\n

Ejemplo clásico: escribe un servidor web, abre una conexión con PostgreSQL y, de repente — Ctrl+C. Si no maneja la señal, la conexión quedará colgada y el pool de conexiones no se liberará. Después de varios reinicios así, la base de datos dirá: «Lo siento, no puedo más».

\n\n
package main\n\nimport (\n    "fmt"\n    "os"\n    "os/signal"\n    "syscall"\n)\n\nfunc main() {\n    // ... su código\n    fmt.Println("Presione Ctrl+C para finalizar")\n    // Esperamos la señal\n    sig := make(chan os.Signal, 1)\n    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)\n    <-sig\n    fmt.Println("Señal recibida, finalizando...")\n}
\n\n

Este código ya captura la señal, pero ¿qué pasa si necesitamos ejecutar una limpieza compleja? Aquí es donde comienza la magia.

\n\n

Cómo funciona bajo el capó: signal.Notify y canales

\n\n

La función signal.Notify registra su canal para recibir señales específicas. Cuando el SO envía una señal, el runtime de Go la coloca en el canal. Si el canal está buferizado (y nosotros hicimos un buffer de 1), la señal no se perderá, incluso si la gorutina receptora está ocupada.

\n\n

Un detalle importante: signal.Notify captura la señal, pero no bloquea su propagación. Si no maneja la señal en el canal, el programa igualmente finalizará después de ejecutar los defer (si los hay en main). Pero si quiere ejecutar una limpieza, debe esperar a que termine.

\n\n

Esquema de funcionamiento:

\n
    \n
  • SO → envía SIGINT al proceso
  • \n
  • Runtime de Go → coloca la señal en el canal registrado mediante signal.Notify
  • \n
  • Su gorutina → lee del canal y ejecuta la limpieza
  • \n
  • Después de la limpieza → llama a os.Exit(0) o simplemente sale de main
  • \n
\n\n

Pero, ¿cómo hacer la limpieza «al estilo defer»? Porque defer se ejecuta al retornar de la función, no al recibir una señal. Respuesta: usamos defer + bloqueo de main hasta recibir la señal.

\n\n

Receta: limpieza «similar a defer» con Ctrl+C

\n\n

La idea es simple: escribimos funciones de limpieza que deben ejecutarse al finalizar y las «colgamos» con defer. Luego bloqueamos main hasta recibir la señal. Cuando la señal llega, main finaliza y todos los defer se ejecutan. Elegante, ¿verdad?

\n\n
package main\n\nimport (\n    "fmt"\n    "os"\n    "os/signal"\n    "syscall"\n    "time"\n)\n\nfunc cleanup() {\n    fmt.Println("Limpieza: cerrando conexiones...")\n    time.Sleep(2 * time.Second) // simulación de trabajo\n    fmt.Println("Limpieza completada")\n}\n\nfunc main() {\n    // defer se ejecutará al finalizar main\n    defer cleanup()\n\n    // Canal para la señal\n    sig := make(chan os.Signal, 1)\n    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)\n\n    fmt.Println("Servidor iniciado. Presione Ctrl+C para detener.")\n    fmt.Println("Ejecutando limpieza...")\n\n    //                

Blogs

Book Recommendations