Ctrl+C en Go : interception de SIGINT et nettoyage « defer » sous le capot

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Peut-on intercepter Ctrl+C en Go et exécuter un nettoyage comme defer ? Analyse sous le capot

\n\n

Avez-vous déjà lancé un serveur en Go, appuyé sur Ctrl+C et pensé : « Et si, avant de quitter, je pouvais fermer proprement les connexions à la base de données, sauvegarder le cache ou simplement dire « Au revoir ! » ? » La question est si populaire sur Stack Overflow (des milliers de vues !) que nous avons décidé de l'analyser en détail. Aujourd'hui, nous ne donnons pas seulement une recette toute prête, mais nous regardons sous le capot — comment Go gère les signaux, ce qu'est signal.Notify et pourquoi defer n'est pas vraiment concerné ici.

\n\n

Prêts ? C'est parti !

\n\n

Le problème : Ctrl+C tue le programme instantanément

\n\n

Par défaut, lorsque vous appuyez sur Ctrl+C dans le terminal, le système d'exploitation envoie le signal SIGINT au processus. Le programme Go, si rien n'est fait, se termine brutalement — toutes les goroutines sont interrompues, les fonctions defer ne sont pas exécutées. C'est comme débrancher la prise : l'ordinateur s'éteint, mais les données peuvent être perdues.

\n\n

Exemple classique : vous écrivez un serveur web, vous ouvrez une connexion à PostgreSQL, et soudain — Ctrl+C. Si vous ne gérez pas le signal, la connexion reste pendante et le pool de connexions n'est pas libéré. Après quelques redémarrages de ce type, la base de données dira : « Désolé, je n'en peux plus ».

\n\n
package main\n\nimport (\n    "fmt"\n    "os"\n    "os/signal"\n    "syscall"\n)\n\nfunc main() {\n    // ... votre code\n    fmt.Println("Appuyez sur Ctrl+C pour terminer")\n    // On attend le signal\n    sig := make(chan os.Signal, 1)\n    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)\n    <-sig\n    fmt.Println("Signal reçu, on termine...")\n}
\n\n

Ce code intercepte déjà le signal, mais que faire si nous devons effectuer un nettoyage complexe ? C'est là que la magie commence.

\n\n

Comment ça marche sous le capot : signal.Notify et les canaux

\n\n

La fonction signal.Notify enregistre votre canal pour recevoir certains signaux. Lorsque le système d'exploitation envoie un signal, le runtime Go le place dans le canal. Si le canal est tamponné (et nous avons créé un tampon de 1), le signal ne sera pas perdu, même si la goroutine réceptrice est occupée.

\n\n

Nuance importante : signal.Notify intercepte le signal, mais ne bloque pas sa propagation. Si vous ne traitez pas le signal dans le canal, le programme se terminera quand même après l'exécution des defer (s'ils existent dans main). Mais si vous voulez effectuer un nettoyage, vous devez attendre qu'il soit terminé.

\n\n

Schéma de fonctionnement :

\n
    \n
  • Système d'exploitation → envoie SIGINT au processus
  • \n
  • Runtime Go → place le signal dans le canal enregistré via signal.Notify
  • \n
  • Votre goroutine → lit le canal et lance le nettoyage
  • \n
  • Après le nettoyage → vous appelez os.Exit(0) ou vous quittez simplement main
  • \n
\n\n

Mais comment faire un nettoyage « dans le style defer » ? Car defer s'exécute au retour d'une fonction, pas à la réception d'un signal. Réponse : on utilise defer + le blocage de main jusqu'à la réception du signal.

\n\n

Recette : nettoyage « façon defer » avec Ctrl+C

\n\n

L'idée est simple : nous écrivons des fonctions de nettoyage qui doivent s'exécuter à la fin, et nous les « accrochons » avec defer. Ensuite, nous bloquons main jusqu'à la réception du signal. Lorsque le signal arrive, main se termine et tous les defer s'exécutent. Élégant, non ?

\n\n
package main\n\nimport (\n    "fmt"\n    "os"\n    "os/signal"\n    "syscall"\n    "time"\n)\n\nfunc cleanup() {\n    fmt.Println("Nettoyage : fermeture des connexions...")\n    time.Sleep(2 * time.Second) // simulation de travail\n    fmt.Println("Nettoyage terminé")\n}\n\nfunc main() {\n    // defer s'exécutera à toute fin de main\n    defer cleanup()\n\n    // Canal pour le signal\n    sig := make(chan os.Signal, 1)\n    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)\n\n    fmt.Println("Serveur démarré. Appuyez sur Ctrl+C pour arrêter.")\n    fmt.Println("Nettoyage en cours...")\n\n    //                

Blogs

Book Recommendations