Czy można przechwycić sygnał Ctrl + C i uruchomić funkcję czyszczenia w „odroczony” sposób?

207

Chcę przechwycić sygnał Ctrl+C( SIGINT) wysłany z konsoli i wydrukować niektóre podsumowania częściowego uruchomienia.

Czy to możliwe w Golang?

Uwaga: Kiedy po raz pierwszy opublikowałem pytanie, byłem zdezorientowany, Ctrl+Cże jestem SIGTERMzamiast SIGINT.

Sebastián Grignoli
źródło

Odpowiedzi:

260

Możesz użyć pakietu os / signal do obsługi sygnałów przychodzących. Ctrl+ Cto SIGINT , więc możesz użyć tego do pułapkowania 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
    }
}()

Sposób, w jaki spowodujesz zakończenie działania programu i wydrukowanie informacji, zależy wyłącznie od Ciebie.

Lily Ballard
źródło
Dzięki! Więc ... ^ C to nie SIGTERM? AKTUALIZACJA: Przepraszamy, podany link jest wystarczająco szczegółowy!
Sebastián Grignoli
19
Zamiast tego for sig := range g {możesz również użyć, <-sigchanjak w poprzedniej odpowiedzi: stackoverflow.com/questions/8403862/...
Denys Séguret
3
@dystroy: Jasne, jeśli faktycznie zamierzasz zakończyć program w odpowiedzi na pierwszy sygnał. Korzystając z pętli, możesz złapać wszystkie sygnały, jeśli zdecydujesz się nie przerywać programu.
Lily Ballard
79
Uwaga: aby to działało, musisz zbudować program. Jeśli uruchomisz program za go runpomocą konsoli i wyślesz SIGTERM przez ^ C, sygnał zostanie zapisany w kanale i program zareaguje, ale wydaje się, że niespodziewanie wypada z pętli. Dzieje się tak, ponieważ SIGRERM również się do tego nadaje go run! (To spowodowało poważne zamieszanie!)
William Pursell
5
Zauważ, że aby goroutine uzyskał czas procesora na obsługę sygnału, główny goroutine musi wywołać operację blokującą lub wywołać runtime.Goschedw odpowiednim miejscu (w głównej pętli twojego programu, jeśli ma taką)
misterbee
107

To działa:

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

źródło
1
Dla innych czytelników: Spójrz na odpowiedź @adamonduty, aby uzyskać wyjaśnienie, dlaczego chcesz złapać OS.Interrupt i syscall.SIGTERM Byłoby miło dołączyć jego wyjaśnienie w tej odpowiedzi, zwłaszcza, że ​​opublikował kilka miesięcy przed tobą.
Chris,
1
Dlaczego korzystasz z kanału nieblokującego? Czy to konieczne?
Awn
4
@ Barry dlaczego rozmiar bufora wynosi 2 zamiast 1?
pdeva
2
Oto fragment dokumentacji . „Sygnał pakietu nie blokuje wysyłania do c: osoba dzwoniąca musi upewnić się, że c ma wystarczającą ilość miejsca w buforze, aby nadążyć za oczekiwaną szybkością sygnału. W przypadku kanału używanego do powiadamiania tylko o jednej wartości sygnału wystarczający jest bufor o rozmiarze 1.”
bmdelacruz
25

Aby dodać nieco do innych odpowiedzi, jeśli faktycznie chcesz złapać SIGTERM (domyślny sygnał wysyłany przez polecenie kill), możesz użyć syscall.SIGTERMzamiast OS.Interrupt. Uwaga: interfejs syscall jest specyficzny dla systemu i może nie działać wszędzie (np. W systemie Windows). Ale ładnie działa, aby złapać oba:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
adamlamar
źródło
7
signal.NotifyFunkcja pozwala określić kilka sygnałów jednocześnie. W ten sposób możesz uprościć swój kod signal.Notify(c, os.Interrupt, syscall.SIGTERM).
jochen
Myślę, że dowiedziałem się o tym po opublikowaniu. Naprawiony!
adamlamar,
2
Co z os.Kill?
Awn
2
@Eclipse Świetne pytanie! os.Kill odpowiada się syscall.Kill, co jest sygnałem, że może być wysłana, ale nie złapać. Jest to odpowiednik polecenia kill -9 <pid>. Jeśli chcesz złapać kill <pid>i z wdziękiem zamknąć, musisz użyć syscall.SIGTERM.
adamlamar
@adamlamar Ahh, to ma sens. Dzięki!
Awn,
18

W przyjętej odpowiedzi powyżej była (w momencie publikacji) jedna lub dwie małe literówki, więc oto wyczyszczona wersja. W tym przykładzie zatrzymuję profilowanie procesora podczas odbierania 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)                                                     
  }                                                                
}()    
grawitron
źródło
2
Zauważ, że aby goroutine uzyskał czas procesora na obsługę sygnału, główny goroutine musi wywołać operację blokującą lub wywołać runtime.Goschedw odpowiednim miejscu (w głównej pętli twojego programu, jeśli ma taką)
misterbee
8

