Ctrl + C信号をキャプチャし、「遅延」方式でクリーンアップ機能を実行することは可能ですか?


207

コンソールから送信されたCtrl+CSIGINT)信号をキャプチャして、一部の実行合計を出力します。

これはGolangで可能ですか?

注:初めて質問を投稿したとき、の代わりに混乱してCtrl+CSIGTERMましたSIGINT

回答:


260

os / signalパッケージを使用して、着信信号を処理できます。Ctrl+ CSIGINTなので、これをトラップに使用できます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
    }
}()

プログラムを終了して情報を出力する方法は、完全にあなた次第です。


ありがとう!それで... ^ CそれはSIGTERMではありませんか?アップデート:申し訳ありませんが、あなたが提供したリンクは十分に詳細です!
セバスティアンGrignoli

19
代わりにfor sig := range g {あなたが使用することもでき、<-sigchanこの前の回答のように:stackoverflow.com/questions/8403862/...
デニス・Séguret

3
@dystroy:もちろん、最初のシグナルに応答してプログラムを実際に終了する場合は、ループを使用すると、プログラムを終了しないことにした場合に、すべてのシグナルをキャッチできます。
リリーバラード

79
注:これを機能させるには、実際にプログラムをビルドする必要があります。go runコンソールでを介してプログラムを実行し、^ Cを介してSIGTERMを送信すると、信号がチャネルに書き込まれ、プログラムが応答しますが、予期せずループから脱落したように見えます。これは、シグレムも行くからgo runです!(これは私にかなりの混乱を引き起こしています!)
ウィリアム・パーセル

5
ゴルーチンがシグナルを処理するためのプロセッサー時間を取得するためには、メインのゴルーチンがブロッキング操作を呼び出すかruntime.Gosched、適切な場所(プログラムのメインループにある場合)で呼び出す必要があります
misterbee

107

これは機能します:

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
他の読者の場合:なぜos.Interruptとsyscall.SIGTERMをキャッチするのかについての説明は、@ adamondutyの回答を参照してください。特に彼が数か月前に投稿したので、彼の説明をこの回答に含めておくとよいでしょう。
クリス

1
なぜ非ブロッキングチャネルを使用しているのですか?これは必要ですか?
オーニング

4
@Barryはなぜバッファサイズが1ではなく2なのですか?
pdeva

2
ドキュメントからの抜粋です。「パッケージ信号はcへの送信をブロックしません。呼び出し元は、cが予想される信号速度に追いつくのに十分なバッファースペースを持っていることを確認する必要があります。1つの信号値の通知に使用されるチャネルの場合、サイズ1のバッファーで十分です。」
bmdelacruz

25

他の回答に少し追加して、実際にSIGTERM(killコマンドによって送信されるデフォルトのシグナル)をキャッチしたい場合syscall.SIGTERMは、os.Interruptの代わりに使用できます。syscallインターフェースはシステム固有であり、どこでも機能しない場合があることに注意してください(例:Windows)。しかし、両方をキャッチすることはうまくいきます:

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

7
このsignal.Notify関数を使用すると、一度に複数の信号を指定できます。したがって、コードをに簡略化できますsignal.Notify(c, os.Interrupt, syscall.SIGTERM)
jochen 2013

投稿後に見つけたと思います。修繕!
adamlamar 2013

2
os.Killはどうですか?
オープニング

2
@Eclipse素晴らしい質問です!os.Kill に対応しますsyscall.Kill。これは、送信できるがキャッチされない信号です。コマンドと同等kill -9 <pid>です。キャッチkill <pid>して正常にシャットダウンする場合は、を使用する必要がありますsyscall.SIGTERM
adamlamar 2017

@adamlamarああ、それは理にかなっています。ありがとう!
AWN

18

上記の受け入れられた回答に(投稿の時点で)1つまたは2つの小さなタイプミスがあったので、これがクリーンアップされたバージョンです。この例では、Ctrl+ を受け取ったときにCPUプロファイラーを停止しています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
ゴルーチンがシグナルを処理するためのプロセッサー時間を取得するためには、メインのゴルーチンがブロッキング操作を呼び出すかruntime.Gosched、適切な場所(プログラムのメインループにある場合)で呼び出す必要があります
misterbee


0

は、チャネルと待機グループを使用してシャットダウン信号を待機する単純なライブラリです。シグナルが受信されると、クリーンアップするすべての構造体でcloseメソッドを呼び出します。


3
非常に多くのコード行と外部ライブラリは、4行のコードで実行できることを実行するために依存していますか?(承認された回答に従って)
Jacob

それはあなたが並行してそれらのすべてのクリーンアップを行うことを可能にし、それらが標準のクローズインターフェースを持っている場合は自動的に構造体をクローズします。
Ben Aldrich、

0

syscall.SIGINTおよびsyscall.SIGTERMシグナルを検出し、signal.Notifyを使用してそれらをチャネルに中継する別のgoroutineを使用できます。チャネルを使用してそのゴルーチンにフックを送信し、関数スライスに保存できます。シャットダウン信号がチャネルで検出されると、それらの機能をスライスで実行できます。これは、リソースのクリーンアップ、実行中のゴルーチンの終了の待機、データの永続化、または部分的な実行合計の印刷に使用できます。

シャットダウン時にフックを追加して実行するための小さくてシンプルなユーティリティを作成しました。お役に立てれば幸いです。

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

これは「延期」して行うことができます。

サーバーを正常にシャットダウンする例:

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

これは、クリーンアップするタスクがある場合に機能する別のバージョンです。コードはメソッドにクリーンアッププロセスを残します。

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
}

メイン関数をクリーンアップする必要がある場合は、go func()を使用してメインスレッドで信号をキャプチャする必要があります。


-2

誰かがWindowsで信号を処理する方法を必要とする場合の記録のためだけに。os / execを介してprog Aからprog Bを呼び出すことを処理する必要がありましたが、exを介して信号を送信するため、prog Bは正常に終了できませんでした。cmd.Process.Signal(syscall.SIGTERM)またはその他のシグナルは、Windowsではサポートされていません。私が扱った方法は、一時ファイルをシグナルexとして作成することです。prog Aとprog Bを介した.signal.termは、そのファイルが間隔ベースに存在するかどうかを確認する必要があります。ファイルが存在する場合、プログラムを終了し、必要に応じてクリーンアップを処理します。他の方法があると確信していますが、これでうまくいきました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.