[インデックス 16982] ファイルの概要
このコミットは、Goランタイムのトレースバック出力に、ゴルーチンが「誰によって作成されたか」という情報(created by
)を追加する変更です。これにより、特に実行中のゴルーチン(running
状態のゴルーチン)のデバッグ時において、そのゴルーチンの起源を特定しやすくなります。
コミット
commit c33d49002069e798b33d0de42d3eb5073aef7c0b
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Thu Aug 1 19:28:38 2013 +0400
runtime: print "created by" for running goroutines in traceback
This allows to at least determine goroutine "identity".
Now it looks like:
goroutine 12 [running]:
goroutine running on other thread; stack unavailable
created by testing.RunTests
src/pkg/testing/testing.go:440 +0x88e
R=golang-dev, r, rsc
CC=golang-dev
https://golang.org/cl/12248043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c33d49002069e798b33d0de42d3eb5073aef7c0b
元コミット内容
このコミットは、Goランタイムのトレースバック機能に、実行中のゴルーチン(running
状態)の「作成元」(created by
)情報を表示する機能を追加します。これにより、スタックトレースが利用できない場合でも、ゴルーチンの「識別情報」を少なくとも特定できるようになります。
変更後の出力例は以下のようになります。
goroutine 12 [running]:
goroutine running on other thread; stack unavailable
created by testing.RunTests
src/pkg/testing/testing.go:440 +0x88e
この変更は、golang-dev
メーリングリスト、r
、rsc
によってレビューされ、golang.org/cl/12248043
で議論されました。
変更されたファイルは以下の通りです。
src/pkg/runtime/proc.c
src/pkg/runtime/runtime.h
src/pkg/runtime/traceback_arm.c
src/pkg/runtime/traceback_x86.c
合計で4つのファイルが変更され、19行が追加、15行が削除されました。
変更の背景
Go言語のプログラムがクラッシュしたり、デッドロックに陥ったり、あるいは単にデバッグ目的でスタックトレースが出力される際、各ゴルーチンの状態とスタック情報が提供されます。しかし、running
状態のゴルーチン、特に別のOSスレッドで実行中のゴルーチンについては、そのスタック情報が常に利用できるとは限りませんでした。このような場合、トレースバックは「goroutine running on other thread; stack unavailable
」といったメッセージを表示するだけで、そのゴルーチンが何のために存在し、どこで生成されたのかを特定するのが困難でした。
このコミットの背景には、このような「スタックが利用できない」ゴルーチンのデバッグを容易にするという明確な目的があります。ゴルーチンの「作成元」情報を提供することで、開発者は問題のあるゴルーチンがプログラムのどの部分で起動されたのかを迅速に把握できるようになり、デバッグの効率が大幅に向上します。特に、テストフレームワーク(例: testing.RunTests
)など、多くのゴルーチンを内部的に生成するようなシステムでは、この情報は非常に有用です。
前提知識の解説
このコミットを理解するためには、以下のGo言語のランタイムとデバッグに関する基本的な知識が必要です。
-
ゴルーチン (Goroutine): Go言語における軽量な実行スレッドです。OSのスレッドとは異なり、Goランタイムによって管理され、非常に低コストで生成・管理できます。
go
キーワードを使って関数呼び出しの前に記述することで、新しいゴルーチンが起動されます。 -
スタックトレース (Stack Trace) / トレースバック (Traceback): プログラムがクラッシュしたり、特定のデバッグポイントに到達したりした際に、現在実行中の関数の呼び出し履歴(スタック)を表示するものです。これにより、プログラムの実行パスを遡り、問題の原因を特定するのに役立ちます。Go言語では、パニック発生時や
runtime.Stack()
関数呼び出し時などにトレースバックが出力されます。 -
ゴルーチンの状態 (Goroutine States): Goランタイムは、ゴルーチンを様々な状態(例:
running
,runnable
,syscall
,waiting
など)で管理します。running
: 現在OSスレッド上で実行中のゴルーチン。runnable
: 実行可能だが、現在OSスレッドが割り当てられていないゴルーチン。syscall
: システムコールを実行中のゴルーチン。waiting
: チャネル操作、ロック、タイマーなどで待機中のゴルーチン。running
状態のゴルーチンは、別のOSスレッドで実行されている場合、そのスタック情報がランタイムから直接アクセスできないことがあります。
-
proc.c
,runtime.h
,traceback_arm.c
,traceback_x86.c
: これらはGoランタイムのC言語で書かれた部分のソースファイルです。proc.c
: プロセス管理、スケジューラ、ゴルーチン管理など、ランタイムのコアな部分を扱います。runtime.h
: ランタイムのデータ構造や関数プロトタイプが定義されています。traceback_arm.c
,traceback_x86.c
: それぞれARMアーキテクチャとx86アーキテクチャ向けのスタックトレース生成ロジックが含まれています。アーキテクチャ固有のレジスタ操作やスタックフレームの解析を行うため、異なるファイルに分かれています。
-
G
構造体: Goランタイム内部でゴルーチンを表すデータ構造です。各ゴルーチンには一意のID(goid
)が割り当てられ、その状態、スタックポインタ、プログラムカウンタなどの情報が格納されています。特に、gp->gopc
はゴルーチンが作成された時点のプログラムカウンタ(PC)を指します。
技術的詳細
このコミットの技術的詳細を掘り下げると、主に以下の点が挙げられます。
-
runtime·printcreatedby(G* gp)
関数の導入と公開: 以前はstatic void printcreatedby(G *gp)
として定義されていた関数が、void runtime·printcreatedby(G *gp)
として公開され、runtime.h
にプロトタイプが追加されました。これにより、ランタイムの他の部分からこの関数を呼び出して、ゴルーチンの作成元情報を出力できるようになりました。 この関数は、引数としてゴルーチン構造体G* gp
を受け取ります。 -
gp->gopc
の利用:runtime·printcreatedby
関数は、ゴルーチン構造体gp
のgopc
フィールドを利用します。gopc
は、そのゴルーチンがgo
キーワードによって起動された時点のプログラムカウンタ(PC)を保持しています。このPC値を使ってruntime·findfunc(pc)
により対応する関数情報(Func
構造体)を取得し、runtime·funcname(f)
で関数名を取得します。 -
スタックフレームの表示条件
runtime·showframe(f, gp)
:runtime·printcreatedby
関数内で、runtime·showframe(f, gp)
という条件が追加されました。これは、特定のフレーム(この場合はゴルーチン作成元のフレーム)を表示すべきかどうかを判断するためのものです。これにより、不要な内部フレームの表示を抑制し、より意味のある情報のみを出力するようになります。 -
メインゴルーチン (
goid 1
) の除外:runtime·printcreatedby
関数は、gp->goid != 1
という条件を含んでいます。これは、メインゴルーチン(IDが1のゴルーチン)については「created by」情報を表示しないことを意味します。メインゴルーチンはプログラムのエントリポイントであり、通常は他のゴルーチンによって作成されるものではないため、この情報は不要と判断されたと考えられます。 -
PCQuantum
の導入:traceback_arm.c
とtraceback_x86.c
のruntime·printcreatedby
関数内で、tracepc -= sizeof(uintptr);
またはtracepc--;
となっていた部分が、tracepc -= PCQuantum;
に変更されました。PCQuantum
は、Goの命令ポインタ(PC)の増分単位を表すランタイム定数です。これはアーキテクチャによって異なり、例えばx86では1バイト、ARMでは4バイトなどとなります。この変更は、funcline
関数に渡すPC値を正確に調整し、正しいソースコードの行番号を取得するために重要です。CALL
命令の直前のPCを指すように調整することで、funcline
が正確な行情報を返すことを保証します。 -
runtime·tracebackothers
での呼び出し:src/pkg/runtime/proc.c
のruntime·tracebackothers
関数内で、Grunning
状態のゴルーチンに対して、スタックが利用できない場合にruntime·printcreatedby(gp)
が呼び出されるようになりました。 以前はGrunning
状態のゴルーチンに対しては「goroutine running on other thread; stack unavailable
」というメッセージのみが表示されていましたが、この変更により、そのメッセージの後に「created by ...
」の情報が追加されるようになりました。
これらの変更により、Goランタイムは、デバッグ時にゴルーチンの起源に関するより豊富な情報を提供できるようになり、特にスタックが利用できない状況でのデバッグ能力が向上しました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下の通りです。
-
src/pkg/runtime/proc.c
:runtime·tracebackothers
関数内で、Grunning
状態のゴルーチンに対する処理が変更されました。--- a/src/pkg/runtime/proc.c +++ b/src/pkg/runtime/proc.c @@ -267,9 +267,10 @@ runtime·tracebackothers(G *me) continue; runtime·printf("\n"); runtime·goroutineheader(gp); - if(gp->status == Grunning) + if(gp->status == Grunning) { runtime·printf("\tgoroutine running on other thread; stack unavailable\n"); - else + runtime·printcreatedby(gp); + } else runtime·traceback(gp->sched.pc, gp->sched.sp, gp->sched.lr, gp); } }
Grunning
状態のゴルーチンに対して、runtime·printcreatedby(gp)
が呼び出されるようになりました。 -
src/pkg/runtime/runtime.h
:runtime·printcreatedby
関数のプロトタイプが追加され、外部から呼び出し可能になりました。--- a/src/pkg/runtime/runtime.h +++ b/src/pkg/runtime/runtime.h @@ -1039,6 +1039,7 @@ Hchan*\truntime·makechan_c(ChanType*, int64); void\truntime·chansend(ChanType*, Hchan*, byte*, bool*, void*);\n void\truntime·chanrecv(ChanType*, Hchan*, byte*, bool*, bool*);\n bool\truntime·showframe(Func*, G*);\n +void\truntime·printcreatedby(G*);\n \n void\truntime·ifaceE2I(InterfaceType*, Eface, Iface*);\n
-
src/pkg/runtime/traceback_arm.c
およびsrc/pkg/runtime/traceback_x86.c
:printcreatedby
関数がstatic
からvoid runtime·printcreatedby(G *gp)
に変更され、runtime·showframe(f, gp)
の条件が追加され、PCQuantum
が使用されるようになりました。src/pkg/runtime/traceback_arm.c
の変更例:--- a/src/pkg/runtime/traceback_arm.c +++ b/src/pkg/runtime/traceback_arm.c @@ -193,20 +193,21 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,\n return n;\t\t\n }\n \n -static void\n -printcreatedby(G *gp)\n +void\n +runtime·printcreatedby(G *gp)\n {\n int32 line;\n uintptr pc, tracepc;\n Func *f;\n String file;\n \n -\tif((pc = gp->gopc) != 0 && (f = runtime·findfunc(pc)) != nil\n -\t\t&& runtime·showframe(f, gp) && gp->goid != 1) {\n +\t// Show what created goroutine, except main goroutine (goid 1).\n +\tif((pc = gp->gopc) != 0 && (f = runtime·findfunc(pc)) != nil &&\n +\t\truntime·showframe(f, gp) && gp->goid != 1) {\n runtime·printf("created by %s\n", runtime·funcname(f));\n tracepc = pc;\t// back up to CALL instruction for funcline.\n if(pc > f->entry)\n -\t\t\ttracepc -= sizeof(uintptr);\n +\t\t\ttracepc -= PCQuantum;\n line = runtime·funcline(f, tracepc, &file);\n runtime·printf("\t%S:%d", file, line);\n if(pc > f->entry)\n @@ -229,7 +230,7 @@ runtime·traceback(uintptr pc, uintptr sp, uintptr lr, G *gp)\n // If that means we print nothing at all, repeat forcing all frames printed.\n if(runtime·gentraceback(pc, sp, lr, gp, 0, nil, 100, nil, nil, false) == 0)\n runtime·gentraceback(pc, sp, lr, gp, 0, nil, 100, nil, nil, true);\n -\tprintcreatedby(gp);\n +\truntime·printcreatedby(gp);\n }\n ``` `runtime·traceback`関数からも`runtime·printcreatedby(gp)`が呼び出されるようになりました。
コアとなるコードの解説
このコミットの核となる変更は、Goランタイムがゴルーチンのトレースバックを生成する際に、そのゴルーチンがどこで作成されたかという情報を追加するロジックです。
-
runtime·tracebackothers
の役割: この関数は、現在実行中のゴルーチン(me
)以外のすべてのゴルーチンについてトレースバックを生成する役割を担っています。デバッグ時やパニック発生時に、システム内のすべてのゴルーチンの状態を把握するために呼び出されます。 -
Grunning
状態の特殊性:if(gp->status == Grunning)
の条件は、対象のゴルーチンが現在OSスレッド上で実行中であることを示します。このようなゴルーチンは、そのスタックが別のOSスレッドに存在するため、Goランタイムが直接スタックを読み取ることが困難な場合があります。そのため、以前は「goroutine running on other thread; stack unavailable
」という一般的なメッセージしか表示されませんでした。 -
runtime·printcreatedby(gp)
の呼び出し: このコミットの主要な変更は、Grunning
状態のゴルーチンに対して、スタックが利用できない場合でもruntime·printcreatedby(gp)
を呼び出すようにした点です。これにより、スタックトレース自体は得られなくても、ゴルーチンの作成元という重要なコンテキスト情報が得られるようになりました。 -
runtime·printcreatedby
の内部動作:gp->gopc
を使用して、ゴルーチンが作成された時点のプログラムカウンタ(PC)を取得します。runtime·findfunc(pc)
でそのPCに対応する関数情報(Func
構造体)を取得します。runtime·showframe(f, gp)
で、このフレームを表示すべきかどうかを判断します。これは、ランタイム内部のヘルパー関数などをフィルタリングし、ユーザーにとって意味のある情報のみを表示するためのものです。gp->goid != 1
で、メインゴルーチン(ID 1)を除外します。runtime·printf("created by %s\n", runtime·funcname(f));
で、作成元の関数名を出力します。tracepc -= PCQuantum;
でPCを調整し、runtime·funcline(f, tracepc, &file);
で正確なファイル名と行番号を取得します。PCQuantum
は、Goの命令ポインタの増分単位であり、アーキテクチャに依存するオフセットを正確に計算するために重要です。- 最後に、ファイル名、行番号、および関数エントリからのオフセット(
+0x...
)を出力します。
この一連の変更により、Goランタイムのデバッグ出力がより詳細になり、特にデッドロックやハングアップなどの問題でスタックが利用できないrunning
状態のゴルーチンを追跡する際に、その起源を特定する手助けとなります。
関連リンク
- Go言語のゴルーチンに関する公式ドキュメントやチュートリアル
- Goランタイムのソースコード(特に
src/runtime
ディレクトリ) - Go言語のデバッグツール(
delve
など)のドキュメント - Goのトレースバックに関する議論やブログ記事
参考にした情報源リンク
- Go issue 5902: runtime: print "created by" for running goroutines in traceback (このコミットに関連するGitHub Issue)
- Go CL 12248043: runtime: print "created by" for running goroutines in traceback (このコミットのGerritレビューページ)
- Go runtime source code
- A Guide to Go's Runtime Scheduler (Goスケジューラに関する記事、ゴルーチンの状態について理解を深めるのに役立つ)
- Go's Execution Tracer (Goの実行トレーサーに関するブログ記事、デバッグとプロファイリングの文脈でゴルーチンの理解を深めるのに役立つ)
- Go: The Complete Developer's Guide (Udemy Course) (Go言語の基礎から応用までを学ぶためのコース、ランタイムの概念も含まれる)
- Go Programming Language (Book) (Go言語の公式書籍、ランタイムの内部動作に関する詳細な情報が含まれる)
- Understanding Goroutines and Concurrency in Go (Goにおけるゴルーチンと並行処理の理解に関するチュートリアル)
- Go's runtime package documentation (Goの
runtime
パッケージの公式ドキュメント) - Go's testing package documentation (Goの
testing
パッケージの公式ドキュメント、testing.RunTests
の文脈で関連) - Go Assembly Language (Goのアセンブリ言語に関するドキュメント、
PCQuantum
のような低レベルの概念を理解するのに役立つ) - Go's
Func
struct andruntime.findfunc
explanation (Goのシンボルテーブルと関数情報の取得に関するソースコード) - Go's
G
struct definition (Goのゴルーチン構造体G
の定義) - Go's
PCQuantum
definition (GoのPCQuantum
の定義)