いろいろ備忘録日記

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

Goメモ-164 (SIGQUITを送ってgoroutineのダンプを取る)

概要

よく忘れるのでここにメモメモ。。。

goroutineを含むプログラムを書いていて、たまにうんともすんとも言わないようになってしまうことがあります。

まあ、大抵はバグなのですが、デバッガも手元にない場合は スレッドダンプ とってしまった方が楽なときもあります。

そういうときのやり方です。ちなみに Java で スレッドダンプ とるときとやり方は同じです。

サンプル

こんな感じのプログラムがあるとします。

package main

func main() {
    ch := make(chan struct{}, 1)
    go func() {
        read(ch)
    }()
    write(ch)
}

func read(ch <-chan struct{}) {
    for {
        <-ch
    }
}

func write(ch chan<- struct{}) {
    for {
        ch <- struct{}{}
    }
}

見ての通り、このプログラムは起動すると永遠に read と write でデータをやり取りし続けて何も制御出来ません。

ログも出していないので、何しているのかもサッパリw

こんな時にスレッドダンプを取ってしまいましょうって話です。

キーボードからSIGQUITを送る

キーボードからSIGQUITを送る場合、デフォルトのキーバインドは Ctrl+\ です。

こんな感じになります。

# ビルド
vscode ➜ /workspaces/go-playground $ go build

# 起動
vscode ➜ /workspaces/go-playground $ ./go-playground 

#
# ここで Ctrl+\ を押す
# 


^\SIGQUIT: quit
PC=0x453cdd m=0 sigcode=128

goroutine 0 [idle]:
runtime.usleep()
        /usr/local/go/src/runtime/sys_linux_amd64.s:146 +0x3d
runtime.runqgrab(0xc00002c800, 0xc00002a5f8, 0x2, 0x1)
        /usr/local/go/src/runtime/proc.go:6155 +0x85
runtime.runqsteal(0xc00002a000, 0x408e73, 0x1e)
        /usr/local/go/src/runtime/proc.go:6190 +0x3d
runtime.stealWork(0xc00002a000)
        /usr/local/go/src/runtime/proc.go:3071 +0x292
runtime.findrunnable()
        /usr/local/go/src/runtime/proc.go:2779 +0x20c
runtime.schedule()
        /usr/local/go/src/runtime/proc.go:3367 +0x239
runtime.park_m(0xc0000001a0)
        /usr/local/go/src/runtime/proc.go:3516 +0x14d
runtime.mcall()
        /usr/local/go/src/runtime/asm_amd64.s:307 +0x43

goroutine 1 [chan send]:
main.write(...)
        /workspaces/go-playground/main.go:19
main.main()
        /workspaces/go-playground/main.go:8 +0x7f

goroutine 17 [runnable]:
main.read(...)
        /workspaces/go-playground/main.go:13
main.main.func1()
        /workspaces/go-playground/main.go:6 +0x25
created by main.main
        /workspaces/go-playground/main.go:5 +0x6f

rax    0xfffffffffffffffc
rbx    0xc00002a5f8
rcx    0x453cdd
rdx    0x0
rdi    0x7ffe3ad558c8
rsi    0x0
rbp    0x7ffe3ad558d8
rsp    0x7ffe3ad558c8
r8     0x0
r9     0xf8
r10    0xc000014048
r11    0x206
r12    0xc00002a000
r13    0x0
r14    0x4bb800
r15    0x7f1504efad03
rip    0x453cdd
rflags 0x206
cs     0x33
fs     0x0
gs     0x0

ダンプが出ましたね。中盤みると、goroutineのダンプが取れています。

killコマンドでSIGQUITを送る

プログラムがバックグラウンドで動作している場合もあります。

そのときは kill コマンドで送ります。

vscode ➜ /workspaces/go-playground $ ./go-playground &
[1] 7809
vscode ➜ /workspaces/go-playground $ kill -SIGQUIT 7809
SIGQUIT: quit
PC=0x454261 m=0 sigcode=0

goroutine 0 [idle]:
runtime.futex()
        /usr/local/go/src/runtime/sys_linux_amd64.s:519 +0x21
runtime.futexsleep(0xc000031800, 0x408e73, 0x10000001e)
        /usr/local/go/src/runtime/os_linux.go:44 +0x36
runtime.notesleep(0x4bbaf0)
        /usr/local/go/src/runtime/lock_futex.go:160 +0x87
runtime.mPark()
        /usr/local/go/src/runtime/proc.go:1441 +0x2a
runtime.stopm()
        /usr/local/go/src/runtime/proc.go:2408 +0x78
runtime.findrunnable()
        /usr/local/go/src/runtime/proc.go:2984 +0x865
runtime.schedule()
        /usr/local/go/src/runtime/proc.go:3367 +0x239
runtime.park_m(0xc0000001a0)
        /usr/local/go/src/runtime/proc.go:3516 +0x14d
runtime.mcall()
        /usr/local/go/src/runtime/asm_amd64.s:307 +0x43

goroutine 1 [runnable]:
main.write(...)
        /workspaces/go-playground/main.go:19
main.main()
        /workspaces/go-playground/main.go:8 +0x7f

goroutine 5 [chan receive]:
main.read(...)
        /workspaces/go-playground/main.go:13
main.main.func1()
        /workspaces/go-playground/main.go:6 +0x25
created by main.main
        /workspaces/go-playground/main.go:5 +0x6f

