[インデックス 11771] ファイルの概要
このコミットは、Go言語のディストリビューションツール(dist
)におけるWindows環境での特定のバグ修正に関するものです。具体的には、fatal()
関数が呼び出された際に発生する可能性のある再帰ループを防ぐことを目的としています。
コミット
commit ef1d2a32ea0b096b1aca9f1bda096f0718f6a2ef
Author: Daniel Theophanes <kardianos@gmail.com>
Date: Thu Feb 9 23:10:27 2012 -0500
dist: prevent recusive loop on windows when fatal() is called.
Fixes #2931.
R=golang-dev, alex.brainman
CC=golang-dev, rsc
https://golang.org/cl/5651049
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ef1d2a32ea0b096b1aca9f1bda096f0718f6a2ef
元コミット内容
dist: prevent recusive loop on windows when fatal() is called.
Fixes #2931.
変更の背景
このコミットは、Go言語のIssue #2931を修正するために行われました。Goのビルドプロセスで使用されるdist
ツールは、バックグラウンドでプロセスを実行し、その終了を待機する機能を持っています。Windows環境において、バックグラウンドプロセスの終了コードを取得する際にエラーが発生し、そのエラーをfatal()
関数で処理しようとすると、特定の条件下で再帰的なループに陥る可能性がありました。
具体的には、bgwait1
関数内でGetExitCodeProcess
が失敗した場合、fatal()
が呼び出されます。このfatal()
の呼び出しが、さらにバックグラウンドジョブのクリーンアップ処理をトリガーし、そのクリーンアップ処理が再びエラーを引き起こすことで、無限ループが発生する可能性があったと考えられます。このような状況は、システムの安定性を損ない、ビルドプロセスを停止させる原因となります。
前提知識の解説
dist
ツール: Go言語のソースコードからGoのツールチェイン(コンパイラ、リンカなど)をビルドするために使用される内部ツールです。Goのビルドシステムの中核をなす部分の一つです。fatal()
関数: プログラムの実行を致命的なエラーで終了させるための関数です。通常、エラーメッセージを出力し、プログラムを異常終了させます。- Windows API
GetExitCodeProcess
: 指定されたプロセスの終了コードを取得するために使用されるWindows API関数です。プロセスがまだ実行中の場合、STILL_ACTIVE
が返されます。 CloseHandle
: Windows API関数で、開いているオブジェクトハンドルを閉じます。プロセスハンドルやスレッドハンドルなど、システムリソースへの参照を解放するために重要です。ハンドルを閉じないと、リソースリークが発生する可能性があります。- バックグラウンドジョブ: メインのプログラム実行とは独立して、並行して実行されるタスクやプロセスを指します。
dist
ツールでは、コンパイルやリンクなどのタスクをバックグラウンドで実行することがあります。 - 再帰ループ: プログラムが自分自身を繰り返し呼び出し、終了条件が満たされないために無限に実行され続ける状態を指します。これは通常、プログラミングエラーによって引き起こされ、スタックオーバーフローやシステムリソースの枯渇につながります。
技術的詳細
このコミットの核心は、bgwait1
関数におけるエラーハンドリングの改善と、バックグラウンドジョブのハンドルクローズ処理の分離です。
変更前は、bgwait1
関数内でGetExitCodeProcess
が失敗した場合、またはバックグラウンドジョブが非ゼロの終了コードを返した場合にfatal()
が呼び出されていました。このfatal()
の呼び出しの直後に、CloseHandle
でプロセスハンドルとスレッドハンドルを閉じ、bg
配列から該当するジョブを削除する処理が行われていました。
問題は、fatal()
が呼び出された際に、その後のCloseHandle
や配列操作が適切に実行されない、あるいはfatal()
の内部処理がさらにbgwait1
を呼び出すような状況(再帰)を引き起こす可能性があった点です。特に、fatal()
が呼び出されるような致命的なエラー状況下では、リソースの解放が不完全になり、それがさらなる問題を引き起こすことが考えられます。
このコミットでは、以下の変更が導入されました。
bgwaitclose
関数の導入: バックグラウンドジョブのプロセスハンドルとスレッドハンドルを閉じ、bg
配列からジョブを削除する処理をbgwaitclose
という新しい静的関数に分離しました。これにより、リソース解放のロジックがカプセル化され、再利用性が向上します。- エラー発生時の即時クリーンアップとリターン:
bgwait1
関数内でGetExitCodeProcess
が失敗した場合、またはmode==CheckExit
で非ゼロの終了コードが返された場合、fatal()
を呼び出す前にbgwaitclose(i)
を呼び出すように変更されました。そして、fatal()
の呼び出し後にはreturn;
を追加し、それ以上bgwait1
関数内の処理が続行されないようにしました。
この変更により、fatal()
が呼び出されるような致命的なエラーが発生した場合でも、関連するプロセスハンドルとスレッドハンドルが確実に閉じられ、リソースリークや再帰ループの原因となる可能性のある状態が回避されます。fatal()
がプログラムを終了させる前に、最低限のクリーンアップが行われるようになったため、より堅牢なエラーハンドリングが実現されました。
コアとなるコードの変更箇所
変更はsrc/cmd/dist/windows.c
ファイルに集中しています。
--- a/src/cmd/dist/windows.c
+++ b/src/cmd/dist/windows.c
@@ -371,6 +371,19 @@ genrun(Buf *b, char *dir, int mode, Vec *argv, int wait)
bfree(&cmd);
}
+// closes the background job for bgwait1
+static void
+bgwaitclose(int i)
+{
+ if(i < 0 || i >= nbg)
+ return;
+
+ CloseHandle(bg[i].pi.hProcess);
+ CloseHandle(bg[i].pi.hThread);
+
+ bg[i] = bg[--nbg];
+}
+
// bgwait1 waits for a single background job
static void
bgwait1(void)
@@ -391,14 +404,19 @@ bgwait1(void)
cmd = bg[i].cmd;
mode = bg[i].mode;
-\tif(!GetExitCodeProcess(bg[i].pi.hProcess, &code))\n+\tif(!GetExitCodeProcess(bg[i].pi.hProcess, &code)) {\n+\t\tbgwaitclose(i);\n \t\tfatal(\"GetExitCodeProcess: %s\", errstr());\n-\tif(mode==CheckExit && code != 0)\n+\t\treturn;\n+\t}\n+\n+\tif(mode==CheckExit && code != 0) {\n+\t\tbgwaitclose(i);\n \t\tfatal(\"FAILED: %s\", cmd);\n-\tCloseHandle(bg[i].pi.hProcess);\n-\tCloseHandle(bg[i].pi.hThread);\n+\t\treturn;\n+\t}\n \n-\tbg[i] = bg[--nbg];\n+\tbgwaitclose(i);\n }
void
コアとなるコードの解説
-
bgwaitclose
関数の追加:- この関数は、引数
i
で指定されたバックグラウンドジョブのプロセスハンドルとスレッドハンドルを閉じ、bg
配列からそのジョブを削除する責任を持ちます。 if(i < 0 || i >= nbg)
: 無効なインデックスが渡された場合の境界チェックです。CloseHandle(bg[i].pi.hProcess);
とCloseHandle(bg[i].pi.hThread);
:PROCESS_INFORMATION
構造体に含まれるプロセスハンドルとスレッドハンドルを閉じます。これにより、OSリソースが解放されます。bg[i] = bg[--nbg];
: 削除対象のジョブを配列の末尾のジョブで上書きし、nbg
(バックグラウンドジョブの数)をデクリメントすることで、配列から論理的に削除します。これは、配列の要素を詰める一般的な手法です。
- この関数は、引数
-
bgwait1
関数の変更:if(!GetExitCodeProcess(bg[i].pi.hProcess, &code)) { ... }
:GetExitCodeProcess
が失敗した場合の処理です。bgwaitclose(i);
: エラーが発生した直後に、該当するバックグラウンドジョブのリソースを解放します。fatal("GetExitCodeProcess: %s", errstr());
: 致命的なエラーとして報告します。return;
:fatal()
が呼び出された後、関数を即座に終了させ、それ以上の処理が続行されないようにします。これにより、再帰ループの可能性を排除します。
if(mode==CheckExit && code != 0) { ... }
: ジョブが非ゼロの終了コードを返した場合(エラー終了)の処理です。bgwaitclose(i);
: 同様に、エラー終了の場合もリソースを解放します。fatal("FAILED: %s", cmd);
: 失敗したコマンドを報告します。return;
: 関数を即座に終了させます。
bgwaitclose(i);
(変更後): 正常終了した場合も、以前は直接CloseHandle
と配列操作を行っていた部分が、新しく導入されたbgwaitclose(i)
の呼び出しに置き換えられました。これにより、コードの重複が解消され、保守性が向上しています。
この変更により、エラー発生時でもリソースが確実に解放され、fatal()
の呼び出しが再帰ループを引き起こす可能性が排除されました。
関連リンク
- Go Issue #2931: このコミットが修正したGoのIssueへの直接リンクは、GoのIssueトラッカーの変更により、古い形式のURLでは直接アクセスできない場合があります。しかし、コミットメッセージに明記されているため、このコミットがこのIssueを解決したことは確かです。
- Go CL 5651049: https://golang.org/cl/5651049 - このコミットのコードレビューページです。レビューの経緯や議論が確認できます。
参考にした情報源リンク
- Go CL 5651049: https://golang.org/cl/5651049
- Windows API Documentation (GetExitCodeProcess, CloseHandle): Microsoft Learnなどの公式ドキュメント
- Go言語のソースコード(
src/cmd/dist/windows.c
) - 一般的なGo言語のビルドプロセスと
dist
ツールの役割に関する知識