Ist es möglich, ein Strg + C-Signal zu erfassen und eine Bereinigungsfunktion auf "verzögerte" Weise auszuführen?

207

Ich möchte das von der Konsole gesendete Ctrl+C( SIGINT) Signal erfassen und einige Teillaufsummen ausdrucken.

Ist das in Golang möglich?

Hinweis: Als ich die Frage zum ersten Mal stellte, war ich verwirrt darüber Ctrl+C, SIGTERMstatt zu sein SIGINT.

Sebastián Grignoli
quelle

Antworten:

259

Sie können das Betriebssystem- / Signalpaket verwenden , um eingehende Signale zu verarbeiten. Ctrl+ Cist SIGINT , daher können Sie dies zum Einfangen verwenden 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
    }
}()

Die Art und Weise, wie Sie Ihr Programm beenden und Informationen drucken lassen, liegt ganz bei Ihnen.

Lily Ballard
quelle
Vielen Dank! Also ... ^ C ist es dann nicht SIGTERM? UPDATE: Der von Ihnen angegebene Link ist leider detailliert genug!
Sebastián Grignoli
19
Stattdessen for sig := range g {können Sie auch <-sigchanwie in dieser vorherigen Antwort verwenden: stackoverflow.com/questions/8403862/…
Denys Séguret
3
@dystroy: Sicher, wenn Sie das Programm tatsächlich als Reaktion auf das erste Signal beenden wollen. Mit der Schleife können Sie alle Signale abfangen, wenn Sie sich entscheiden , das Programm nicht zu beenden.
Lily Ballard
79
Hinweis: Sie müssen das Programm tatsächlich erstellen, damit dies funktioniert. Wenn Sie das Programm über go runeine Konsole ausführen und ein SIGTERM über ^ C senden, wird das Signal in den Kanal geschrieben und das Programm antwortet, scheint jedoch unerwartet aus der Schleife auszusteigen. Dies liegt daran, dass das SIGRERM auch geht go run! (Dies hat mich erheblich verwirrt!)
William Pursell
5
Beachten Sie, dass die Hauptgoroutine eine Blockierungsoperation aufrufen oder runtime.Goschedan einer geeigneten Stelle aufrufen muss (in der Hauptschleife Ihres Programms, falls vorhanden), damit die Goroutine eine Prozessorzeit erhält, um das Signal zu verarbeiten
misterbee
107

Das funktioniert:

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

quelle
1
Für andere Leser: Sehen Sie sich die Antwort von @adamonduty an, um zu erklären, warum Sie os.Interrupt und syscall abfangen möchten. SIGTERM Es wäre schön gewesen, seine Erklärung in diese Antwort aufzunehmen, insbesondere seit er Monate vor Ihnen gepostet hat.
Chris
1
Warum verwenden Sie einen nicht blockierenden Kanal? Ist das notwendig?
Awn
4
@ Barry warum ist die Puffergröße 2 statt 1?
Pdeva
2
Hier ist ein Auszug aus der Dokumentation . "Das Paketsignal blockiert das Senden an c nicht: Der Anrufer muss sicherstellen, dass c über ausreichend Pufferplatz verfügt, um mit der erwarteten Signalrate Schritt zu halten. Für einen Kanal, der zur Benachrichtigung nur eines Signalwerts verwendet wird, ist ein Puffer der Größe 1 ausreichend."
bmdelacruz
25

Um die anderen Antworten etwas zu ergänzen, können Sie syscall.SIGTERManstelle von os.Interrupt SIGTERM (das vom Befehl kill gesendete Standardsignal) abfangen. Beachten Sie, dass die Syscall-Schnittstelle systemspezifisch ist und möglicherweise nicht überall funktioniert (z. B. unter Windows). Aber es funktioniert gut, beides zu fangen:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
Adamlamar
quelle
7
Mit dieser signal.NotifyFunktion können mehrere Signale gleichzeitig angegeben werden. So können Sie Ihren Code vereinfachen signal.Notify(c, os.Interrupt, syscall.SIGTERM).
Jochen
Ich glaube, ich habe das nach dem Posten herausgefunden. Fest!
Adamlamar
1
Was ist mit os.Kill?
Awn
2
@Eclipse Gute Frage! os.Kill entspricht auf syscall.Kill, das ein Signal ist , das aber nicht gefangen versandt werden kann. Es entspricht dem Befehl kill -9 <pid>. Wenn Sie fangen kill <pid>und ordnungsgemäß herunterfahren möchten , müssen Sie verwenden syscall.SIGTERM.
Adamlamar
@adamlamar Ahh, das macht Sinn. Vielen Dank!
Awn
18

Die oben akzeptierte Antwort enthielt (zum Zeitpunkt der Veröffentlichung) ein oder zwei kleine Tippfehler. Hier ist also die bereinigte Version. In diesem Beispiel stoppe ich den CPU-Profiler beim Empfang von 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)                                                     
  }                                                                
}()    
Gravitron
quelle
2
Beachten Sie, dass die Hauptgoroutine eine Blockierungsoperation aufrufen oder runtime.Goschedan einer geeigneten Stelle aufrufen muss (in der Hauptschleife Ihres Programms, falls vorhanden), damit die Goroutine eine Prozessorzeit erhält, um das Signal zu verarbeiten
misterbee
8

