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.

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.


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

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

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

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


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


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

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


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.