[インデックス 11669] ファイルの概要
このコミットは、Go言語のビルドツールである cmd/dist
における2つの問題を修正します。一つは VERSION
ファイルの作成に関する競合状態(race condition)の防止、もう一つは cmdversion
コマンドがバージョン情報を二重にロードするのを防ぐことです。これにより、ビルドプロセスの堅牢性と効率が向上します。
コミット
commit c2fe6634db08902f078093f3ad9c7fa9cf7bb330
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Tue Feb 7 00:38:15 2012 -0200
cmd/dist: prevent race on VERSION creation
Commands such as "dist version > VERSION" will cause
the shell to create an empty VERSION file and set dist's
stdout to its fd. dist in turn looks at VERSION and uses
its content if available, which is empty at this point.
Fix that by ignoring VERSION if it's empty.
Also prevent cmdversion from running findgoversion a
second time. It was already loaded by init.
R=adg, gustavo, rsc
CC=golang-dev
https://golang.org/cl/5639044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c2fe6634db08902f078093f3ad9c7fa9cf7bb330
元コミット内容
cmd/dist: prevent race on VERSION creation
"dist version > VERSION"
のようなコマンドは、シェルが空の VERSION
ファイルを作成し、dist
の標準出力をそのファイルディスクリプタに設定します。dist
は VERSION
ファイルを調べ、利用可能であればその内容を使用しますが、この時点ではファイルは空です。
この問題を、VERSION
ファイルが空の場合には無視することで修正します。
また、cmdversion
が findgoversion
を二度実行するのを防ぎます。これは init
によって既にロードされています。
変更の背景
このコミットは、Go言語のビルドシステムにおける2つの独立した、しかし関連する問題を解決するために導入されました。
-
VERSION
ファイル作成時の競合状態: Goのビルドプロセスでは、Goのバージョン情報を管理するためにVERSION
というファイルが使用されることがあります。ユーザーがdist version > VERSION
のようなシェルコマンドを実行すると、シェルはコマンドが実行される前にVERSION
という名前の空のファイルを即座に作成し、そのファイルに標準出力をリダイレクトします。dist
ツールは、Goのバージョンを決定する際に、まずVERSION
ファイルの存在と内容を確認します。しかし、このシナリオでは、dist
がVERSION
ファイルを読み込もうとした時点で、シェルによって作成されたばかりの空のファイルが存在するため、dist
はその空のファイルを有効なバージョン情報として誤って解釈してしまう可能性がありました。これにより、ビルドプロセスが誤ったバージョン情報を使用したり、予期せぬ動作を引き起こしたりする可能性がありました。これは典型的なファイル操作における競合状態の一種であり、ファイルの作成と内容の書き込みがアトミックに行われない場合に発生します。 -
cmdversion
によるバージョン情報の二重ロード:cmd/dist
ツールには、Goのバージョン情報を取得するためのfindgoversion
関数と、そのバージョンを表示するcmdversion
コマンドがあります。Goのプログラムでは、通常、初期化フェーズ(init
関数など)で必要な情報を一度ロードし、それを再利用することが推奨されます。しかし、既存の実装では、cmdversion
コマンドが実行される際に、既にinit
フェーズでロードされているはずのバージョン情報を、findgoversion
を再度呼び出すことで二重にロードしていました。これはパフォーマンスの無駄であり、不必要なファイルI/Oや処理を引き起こしていました。
これらの問題は、Goのビルドシステムの堅牢性と効率性に影響を与えるため、修正が必要とされました。
前提知識の解説
このコミットの理解には、以下の概念が役立ちます。
cmd/dist
: Go言語のソースコードリポジトリに含まれる、Goのビルドシステムの中核をなすツールです。Goのコンパイラ、ツール、標準ライブラリなどをビルドするために使用されます。C言語で書かれており、Goのブートストラッププロセスにおいて重要な役割を果たします。VERSION
ファイル: Goのソースツリーのルートに存在する可能性のあるファイルで、Goの現在のバージョン文字列(例:go1.0.3
)が記述されています。cmd/dist
はこのファイルを読み込んでバージョン情報を取得します。- シェルリダイレクト (
>
): Unix系システムにおけるシェルの機能で、コマンドの標準出力(stdout)をファイルに書き込むために使用されます。例えば、command > file.txt
とすると、command
の出力はfile.txt
に書き込まれます。重要なのは、シェルはcommand
が実行される前にfile.txt
を作成(または上書き)するという点です。 - ファイル操作における競合状態 (Race Condition): 複数のプロセスやスレッドが共有リソース(この場合はファイル)に同時にアクセスしようとしたときに発生する問題です。特に、ファイルが作成され、その内容が書き込まれるまでの間に、別のプロセスがそのファイルを読み込もうとすると、空のファイルや不完全な内容を読み込んでしまう可能性があります。
findgoversion
関数:cmd/dist/build.c
内に定義されている関数で、Goのバージョン情報を決定するためのロジックを含んでいます。VERSION
ファイルの読み込み、VERSION.cache
ファイルの利用、Gitリポジトリからのバージョン情報の取得などを行います。cmdversion
関数:cmd/dist/build.c
内に定義されている関数で、dist version
コマンドが実行されたときに呼び出されます。Goのバージョン文字列を標準出力に出力します。goversion
グローバル変数:cmd/dist/build.c
内でGoのバージョン文字列を保持するために使用されるグローバル変数です。通常、プログラムの初期化時に一度だけ設定されます。init
関数 (C言語の文脈): Go言語のinit
関数とは異なり、C言語の文脈では、プログラムの起動時に一度だけ実行される初期化ルーチンや、特定の変数が初期化されるタイミングを指すことがあります。このコミットの文脈では、goversion
変数がプログラムの初期化時に一度ロードされることを指しています。
技術的詳細
このコミットは、前述の2つの問題を解決するために src/cmd/dist/build.c
ファイルに具体的な変更を加えます。
1. VERSION
ファイル作成時の競合状態の防止
元の findgoversion
関数では、VERSION
ファイルが存在し、読み込み可能であれば、その内容をそのままGoのバージョンとして採用していました。しかし、dist version > VERSION
のようなコマンドが実行された場合、シェルが先に空の VERSION
ファイルを作成するため、findgoversion
がこの空のファイルを読み込んでしまい、誤った(空の)バージョン情報を使用してしまう問題がありました。
この問題を解決するために、コミットでは findgoversion
関数内の VERSION
ファイルを読み込むロジックに条件を追加しました。具体的には、ファイルを読み込んだ後、その内容の長さ (b.len
) が0より大きい場合にのみ、その内容を有効なバージョン情報として採用するように変更しました。もし b.len
が0であれば、それはシェルによって作成されたばかりの空のファイルであると判断し、そのファイルを無視して、次のバージョン情報取得ロジック(VERSION.cache
の確認など)に進むようにしました。これにより、空の VERSION
ファイルによる誤ったバージョン情報の取得を防ぎ、競合状態を回避します。
2. cmdversion
によるバージョン情報の二重ロードの防止
元の cmdversion
関数は、Goのバージョンを表示するために findgoversion()
を直接呼び出していました。しかし、goversion
というグローバル変数は、プログラムの初期化フェーズ(init
)で既に findgoversion
を呼び出して設定されているはずでした。したがって、cmdversion
が再度 findgoversion
を呼び出すことは、不必要な処理であり、効率的ではありませんでした。
このコミットでは、cmdversion
関数が findgoversion()
を直接呼び出す代わりに、既に初期化時に設定されている goversion
グローバル変数の内容を直接出力するように変更しました。これにより、findgoversion
の二重実行が防止され、dist version
コマンドの実行効率が向上します。
これらの変更は、Goのビルドシステムの堅牢性とパフォーマンスを向上させるための、細部への注意と最適化を示しています。
コアとなるコードの変更箇所
--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -179,7 +179,12 @@ findgoversion(void)
if(isfile(bstr(&path))) {
readfile(&b, bstr(&path));
chomp(&b);
-\t\tgoto done;
+\t\t// Commands such as "dist version > VERSION" will cause
+\t\t// the shell to create an empty VERSION file and set dist's
+\t\t// stdout to its fd. dist in turn looks at VERSION and uses
+\t\t// its content if available, which is empty at this point.
+\t\tif(b.len > 0)
+\t\t\tgoto done;
}
// The $GOROOT/VERSION.cache file is a cache to avoid invoking
@@ -1370,5 +1375,5 @@ cmdversion(int argc, char **argv)
if(argc > 0)
usage();
-\txprintf("%s\n", findgoversion());
+\txprintf("%s\n", goversion);
}
コアとなるコードの解説
findgoversion
関数の変更
if(isfile(bstr(&path))) {
readfile(&b, bstr(&path));
chomp(&b);
-\t\tgoto done;
+\t\t// Commands such as "dist version > VERSION" will cause
+\t\t// the shell to create an empty VERSION file and set dist's
+\t\t// stdout to its fd. dist in turn looks at VERSION and uses
+\t\t// its content if available, which is empty at this point.
+\t\tif(b.len > 0)
+\t\t\tgoto done;
}
この変更は、findgoversion
関数内で VERSION
ファイルを読み込む部分にあります。
if(isfile(bstr(&path)))
は、指定されたパス(VERSION
ファイルへのパス)にファイルが存在するかどうかを確認します。readfile(&b, bstr(&path));
は、ファイルの内容をバッファb
に読み込みます。chomp(&b);
は、読み込んだ内容の末尾から改行文字などを削除します。- 変更前のコードでは、ファイルが存在し、読み込みが成功すればすぐに
goto done;
で処理を終了し、読み込んだ内容をバージョンとして採用していました。 - 変更後のコードでは、
goto done;
の前に新しい条件if(b.len > 0)
が追加されています。これは、読み込んだバッファb
の長さが0より大きい(つまり、ファイルが空ではない)場合にのみgoto done;
を実行するようにします。 - コメントで説明されているように、
dist version > VERSION
のようなコマンドでは、シェルが空のVERSION
ファイルを作成するため、b.len
が0になります。この条件により、空のファイルは無視され、findgoversion
は次のロジック(例:VERSION.cache
の確認)に進み、正しいバージョン情報を取得しようとします。
cmdversion
関数の変更
if(argc > 0)
usage();
-\txprintf("%s\n", findgoversion());
+\txprintf("%s\n", goversion);
}
この変更は、cmdversion
関数内でGoのバージョンを出力する部分にあります。
- 変更前のコードでは、
xprintf("%s\n", findgoversion());
となっており、findgoversion()
関数を呼び出してその戻り値(バージョン文字列)を出力していました。 - 変更後のコードでは、
xprintf("%s\n", goversion);
となっています。これは、findgoversion()
を再度呼び出す代わりに、既にプログラムの初期化時に設定されているグローバル変数goversion
の内容を直接出力するように変更されています。 - これにより、不必要な
findgoversion
の呼び出しが回避され、パフォーマンスが向上します。
これらの変更は、Goのビルドシステムの堅牢性と効率性を高めるための、具体的かつ効果的な修正です。
関連リンク
- Go CL 5639044: https://golang.org/cl/5639044
参考にした情報源リンク
- 提供されたコミット情報 (
./commit_data/11669.txt
)