Est-il possible de capturer un signal Ctrl + C et d'exécuter une fonction de nettoyage, de manière «différée»?


207

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 SIGTERMlieu de SIGINT.

Réponses:


261

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.


Merci! Alors ... ^ C ce n'est pas SIGTERM, alors? MISE À JOUR: Désolé, le lien que vous avez fourni est suffisamment détaillé!
Sebastián Grignoli

19
Au lieu de for sig := range g {, vous pouvez également utiliser <-sigchancomme dans cette réponse précédente: stackoverflow.com/questions/8403862/…
Denys Séguret

3
@dystroy: Bien sûr, si vous allez réellement terminer le programme en réponse au premier signal. En utilisant la boucle, vous pouvez capter tous les signaux si vous décidez de ne pas terminer le programme.
Lily Ballard

79
Remarque: vous devez réellement créer le programme pour que cela fonctionne. Si vous exécutez le programme via go rundans 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 runaussi! (Cela m'a causé beaucoup de confusion!)
William Pursell

5
Notez que pour que le goroutine ait le temps du processeur pour gérer le signal, le goroutine principal doit appeler une opération de blocage ou appeler runtime.Goschedà un endroit approprié (dans la boucle principale de votre programme, s'il en a un)
misterbee

107

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
    }
}

1
Pour les autres lecteurs: regardez la réponse de @adamonduty pour une explication de la raison pour laquelle vous voulez attraper os.Interrupt et syscall.SIGTERM Cela aurait été bien d'inclure son explication dans cette réponse, surtout depuis qu'il a posté des mois avant vous.
Chris

1
Pourquoi utilisez-vous un canal non bloquant? Est-ce nécessaire?
Awn

4
@Barry pourquoi la taille du tampon est-elle de 2 au lieu de 1?
pdeva

2
Voici un extrait de la documentation . "Le signal du package ne bloquera pas l'envoi à c: l'appelant doit s'assurer que c dispose d'un espace tampon suffisant pour suivre le débit de signal attendu. Pour un canal utilisé pour la notification d'une seule valeur de signal, un tampon de taille 1 est suffisant."
bmdelacruz

25

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)
....

7
La signal.Notifyfonction permet de spécifier plusieurs signaux à la fois. Ainsi, vous pouvez simplifier votre code en signal.Notify(c, os.Interrupt, syscall.SIGTERM).
jochen

Je pense que je l'ai découvert après avoir posté. Fixé!
adamlamar

2
Et os.Kill?
Awn

2
@Eclipse Grande question! 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.
adamlamar

@adamlamar Ahh, c'est logique. Merci!
Awn

18

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)                                                     
  }                                                                
}()    

2
Notez que pour que le goroutine ait le temps du processeur pour gérer le signal, le goroutine principal doit appeler une opération de blocage ou appeler runtime.Goschedà un endroit approprié (dans la boucle principale de votre programme, s'il en a un)
misterbee


0

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.


3
Tant de lignes de code et une dépendance de bibliothèque externe pour faire ce qui peut être fait en quatre lignes de code? (selon la réponse acceptée)
Jacob

il vous permet de nettoyer tous en parallèle et de fermer automatiquement les structures si elles ont l'interface de fermeture standard.
Ben Aldrich

0

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()

0

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 ().


-2

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.