rax    0xca
rbx    0x0
rcx    0x454263
rdx    0x0
rdi    0x4bbaf0
rsi    0x80
rbp    0x7ffdd132caa8
rsp    0x7ffdd132ca60
r8     0x0
r9     0x0
r10    0x0
r11    0x286
r12    0x0
r13    0x0
r14    0x4bb800
r15    0x7f307be95106
rip    0x454261
rflags 0x286
cs     0x33
fs     0x0
gs     0x0
[1]+  Exit 2                  ./go-playground

killコマンドの部分は pkill を使っても同じです。

$ pkill --signal SIGQUIT -f ./go-playground

DockerコンテナにSIGQUIT送る

最近では、プログラムはDockerコンテナ上で動いていることも多いですね。

その場合は、Dockerコンテナに対して SIGQUIT を送ります。

以下のDockerfileがあるとします。

FROM debian:bullseye-slim

WORKDIR /app
COPY ./go-playground /app/App

ENTRYPOINT [ "/app/App" ]

んで、イメージをビルドしてコンテナを走らせておきます。

vscode ➜ /workspaces/go-playground $ docker image build -t myapp -f Dockerfile ${PWD}
[+] Building 1.9s (8/8) FINISHED                                                                                                                       
 => [internal] load build definition from Dockerfile                                                                                              0.0s
 => => transferring dockerfile: 37B                                                                                                               0.0s
 => [internal] load .dockerignore                                                                                                                 0.0s
 => => transferring context: 2B                                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/debian:bullseye-slim                                                                           1.8s
 => [1/3] FROM docker.io/library/debian:bullseye-slim@sha256:dddc0f5f01db7ca3599fd8cf9821ffc4d09ec9d7d15e49019e73228ac1eee7f9                     0.0s
 => [internal] load build context                                                                                                                 0.0s
 => => transferring context: 1.16MB                                                                                                               0.0s
 => CACHED [2/3] WORKDIR /app                                                                                                                     0.0s
 => CACHED [3/3] COPY ./go-playground /app/App                                                                                                    0.0s
 => exporting to image                                                                                                                            0.0s
 => => exporting layers                                                                                                                           0.0s
 => => writing image sha256:5483bdb4605e092923ec96e1e0a6b0a3ba21a8436972ee2482f3f708f8e430da                                                      0.0s
 => => naming to docker.io/library/myapp

vscode ➜ /workspaces/go-playground $ docker container run -dit --rm --name myapp001 myapp
00fee46c393f6cebc3f4defd97e072d74fa563f6fce636295982982f35f8d1c4

vscode ➜ /workspaces/go-playground $ docker container list
CONTAINER ID   IMAGE                                                             COMMAND                  CREATED          STATUS          PORTS     NAMES
00fee46c393f   myapp                                                             "/app/App"               12 seconds ago   Up 10 seconds             myapp001

んで、コンテナのログを見るためにもう一つターミナルを開いて、tail -f状態にしておきます。

$ docker container logs myapp001 -f

んで、元のターミナルからSIGQUITを送ります。

vscode ➜ /workspaces/go-playground $ docker container kill --signal=SIGQUIT myapp001
myapp001

そうすると、さっきログを表示状態にしていたターミナルに出力が出ています。

$ docker container logs myapp001 -f
SIGQUIT: quit
PC=0x454261 m=0 sigcode=0

goroutine 0 [idle]:
runtime.futex()
        /usr/local/go/src/runtime/sys_linux_amd64.s:519 +0x21
runtime.futexsleep(0xc000021800, 0x408e73, 0x10000001e)
        /usr/local/go/src/runtime/os_linux.go:44 +0x36
runtime.notesleep(0x4bbaf0)
        /usr/local/go/src/runtime/lock_futex.go:160 +0x87
runtime.mPark()
        /usr/local/go/src/runtime/proc.go:1441 +0x2a
runtime.stopm()
        /usr/local/go/src/runtime/proc.go:2408 +0x78
runtime.findrunnable()
        /usr/local/go/src/runtime/proc.go:2984 +0x865
runtime.schedule()
        /usr/local/go/src/runtime/proc.go:3367 +0x239
runtime.park_m(0xc0000011e0)
        /usr/local/go/src/runtime/proc.go:3516 +0x14d
runtime.mcall()
        /usr/local/go/src/runtime/asm_amd64.s:307 +0x43

goroutine 1 [chan send]:
main.write(...)
        /workspaces/go-playground/main.go:19
main.main()
        /workspaces/go-playground/main.go:8 +0x7f

goroutine 5 [runnable]:
main.read(...)
        /workspaces/go-playground/main.go:13
main.main.func1()
        /workspaces/go-playground/main.go:6 +0x25
created by main.main
        /workspaces/go-playground/main.go:5 +0x6f

rax    0xca
rbx    0x0
rcx    0x454263
rdx    0x0
rdi    0x4bbaf0
rsi    0x80
rbp    0x7ffee9eb11b8
rsp    0x7ffee9eb1170
r8     0x0
r9     0x0
r10    0x0
r11    0x286
r12    0x0
r13    0x0
r14    0x4bb800
r15    0x7f0eab316a06
rip    0x454261
rflags 0x286
cs     0x33
fs     0x0
gs     0x0

参考情報

evilmartians.com


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

  • いろいろ備忘録日記まとめ

devlights.github.io

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

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com