Je veux capturer le signal Ctrl+C( SIGINT
) envoyé depuis la console et imprimer des totaux partiels.
Est-ce possible à Golang?
Note: Quand je suis posté la question que je confus au sujet d' Ctrl+Cêtre au SIGTERM
lieu de SIGINT
.
Je veux capturer le signal Ctrl+C( SIGINT
) envoyé depuis la console et imprimer des totaux partiels.
Est-ce possible à Golang?
Note: Quand je suis posté la question que je confus au sujet d' Ctrl+Cêtre au SIGTERM
lieu de SIGINT
.
Réponses:
Vous pouvez utiliser le package os / signal pour gérer les signaux entrants. Ctrl+ Cest SIGINT , vous pouvez donc l'utiliser pour intercepter os.Interrupt
.
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func(){
for sig := range c {
// sig is a ^C, handle it
}
}()
La manière dont vous faites interrompre votre programme et imprimer des informations vous appartient entièrement.
for sig := range g {
, vous pouvez également utiliser <-sigchan
comme dans cette réponse précédente: stackoverflow.com/questions/8403862/…
go run
dans une console et envoyez un SIGTERM via ^ C, le signal est écrit dans le canal et le programme répond, mais semble sortir de la boucle de manière inattendue. C'est parce que le SIGRERM va go run
aussi! (Cela m'a causé beaucoup de confusion!)
runtime.Gosched
à un endroit approprié (dans la boucle principale de votre programme, s'il en a un)
Cela marche:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time" // or "runtime"
)
func cleanup() {
fmt.Println("cleanup")
}
func main() {
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
cleanup()
os.Exit(1)
}()
for {
fmt.Println("sleeping...")
time.Sleep(10 * time.Second) // or runtime.Gosched() or similar per @misterbee
}
}
Pour ajouter un peu aux autres réponses, si vous voulez réellement attraper SIGTERM (le signal par défaut envoyé par la commande kill), vous pouvez utiliser syscall.SIGTERM
à la place de os.Interrupt. Attention, l'interface syscall est spécifique au système et peut ne pas fonctionner partout (par exemple sur Windows). Mais cela fonctionne bien pour attraper les deux:
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
signal.Notify
fonction permet de spécifier plusieurs signaux à la fois. Ainsi, vous pouvez simplifier votre code en signal.Notify(c, os.Interrupt, syscall.SIGTERM)
.
os.Kill
correspond à syscall.Kill
, qui est un signal qui peut être envoyé mais pas capté. Son équivalent à la commande kill -9 <pid>
. Si vous voulez intercepter kill <pid>
et arrêter gracieusement, vous devez utiliser syscall.SIGTERM
.
Il y avait (au moment de la publication) une ou deux petites fautes de frappe dans la réponse acceptée ci-dessus, alors voici la version nettoyée. Dans cet exemple, j'arrête le profileur de processeur lors de la réception de Ctrl+ C.
// capture ctrl+c and stop CPU profiler
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
log.Printf("captured %v, stopping profiler and exiting..", sig)
pprof.StopCPUProfile()
os.Exit(1)
}
}()
runtime.Gosched
à un endroit approprié (dans la boucle principale de votre programme, s'il en a un)
Tous les éléments ci-dessus semblent fonctionner lorsqu'ils sont épissés, mais la page des signaux de gobyexample propose un exemple vraiment propre et complet de capture de signal. Vaut la peine d'être ajouté à cette liste.
Death est une bibliothèque simple qui utilise des canaux et un groupe d'attente pour attendre les signaux d'arrêt. Une fois le signal reçu, il appellera une méthode close sur toutes vos structures que vous souhaitez nettoyer.
Vous pouvez avoir une goroutine différente qui détecte les signaux syscall.SIGINT et syscall.SIGTERM et les relaie à un canal à l'aide de signal.Notify . Vous pouvez envoyer un crochet à ce goroutine en utilisant un canal et l'enregistrer dans une tranche de fonction. Lorsque le signal d'arrêt est détecté sur le canal, vous pouvez exécuter ces fonctions dans la tranche. Cela peut être utilisé pour nettoyer les ressources, attendre la fin des goroutines en cours d'exécution, conserver les données ou imprimer les totaux des exécutions partielles.
J'ai écrit un petit utilitaire simple pour ajouter et exécuter des hooks à l'arrêt. J'espère que cela peut être utile.
https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go
Vous pouvez le faire de manière «différée».
exemple pour arrêter un serveur normalement:
srv := &http.Server{}
go_shutdown_hook.ADD(func() {
log.Println("shutting down server")
srv.Shutdown(nil)
log.Println("shutting down server-done")
})
l, err := net.Listen("tcp", ":3090")
log.Println(srv.Serve(l))
go_shutdown_hook.Wait()
Il s'agit d'une autre version qui fonctionne au cas où vous auriez des tâches à nettoyer. Le code laissera le processus de nettoyage dans leur méthode.
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
_,done1:=doSomething1()
_,done2:=doSomething2()
//do main thread
println("wait for finish")
<-done1
<-done2
fmt.Print("clean up done, can exit safely")
}
func doSomething1() (error, chan bool) {
//do something
done:=make(chan bool)
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
//cleanup of something1
done<-true
}()
return nil,done
}
func doSomething2() (error, chan bool) {
//do something
done:=make(chan bool)
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
//cleanup of something2
done<-true
}()
return nil,done
}
si vous avez besoin de nettoyer la fonction principale, vous devez également capturer le signal dans le thread principal en utilisant go func ().
Juste pour mémoire si quelqu'un a besoin d'un moyen de gérer les signaux sous Windows. J'avais besoin de gérer depuis prog A en appelant prog B via os / exec mais prog B n'a jamais pu se terminer gracieusement car l'envoi de signaux via ex. cmd.Process.Signal (syscall.SIGTERM) ou d'autres signaux ne sont pas pris en charge sous Windows. La façon dont j'ai géré est de créer un fichier temporaire comme un signal ex. .signal.term via prog A et prog B doit vérifier si ce fichier existe sur la base de l'intervalle, s'il existe, il quittera le programme et effectuera un nettoyage si nécessaire, je suis sûr qu'il existe d'autres moyens mais cela a fait l'affaire.