Apakah mungkin untuk menangkap sinyal Ctrl + C dan menjalankan fungsi pembersihan, dengan cara "menunda"?

207

Saya ingin menangkap sinyal Ctrl+C( SIGINT) yang dikirim dari konsol dan mencetak beberapa total run parsial.

Apakah ini mungkin di Golang?

Catatan: Ketika saya pertama kali memposting pertanyaan saya bingung tentang Ctrl+Cmenjadi SIGTERMbukan SIGINT.

Sebastián Grignoli
sumber

Jawaban:

260

Anda dapat menggunakan paket os / sinyal untuk menangani sinyal yang masuk. Ctrl+ Cadalah SIGINT , jadi Anda dapat menggunakan ini untuk menjebak 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
    }
}()

Cara Anda menghentikan program dan mencetak informasi sepenuhnya tergantung pada Anda.

Lily Ballard
sumber
Terima kasih! Jadi ... ^ C, bukan SIGTERM? UPDATE: Maaf, tautan yang Anda berikan cukup detail!
Sebastián Grignoli
19
Alih-alih for sig := range g {, Anda juga dapat menggunakan <-sigchanseperti pada jawaban sebelumnya: stackoverflow.com/questions/8403862/…
Denys Séguret
3
@dystroy: Tentu, jika Anda benar-benar akan menghentikan program sebagai respons terhadap sinyal pertama. Dengan menggunakan loop Anda dapat menangkap semua sinyal jika Anda memutuskan untuk tidak menghentikan program.
Lily Ballard
79
Catatan: Anda harus benar-benar membangun program agar ini berfungsi. Jika Anda menjalankan program melalui go rundi konsol dan mengirim SIGTERM melalui ^ C, sinyal ditulis ke dalam saluran dan program merespons, tetapi tampaknya keluar dari loop secara tak terduga. Ini karena SIGRERM juga berlaku go run! (Ini membuat saya banyak kebingungan!)
William Pursell
5
Perhatikan bahwa agar goroutine mendapatkan waktu prosesor untuk menangani sinyal, goroutine utama harus memanggil operasi pemblokiran atau menelepon runtime.Goscheddi tempat yang sesuai (di loop utama program Anda, jika ada)
misterbee
107

Ini bekerja:

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

sumber
1
Untuk pembaca lain: Lihatlah jawaban @adamonduty untuk penjelasan mengapa Anda ingin menangkap os.Interrupt dan syscall.SIGTERM Akan sangat bagus untuk memasukkan penjelasannya dalam jawaban ini, terutama karena ia diposting beberapa bulan sebelum Anda.
Chris
1
Mengapa Anda menggunakan saluran yang tidak menghalangi? Apakah ini perlu?
Awn
4
@Barry mengapa ukuran buffer 2 bukannya 1?
pdeva
2
Berikut kutipan dari dokumentasi . "Sinyal paket tidak akan memblokir pengiriman ke c: pemanggil harus memastikan bahwa c memiliki ruang buffer yang cukup untuk mengikuti laju sinyal yang diharapkan. Untuk saluran yang digunakan untuk pemberitahuan hanya satu nilai sinyal, buffer ukuran 1 sudah cukup."
bmdelacruz
25

Untuk menambahkan sedikit ke jawaban lain, jika Anda benar-benar ingin menangkap SIGTERM (sinyal default yang dikirim oleh perintah kill), Anda dapat menggunakannya syscall.SIGTERMsebagai ganti os.Interrupt. Berhati-hatilah karena antarmuka syscall khusus untuk sistem dan mungkin tidak berfungsi di mana-mana (mis. Di windows). Tetapi bekerja dengan baik untuk menangkap keduanya:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
adamlamar
sumber
7
The signal.NotifyFungsi memungkinkan untuk menentukan beberapa sinyal sekaligus. Dengan demikian, Anda dapat menyederhanakan kode Anda menjadi signal.Notify(c, os.Interrupt, syscall.SIGTERM).
jochen
Saya pikir saya mengetahui hal itu setelah memposting. Tetap!
adamlamar
2
Bagaimana dengan o. Masih?
Awn
2
@Eclipse Pertanyaan bagus! os.Kill sesuai dengan syscall.Kill, yang merupakan sinyal yang dapat dikirim tetapi tidak ditangkap. Ini setara dengan perintah kill -9 <pid>. Jika Anda ingin menangkap kill <pid>dan mematikan dengan anggun, Anda harus menggunakannya syscall.SIGTERM.
adamlamar
@adamlamar Ahh, itu masuk akal. Terima kasih!
Awn
18

Ada (pada saat posting) satu atau dua kesalahan ketik kecil dalam jawaban yang diterima di atas, jadi inilah versi yang dibersihkan. Dalam contoh ini saya menghentikan profiler CPU ketika menerima 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
sumber
2
Perhatikan bahwa agar goroutine mendapatkan waktu prosesor untuk menangani sinyal, goroutine utama harus memanggil operasi pemblokiran atau menelepon runtime.Goscheddi tempat yang sesuai (di loop utama program Anda, jika ada)
misterbee
8

Semua hal di atas tampaknya berfungsi ketika disambung, tetapi halaman sinyal gobyexample memiliki contoh penangkapan sinyal yang benar-benar bersih dan lengkap. Layak ditambahkan ke daftar ini.

akan dituliskan
sumber
0

Kematian adalah perpustakaan sederhana yang menggunakan saluran dan grup tunggu untuk menunggu sinyal penutupan. Setelah sinyal diterima, maka akan memanggil metode penutupan pada semua struct Anda yang ingin Anda bersihkan.

Ben Aldrich
sumber
3
Begitu banyak baris kode dan ketergantungan perpustakaan eksternal untuk melakukan apa yang dapat dilakukan dalam empat baris kode? (sesuai jawaban yang diterima)
Yakub
ini memungkinkan Anda untuk melakukan pembersihan semuanya secara paralel dan secara otomatis menutup struct jika mereka memiliki antarmuka standar dekat.
Ben Aldrich
0

Anda dapat memiliki goroutine berbeda yang mendeteksi sinyal syscall.SIGINT dan syscall.SIGTERM dan menyampaikannya ke saluran menggunakan sinyal.Notify . Anda dapat mengirim kail ke goroutine itu menggunakan saluran dan menyimpannya dalam irisan fungsi. Ketika sinyal shutdown terdeteksi pada saluran, Anda dapat menjalankan fungsi-fungsi tersebut di slice. Ini dapat digunakan untuk membersihkan sumber daya, menunggu goroutine berjalan untuk menyelesaikan, bertahan data, atau mencetak total menjalankan parsial.

Saya menulis sebuah utilitas kecil dan sederhana untuk menambah dan menjalankan hook saat shutdown. Semoga bisa membantu.

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

Anda dapat melakukan ini dengan cara 'menunda'.

contoh untuk mematikan server dengan anggun:

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
sumber
0

Ini adalah versi lain yang berfungsi jika Anda memiliki beberapa tugas untuk dibersihkan. Kode akan meninggalkan proses pembersihan dalam metode mereka.

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
}

jika Anda perlu membersihkan fungsi utama, Anda perlu menangkap sinyal di utas utama menggunakan go func () juga.

Hlex
sumber
-2

Sekadar catatan jika seseorang membutuhkan cara untuk menangani sinyal di Windows. Saya memiliki persyaratan untuk menangani dari prog A memanggil prog B melalui os / exec tetapi prog B tidak pernah dapat mengakhiri dengan anggun karena mengirim sinyal melalui ex. cmd.Process.Signal (syscall.SIGTERM) atau sinyal lain tidak didukung pada Windows. Cara saya menangani adalah dengan membuat file temp sebagai ex sinyal. .signal.term melalui prog A dan prog B perlu memeriksa apakah file itu ada pada interval dasar, jika ada file itu akan keluar dari program dan menangani pembersihan jika diperlukan, saya yakin ada cara lain tetapi ini berhasil.

pengguna212806
sumber