Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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");
  1. if(runtime·panicking):

    • runtime·panickingは、Goランタイム内部で管理されるグローバルなフラグです。いずれかのゴルーチンでパニックが発生し、その処理が進行中である場合にtrueになります。
    • この条件文は、mainゴルーチンが終了しようとしている時点で、システム全体でアクティブなパニックが存在するかどうかをチェックしています。
  2. runtime·park(nil, nil, "panicwait");:

    • もしruntime·panickingtrueであれば、runtime·park関数が呼び出されます。
    • runtime·parkは、現在のゴルーチン(この場合はmainゴルーチン)を待機状態にするためのランタイム関数です。
    • 引数nil, nilは、このparkが特定のチャネルやミューテックスに関連付けられていないことを示します。
    • "panicwait"は、このゴルーチンが待機している理由を示す文字列です。これはデバッグやプロファイリングの際に役立ちます。
    • mainゴルーチンは、このruntime·parkの呼び出しによって、他のゴルーチンで進行中のパニック処理が完了するまでブロックされます。パニック処理が完了すると、通常はプログラムが終了するため、mainゴルーチンがruntime·parkから戻ることはありません。

この変更により、mainゴルーチンが他のゴルーチンのパニック処理を「追い越して」終了してしまうことがなくなり、パニックに関する完全な情報がユーザーに提供されることが保証されます。

関連リンク

参考にした情報源リンク