È possibile acquisire un segnale Ctrl + C ed eseguire una funzione di pulizia, in modo "differito"?


207

Voglio catturare il segnale Ctrl+C( SIGINT) inviato dalla console e stampare alcuni totali della corsa parziale.

Questo è possibile a Golang?

Nota: quando ho pubblicato per la prima volta la domanda ero confuso di Ctrl+Cessere SIGTERMinvece di SIGINT.

Risposte:


260

È possibile utilizzare il pacchetto os / signal per gestire i segnali in arrivo. Ctrl+ Cè SIGINT , quindi puoi usarlo per intrappolare 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
    }
}()

Il modo in cui causi la chiusura del programma e la stampa delle informazioni dipende interamente da te.


Grazie! Quindi ... ^ C non è SIGTERM, allora? AGGIORNAMENTO: Siamo spiacenti, il link che hai fornito è abbastanza dettagliato!
Sebastián Grignoli,

19
Invece di for sig := range g {, puoi anche usare <-sigchancome in questa risposta precedente: stackoverflow.com/questions/8403862/…
Denys Séguret

3
@dystroy: Certo, se stai per terminare il programma in risposta al primo segnale. Usando il loop puoi catturare tutti i segnali se decidi di non terminare il programma.
Lily Ballard,

79
Nota: è necessario creare effettivamente il programma affinché funzioni. Se si esegue il programma go runin una console e si invia un SIGTERM tramite ^ C, il segnale viene scritto nel canale e il programma risponde, ma sembra uscire improvvisamente dal ciclo. Questo perché anche SIGRERM lo fa go run! (Questo mi ha causato una notevole confusione!)
William Pursell,

5
Si noti che affinché la goroutine ottenga il tempo del processore per gestire il segnale, la goroutine principale deve chiamare un'operazione di blocco o chiamare runtime.Goschedin un posto appropriato (nel ciclo principale del programma, se ne ha uno)
misterbee

107

Questo funziona:

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
Per gli altri lettori: guarda la risposta di @adamonduty per una spiegazione del perché vuoi catturare os.Interrupt e syscall.SIGTERM Sarebbe stato bello includere la sua spiegazione in questa risposta, soprattutto perché ha pubblicato mesi prima di te.
Chris,

1
Perché stai usando un canale non bloccante? È necessario?
Alba

4
@Barry perché la dimensione del buffer è 2 anziché 1?
pdeva,

2
Ecco un estratto dalla documentazione . "Il segnale del pacchetto non bloccherà l'invio a c: il chiamante deve assicurarsi che c abbia spazio di buffer sufficiente per tenere il passo con la velocità del segnale prevista. Per un canale utilizzato per la notifica di un solo valore di segnale, è sufficiente un buffer di dimensione 1".
bmdelacruz,

25

Per aggiungere leggermente alle altre risposte, se vuoi catturare SIGTERM (il segnale predefinito inviato dal comando kill), puoi usare syscall.SIGTERMal posto di os.Interrupt. Attenzione che l'interfaccia di syscall è specifica del sistema e potrebbe non funzionare ovunque (ad es. Su Windows). Ma funziona bene per catturare entrambi:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....

7
La signal.Notifyfunzione consente di specificare più segnali contemporaneamente. Pertanto, è possibile semplificare il codice signal.Notify(c, os.Interrupt, syscall.SIGTERM).
jochen

Penso di averlo scoperto dopo aver pubblicato. Fisso!
adamlamar,

2
Che dire di os.Kill?
Alba

2
@Eclipse Ottima domanda! os.Kill corrisponde a syscall.Kill, che è un segnale che può essere inviato ma non catturato. È equivalente al comando kill -9 <pid>. Se vuoi catturare kill <pid>e spegnere con grazia, devi usare syscall.SIGTERM.
adamlamar,

@adamlamar Ahh, ha senso. Grazie!
Alba

18

C'erano (al momento della pubblicazione) uno o due piccoli errori di battitura nella risposta accettata sopra, quindi ecco la versione pulita. In questo esempio sto arrestando il profiler della CPU quando ricevo 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
Si noti che affinché la goroutine ottenga il tempo del processore per gestire il segnale, la goroutine principale deve chiamare un'operazione di blocco o chiamare runtime.Goschedin un posto appropriato (nel ciclo principale del programma, se ne ha uno)
misterbee


0

Death è una semplice libreria che utilizza i canali e un gruppo di attesa per attendere i segnali di spegnimento. Una volta che il segnale è stato ricevuto, chiamerà quindi un metodo di chiusura su tutte le strutture che si desidera pulire.


3
Così tante righe di codice e una dipendenza da libreria esterna per fare ciò che si può fare in quattro righe di codice? (Come da risposta accettata)
Jacob

ti permette di fare la pulizia di tutti in parallelo e di chiudere automaticamente le strutture se hanno l'interfaccia di chiusura standard.
Ben Aldrich,

0

Puoi avere una diversa goroutine che rileva i segnali syscall.SIGINT e syscall.SIGTERM e li inoltra a un canale usando signal.Notify . Puoi inviare un hook a quel goroutine usando un canale e salvarlo in una sezione di funzioni. Quando viene rilevato il segnale di arresto sul canale, è possibile eseguire quelle funzioni nella sezione. Può essere utilizzato per ripulire le risorse, attendere il completamento delle goroutine, conservare i dati o stampare i totali delle esecuzioni parziali.

Ho scritto una piccola e semplice utility per aggiungere ed eseguire hook all'arresto. Spero possa essere di aiuto.

https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go

Puoi farlo in modo "differito".

esempio per spegnere un server con garbo:

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

Questa è un'altra versione che funziona nel caso abbiate delle attività da ripulire. Il codice lascerà il processo di pulizia nel loro metodo.

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
}

nel caso in cui sia necessario pulire la funzione principale, è necessario acquisire il segnale nel thread principale utilizzando anche go func ().


-2

Solo per la cronaca se qualcuno ha bisogno di un modo per gestire i segnali su Windows. Avevo l'obbligo di gestire da prog A chiamando prog B tramite os / exec ma prog B non è mai stato in grado di terminare con grazia perché inviando segnali tramite ex. cmd.Process.Signal (syscall.SIGTERM) o altri segnali non sono supportati su Windows. Il modo in cui ho gestito è creando un file temporaneo come segnale ex. .signal.term tramite prog A e prog B devono verificare se quel file esiste su base intervallo, se il file esiste uscirà dal programma e gestirà una pulizia se necessario, sono sicuro che ci sono altri modi ma questo ha fatto il lavoro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.