いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-189 (GoでJavaVMみたいにSIGQUITでスレッドダンプを採取できるようにする)

概要

以下は自分用のメモです。よく忘れるので、ここにメモメモ。。

常に必要じゃないんですが、たまにシグナルをちゃんとトラップしていろいろ動いてくれるアプリを作るときがあります。

そんなときに、JavaVMみたいにSIGQUIT受けたらスレッドダンプ(Goの場合はゴルーチンダンプ)を出力できたら便利なときがあります。

サンプル

// OVERVIEW
//
// JAVA VM は SIGQUIT を受け取るとスレッドダンプを出力します。
// Goで同じような動きをするサンプルです。
//
// REFERENCE
//
//   - https://stackoverflow.com/a/27398062

package main

import (
    "context"
    "log"
    "os"
    "os/signal"
    "runtime"
    "syscall"
    "time"
)

const (
    Interval  = 1 * time.Second
    TimeLimit = 10 * time.Second
)

var (
    appLog  = log.New(os.Stdout, "", 0)
    tickLog = log.New(os.Stdout, "", log.Ltime)
    dumpLog = log.New(os.Stderr, "", 0)
)

func main() {
    // ------------------------------------------------
    // Overview
    // ------------------------------------------------
    // [x] 10秒たったらプログラム終了
    // [x] SIGINTを受けたらプログラム終了
    // [x] SIGQUITを受けたらスレッドダンプを出力
    // [x] 1秒毎の経過ログを出力
    // ------------------------------------------------

    var (
        rootCtx          = context.Background()
        mainCtx, mainCxl = context.WithTimeout(rootCtx, TimeLimit)
    )
    defer mainCxl()

    var (
        sigIntCh  = make(chan os.Signal, 1)
        sigQuitCh = make(chan os.Signal, 1)
    )
    defer close(sigIntCh)
    defer close(sigQuitCh)

    signal.Notify(sigIntCh, syscall.SIGINT)
    signal.Notify(sigQuitCh, syscall.SIGQUIT)

    var (
        ticker = time.NewTicker(Interval)
        count  = 0
    )
    defer ticker.Stop()

LOOP:
    for {
        select {
        case <-mainCtx.Done():
            appLog.Println("Timed out")
            break LOOP
        case <-sigIntCh:
            appLog.Println(" >>> Recv SIGINT")
            break LOOP
        case <-sigQuitCh:
            buf := make([]byte, 1<<25)
            dumpLog.Printf(" >>> *** DUMP ***\n\n%s\n", buf[:runtime.Stack(buf, true)])
        case <-ticker.C:
            count++
            tickLog.Println(count)
        }
    }

    appLog.Println("DONE")
}

実行すると以下のようになります。

適当なタイミングでSIGQUIT送ってみてみると、ちゃんとダンプ出てますね。

Linuxでは、SIGQUITは Ctrl+\ で送れます。

gitpod /workspace/try-golang (master) $ go run examples/singleapp/sigquit_like_javavm/main.go
04:34:50 1
04:34:51 2
04:34:52 3
^\ >>> *** DUMP ***

goroutine 1 [running]:
main.main()
        /workspace/try-golang/examples/singleapp/sigquit_like_javavm/main.go:76 +0x2b5

goroutine 17 [syscall]:
os/signal.signal_recv()
        /home/gitpod/go/src/runtime/sigqueue.go:169 +0x98
os/signal.loop()
        /home/gitpod/go/src/os/signal/signal_unix.go:24 +0x19
created by os/signal.Notify.func1.1
        /home/gitpod/go/src/os/signal/signal.go:151 +0x2c

04:34:53 4
04:34:54 5
04:34:55 6
^\ >>> *** DUMP ***

goroutine 1 [running]:
main.main()
        /workspace/try-golang/examples/singleapp/sigquit_like_javavm/main.go:76 +0x2b5

goroutine 17 [syscall]:
os/signal.signal_recv()
        /home/gitpod/go/src/runtime/sigqueue.go:169 +0x98
os/signal.loop()
        /home/gitpod/go/src/os/signal/signal_unix.go:24 +0x19
created by os/signal.Notify.func1.1
        /home/gitpod/go/src/os/signal/signal.go:151 +0x2c

04:34:56 7
04:34:57 8
^C >>> Recv SIGINT
DONE

参考情報

stackoverflow.com

stackoverflow.com


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。