Alle oben genannten Punkte scheinen zu funktionieren, wenn sie zusammengefügt werden, aber die Signalseite von gobyexample enthält ein wirklich sauberes und vollständiges Beispiel für die Signalerfassung. Es lohnt sich, diese Liste zu ergänzen.

willscripted
quelle
0

Death ist eine einfache Bibliothek, die Kanäle und eine Wartegruppe verwendet, um auf Abschaltsignale zu warten. Sobald das Signal empfangen wurde, ruft es eine close-Methode für alle Ihre Strukturen auf, die Sie bereinigen möchten.

Ben Aldrich
quelle
3
So viele Codezeilen und eine externe Bibliotheksabhängigkeit, um das zu tun, was in vier Codezeilen getan werden kann? (gemäß akzeptierter Antwort)
Jacob
Sie können alle parallel bereinigen und Strukturen automatisch schließen, wenn sie über die Standardschnittstelle zum Schließen verfügen.
Ben Aldrich
0

Sie können eine andere Goroutine verwenden, die syscall.SIGINT- und syscall.SIGTERM-Signale erkennt und diese mithilfe von signal.Notify an einen Kanal weiterleitet . Sie können einen Hook über einen Kanal an diese Goroutine senden und in einem Funktions-Slice speichern. Wenn das Abschaltsignal auf dem Kanal erkannt wird, können Sie diese Funktionen im Slice ausführen. Dies kann verwendet werden, um die Ressourcen zu bereinigen, auf das Ausführen von Goroutinen zu warten, Daten beizubehalten oder Teillaufsummen zu drucken.

Ich habe ein kleines und einfaches Dienstprogramm geschrieben, um beim Herunterfahren Hooks hinzuzufügen und auszuführen. Hoffe es kann hilfreich sein.

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

Sie können dies auf eine "verzögerte" Weise tun.

Beispiel für das ordnungsgemäße Herunterfahren eines Servers:

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()
Ankit Arora
quelle
0

Dies ist eine andere Version, die funktioniert, wenn Sie einige Aufgaben bereinigen müssen. Der Code verlässt den Bereinigungsprozess in seiner Methode.

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
}

Wenn Sie die Hauptfunktion bereinigen müssen, müssen Sie das Signal im Hauptthread auch mit go func () erfassen.

Hlex
quelle
-2

Nur zur Veranschaulichung, wenn jemand eine Möglichkeit benötigt, Signale unter Windows zu verarbeiten. Ich hatte die Anforderung, von Prog A, das Prog B aufruft, über OS / Exec zu kommen, aber Prog B konnte nie ordnungsgemäß beendet werden, da Signale über Ex gesendet wurden. cmd.Process.Signal (syscall.SIGTERM) oder andere Signale werden unter Windows nicht unterstützt. Ich habe damit umgegangen, indem ich eine temporäre Datei als Signal erstellt habe. .signal.term durch Prog A und Prog B muss überprüfen, ob diese Datei auf Intervallbasis vorhanden ist. Wenn eine Datei vorhanden ist, wird das Programm beendet und bei Bedarf bereinigt. Ich bin sicher, dass es andere Möglichkeiten gibt, aber dies hat den Job erledigt.

user212806
quelle