概要
よく忘れるのでここにメモメモ。。。
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
参考情報
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場