[インデックス 15281] ファイルの概要
このコミットは、Goランタイムにおけるmain
ゴルーチンからのリターン時の挙動を修正するものです。具体的には、他のゴルーチンでパニックが発生している最中にmain
関数が終了しようとした場合、そのパニック処理が完了するまでmain
ゴルーチンが待機するように変更されています。これにより、パニックメッセージが完全に表示される前にプログラムが終了してしまうという、混乱を招く可能性のある挙動が修正されます。
コミット
commit c8214c78bd2f8ad308119dc2344634993ab499c8
Author: Russ Cox <rsc@golang.org>
Date: Fri Feb 15 14:48:35 2013 -0500
runtime: make return from main wait for active panic
Arguably if this happens the program is buggy anyway,
but letting the panic continue looks better than interrupting it.
Otherwise things like this are possible, and confusing:
$ go run x.go
panic: $ echo $?\n 0
$
Fixes #3934.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7322083
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c8214c78bd2f8ad308119dc2344634993ab499c8
元コミット内容
このコミットは、Goランタイムのproc.c
ファイルに対して行われた変更です。runtime·main
関数(Goプログラムのエントリポイント)が終了する際に、もし他のゴルーチンでパニックが進行中であれば、そのパニック処理が完了するまで待機するロジックが追加されました。これにより、パニックのスタックトレースなどが完全に表示される前にプログラムが終了してしまう問題が解決されます。
変更の背景
Goプログラムでは、main
関数が終了すると、他のゴルーチンがまだ実行中であってもプログラム全体が終了します。しかし、もしmain
関数が終了するのと同時に、別のゴルーチンでパニックが発生していた場合、パニックの処理(スタックトレースの出力など)が完了する前にプログラムが強制終了してしまう可能性がありました。
コミットメッセージに示されている例では、go run x.go
を実行するとパニックが発生するにもかかわらず、終了ステータスが0
(成功)となり、パニックメッセージも不完全な形で表示されています。これは、パニックが完全に処理される前にmain
ゴルーチンが終了し、プログラムが終了してしまったためです。このような挙動は、デバッグを困難にし、プログラムが予期せぬ形で終了した際に混乱を招きます。
この問題は、GoのIssue #3934として報告されており、このコミットはその問題を解決するために導入されました。
前提知識の解説
- Goランタイム (Go Runtime): Goプログラムの実行を管理するシステムです。ゴルーチンのスケジューリング、メモリ管理、ガベージコレクション、チャネル通信、パニック処理など、Go言語の並行処理モデルを支える低レベルな機能を提供します。C言語とGo言語で実装されています。
- ゴルーチン (Goroutine): Go言語における軽量な並行実行単位です。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行することも可能です。Goランタイムがゴルーチンのスケジューリングを行います。
main
関数とmain
ゴルーチン: Goプログラムのエントリポイントはmain
パッケージのmain
関数です。このmain
関数は、プログラム開始時に自動的に起動される最初のゴルーチン(main
ゴルーチン)で実行されます。main
ゴルーチンが終了すると、他のゴルーチンが実行中であってもプログラム全体が終了します。- パニック (Panic): Goにおけるランタイムエラーの一種です。通常、プログラムの回復不可能なエラー(例: nilポインタ参照、配列の範囲外アクセス)が発生した場合にパニックが起こります。パニックが発生すると、現在のゴルーチンの実行が停止し、遅延関数(
defer
)が実行され、最終的にプログラムが終了します(ただし、recover
関数でパニックを捕捉することも可能です)。パニック時には、通常、詳細なスタックトレースが標準エラー出力に表示されます。 runtime·park
関数: Goランタイム内部で使用される関数で、ゴルーチンを一時的に停止(パーク)させ、スケジューラから実行キューから外します。これにより、ゴルーチンはCPUを消費せずに待機状態に入ります。特定のイベントが発生するまでゴルーチンをブロックするために使用されます。対応する関数としてruntime·ready
(またはruntime.goready
)があり、パークされたゴルーチンを実行可能な状態に戻します。
技術的詳細
このコミットの核心は、runtime·main
関数が終了する直前に、グローバルなパニック状態を示すフラグruntime·panicking
をチェックし、もしパニックが進行中であればruntime·park
を呼び出してmain
ゴルーチンを待機させる点にあります。
Goランタイムでは、パニックが発生するとruntime·panicking
というフラグがセットされます。このフラグは、パニック処理が完了するまで真の状態を保ちます。main
ゴルーチンがruntime·park
を呼び出すと、main
ゴルーチンは「panicwait」という理由で待機状態に入ります。この待機は、他のゴルーチンで進行中のパニック処理が完了し、プログラムが終了するまで続きます。パニック処理が完了すると、通常はruntime·exit
が呼び出され、プログラムが適切な終了ステータスで終了します。
これにより、main
ゴルーチンが性急に終了してしまい、他のゴルーチンで発生したパニックのスタックトレースが途中で切れてしまうという問題が回避されます。プログラムは、パニックの全情報が出力されるのを待ってから終了するようになります。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/proc.c
ファイル内のruntime·main
関数に対して行われています。
--- a/src/pkg/runtime/proc.c
+++ b/src/pkg/runtime/proc.c
@@ -253,6 +253,14 @@ runtime·main(void)
main·main();
if(raceenabled)
runtime·racefini();
+
+ // Make racy client program work: if panicking on
+ // another goroutine at the same time as main returns,
+ // let the other goroutine finish printing the panic trace.
+ // Once it does, it will exit. See issue 3934.
+ if(runtime·panicking)
+ runtime·park(nil, nil, "panicwait");
+
runtime·exit(0);
for(;;)
*(int32*)runtime·main = 0;
コアとなるコードの解説
追加されたコードブロックは以下の通りです。
// Make racy client program work: if panicking on
// another goroutine at the same time as main returns,
// let the other goroutine finish printing the panic trace.
// Once it does, it will exit. See issue 3934.
if(runtime·panicking)
runtime·park(nil, nil, "panicwait");
-
if(runtime·panicking)
:runtime·panicking
は、Goランタイム内部で管理されるグローバルなフラグです。いずれかのゴルーチンでパニックが発生し、その処理が進行中である場合にtrue
になります。- この条件文は、
main
ゴルーチンが終了しようとしている時点で、システム全体でアクティブなパニックが存在するかどうかをチェックしています。
-
runtime·park(nil, nil, "panicwait");
:- もし
runtime·panicking
がtrue
であれば、runtime·park
関数が呼び出されます。 runtime·park
は、現在のゴルーチン(この場合はmain
ゴルーチン)を待機状態にするためのランタイム関数です。- 引数
nil, nil
は、このpark
が特定のチャネルやミューテックスに関連付けられていないことを示します。 "panicwait"
は、このゴルーチンが待機している理由を示す文字列です。これはデバッグやプロファイリングの際に役立ちます。main
ゴルーチンは、このruntime·park
の呼び出しによって、他のゴルーチンで進行中のパニック処理が完了するまでブロックされます。パニック処理が完了すると、通常はプログラムが終了するため、main
ゴルーチンがruntime·park
から戻ることはありません。
- もし
この変更により、main
ゴルーチンが他のゴルーチンのパニック処理を「追い越して」終了してしまうことがなくなり、パニックに関する完全な情報がユーザーに提供されることが保証されます。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/
- GoのIssueトラッカー: https://github.com/golang/go/issues
参考にした情報源リンク
- GoのIssue #3934 (コミットメッセージに記載されているが、Web検索では直接見つからず、関連するIssue #20018が示唆された): https://github.com/golang/go/issues/20018 (これはWindows固有の類似問題だが、挙動の背景理解に役立つ)
runtime.gopark
に関するStack Overflowの議論: https://stackoverflow.com/questions/32800000/what-is-the-purpose-of-runtime-gopark-and-runtime-goready- Goの
runtime.gopark
のソースコードに関する解説記事など (一般的な情報源): https://go.dev/src/runtime/proc.go (Goのソースコード自体)