[インデックス 18290] ファイルの概要
このコミットは、Goランタイムの src/pkg/runtime/panic.c
ファイルに対する変更です。このファイルは、Goプログラムにおけるパニック(panic)の処理、特にパニック発生時のスタックトレースの生成と、パニックが複数回発生する「panic during panic」という特殊な状況のハンドリングを司る重要な部分です。
コミット
このコミットは、Goランタイムにおいて「panic during panic」(パニック処理中にさらにパニックが発生する状況)が発生した場合に、スタックトレースを出力するように修正します。これにより、このような稀で深刻なエラー状況におけるデバッグ情報が強化され、問題の診断が容易になります。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6c9f198c9a7ea0597c1181b46f794380a7be5cfe
元コミット内容
runtime: print stack trace when "panic during panic"
Fixes bug 7145
R=golang-codereviews, iant
CC=golang-codereviews
https://golang.org/cl/53970043
変更の背景
Go言語では、プログラムの異常終了時に「パニック(panic)」が発生します。パニックが発生すると、通常の実行フローは中断され、遅延関数(defer)が実行され、最終的にはスタックトレースが出力されてプログラムが終了します。しかし、非常に稀なケースとして、このパニックの処理中に、さらに別のパニックが発生することがあります。これを「panic during panic」と呼びます。
従来のGoランタイムでは、「panic during panic」が発生した場合、単に「panic during panic」というメッセージを出力してプログラムを終了していました。この挙動では、なぜ二度目のパニックが発生したのか、その根本原因を特定するための情報(スタックトレース)が提供されませんでした。これはデバッグを非常に困難にする問題でした。
このコミットは、この情報不足を解消し、「panic during panic」が発生した際にも、その時点でのスタックトレースを出力することで、開発者が問題の原因をより詳細に分析できるようにすることを目的としています。コミットメッセージにある「Fixes bug 7145」は、この問題がGoの内部バグトラッカーで追跡されていたことを示唆しています。
前提知識の解説
Goのパニックとリカバリ (Panic and Recover)
Go言語には、エラーハンドリングのためのerror
インターフェースと、回復不可能なエラーを扱うためのpanic
とrecover
メカニズムがあります。
panic
: プログラムの実行を中断し、現在のゴルーチン(goroutine)のスタックを巻き戻し(unwind)始めます。この巻き戻しの過程で、defer
文で登録された関数が実行されます。recover
:panic
によって中断されたゴルーチンの実行を再開するために使用されます。recover
はdefer
関数内で呼び出された場合にのみ有効で、パニックの値を捕捉し、パニックの連鎖を停止させます。recover
がdefer
関数外で呼び出された場合、またはパニックが発生していないときに呼び出された場合、nil
を返します。
スタックトレース (Stack Trace)
スタックトレースは、プログラムが特定の時点(通常はエラーやパニックが発生した時点)で実行していた関数の呼び出し履歴を示すリストです。各エントリは、関数名、ファイル名、行番号を含み、問題が発生した場所とその原因となった呼び出しパスを特定するのに役立ちます。Goランタイムは、パニック発生時に自動的にスタックトレースを生成し、標準エラー出力に表示します。
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理する低レベルのシステムです。これには、ゴルーチンのスケジューリング、メモリ管理(ガベージコレクション)、チャネル通信、パニック処理などが含まれます。src/pkg/runtime/
ディレクトリには、これらのランタイム機能のC言語(またはGoのサブセットであるPlan 9 Assembly)で書かれたソースコードが含まれています。panic.c
はその一部であり、パニック処理の核心を担っています。
m->dying
フラグ
Goランタイムの内部では、各M(Machine、OSスレッドを表す)構造体にdying
というフラグが存在します。このフラグは、そのMが現在パニック処理中であるかどうかを示します。dying
フラグが1
(true)の場合、そのMは既にパニック状態にあり、パニック処理の最中であることを意味します。このフラグは、「panic during panic」のような状況を検出するために使用されます。
技術的詳細
Goランタイムのパニック処理は、runtime·startpanic
関数(C言語で実装)から始まります。この関数は、パニックが発生した際に呼び出され、パニック処理の初期化と管理を行います。
コミット前のruntime·startpanic
関数には、以下のようなロジックがありました。
if(m->dying) {
runtime·printf("panic during panic\n");
runtime·exit(3);
}
このコードは、現在のM(OSスレッド)が既にパニック処理中(m->dying
が1
)である場合に、「panic during panic」というメッセージを出力し、runtime·exit(3)
を呼び出してプログラムを即座に終了させていました。ここで問題となるのは、runtime·exit(3)
が呼び出される前にスタックトレースが出力されないため、二度目のパニックの原因を特定する手掛かりが全くない点でした。
このコミットの目的は、このruntime·exit(3)
の呼び出しの前に、スタックトレースを出力する処理を挿入することです。スタックトレースの出力は、通常runtime·dopanic
関数によって行われます。
コアとなるコードの変更箇所
変更は src/pkg/runtime/panic.c
ファイルの runtime·startpanic
関数内で行われました。
--- a/src/pkg/runtime/panic.c
+++ b/src/pkg/runtime/panic.c
@@ -371,7 +371,8 @@ runtime·startpanic(void)
m->mcache = runtime·allocmcache();
if(m->dying) {
runtime·printf("panic during panic\n");
- runtime·exit(3);
+ runtime·dopanic(0);
+ runtime·exit(3); // not reached
}
m->dying = 1;
if(g != nil)
コアとなるコードの解説
変更点は非常にシンプルですが、その影響は大きいです。
-
runtime·dopanic(0);
の追加:m->dying
が1
、つまり「panic during panic」の状態が検出された直後に、runtime·dopanic(0)
が呼び出されるようになりました。runtime·dopanic
関数は、Goランタイムの内部でスタックトレースを生成し、出力する役割を担っています。引数の0
は、このパニックが通常のパニック処理の一部ではなく、特別な状況(この場合は「panic during panic」)であることを示唆している可能性があります。この呼び出しにより、二度目のパニックが発生した時点でのスタックトレースが標準エラー出力に書き込まれるようになります。これにより、開発者は何が原因で二度目のパニックが引き起こされたのかを、より詳細なコンテキストで把握できるようになります。 -
runtime·exit(3); // not reached
:runtime·dopanic(0)
の呼び出し後も、runtime·exit(3)
は引き続き呼び出されます。これは、二度目のパニックが発生した場合は、プログラムを終了させるという最終的な挙動は変わらないことを意味します。 コメント// not reached
は、runtime·dopanic(0)
がスタックトレースを出力した後、通常はプログラムを終了させるような内部的な処理を行うため、その後のruntime·exit(3)
が実際に実行されることはない、という開発者の意図を示しています。これは、runtime·dopanic
が最終的にfatal
なエラーとしてプログラムを終了させるパスを持っているためです。
この変更により、「panic during panic」という極めてデバッグが困難な状況においても、有用なスタックトレースが提供されるようになり、Goプログラムの堅牢性とデバッグ性が向上しました。
関連リンク
- Go Change List: https://golang.org/cl/53970043 (このリンクはGoの内部Gerritレビューシステムへのものであり、直接内容を閲覧できない場合があります。)
参考にした情報源リンク
- Go言語の公式ドキュメント(panicとrecoverに関するセクション)
- Goランタイムのソースコード(特に
src/pkg/runtime/panic.c
) - Goのバグトラッカー(
bug 7145
に関する情報がGoLand IDEのバグとして検索されましたが、このコミットが修正するbug 7145
はGoランタイムの内部バグトラッカーの可能性が高いです。)