Wszystkie powyższe wydają się działać po połączeniu, ale strona sygnałów gobyexample ma naprawdę czysty i kompletny przykład przechwytywania sygnału. Warto dodać do tej listy.

zostanie napisane
źródło
0

Śmierć to prosta biblioteka, która korzysta z kanałów i grupy oczekiwania do oczekiwania na sygnały zamknięcia. Po odebraniu sygnału wywoła metodę close na wszystkich twoich strukturach, które chcesz oczyścić.

Ben Aldrich
źródło
3
Tyle wierszy kodu i zewnętrznej biblioteki zależy od tego, co można zrobić w czterech wierszach kodu? (zgodnie z przyjętą odpowiedzią)
Jacob
pozwala na równoległe czyszczenie wszystkich i automatyczne zamykanie struktur, jeśli mają standardowy interfejs zamykania.
Ben Aldrich,
0

Możesz mieć inną goroutine, która wykrywa sygnały syscall.SIGINT i syscall.SIGTERM i przekazuje je do kanału za pomocą sygnału . Możesz wysłać hak do tego goroutine za pomocą kanału i zapisać go w wycinku funkcji. Po wykryciu sygnału wyłączenia na kanale można wykonać te funkcje w wycinku. Można to wykorzystać do czyszczenia zasobów, oczekiwania na zakończenie działania goroutyn, utrwalania danych lub drukowania sum częściowych przebiegów.

Napisałem małe i proste narzędzie do dodawania i uruchamiania haków przy zamykaniu systemu. Mam nadzieję, że to może pomóc.

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

Możesz to zrobić w odroczony sposób.

przykład płynnego zamknięcia serwera:

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
źródło
0

To kolejna wersja, która działa w przypadku, gdy masz jakieś zadania do wyczyszczenia. Kod pozostawi proces czyszczenia w swojej metodzie.

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
}

na wypadek, gdybyś musiał wyczyścić główną funkcję, musisz przechwycić sygnał w głównym wątku za pomocą go func ().

Hlex
źródło
-2

Dla przypomnienia, jeśli ktoś potrzebuje sposobu na obsługę sygnałów w systemie Windows. Miałem wymóg obsługi z prog A wywołującego prog B przez os / exec, ale prog B nigdy nie był w stanie zakończyć z wdziękiem, ponieważ wysyłał sygnały przez ex. cmd.Process.Signal (syscall.SIGTERM) lub inne sygnały nie są obsługiwane w systemie Windows. Sposób, w jaki sobie poradziłem, to utworzenie pliku tymczasowego jako sygnału ex. .signal.term przez prog A i prog B musi sprawdzić, czy plik istnieje w odstępach czasowych, jeśli plik istnieje, opuści program i w razie potrzeby zajmie się czyszczeniem, jestem pewien, że istnieją inne sposoby, ale to zadziałało.

użytkownik212806
źródło