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

[インデックス 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 の標準出力をそのファイルディスクリプタに設定します。distVERSION ファイルを調べ、利用可能であればその内容を使用しますが、この時点ではファイルは空です。

この問題を、VERSION ファイルが空の場合には無視することで修正します。

また、cmdversionfindgoversion を二度実行するのを防ぎます。これは init によって既にロードされています。

変更の背景

このコミットは、Go言語のビルドシステムにおける2つの独立した、しかし関連する問題を解決するために導入されました。

  1. VERSION ファイル作成時の競合状態: Goのビルドプロセスでは、Goのバージョン情報を管理するために VERSION というファイルが使用されることがあります。ユーザーが dist version > VERSION のようなシェルコマンドを実行すると、シェルはコマンドが実行される前に VERSION という名前の空のファイルを即座に作成し、そのファイルに標準出力をリダイレクトします。dist ツールは、Goのバージョンを決定する際に、まず VERSION ファイルの存在と内容を確認します。しかし、このシナリオでは、distVERSION ファイルを読み込もうとした時点で、シェルによって作成されたばかりの空のファイルが存在するため、dist はその空のファイルを有効なバージョン情報として誤って解釈してしまう可能性がありました。これにより、ビルドプロセスが誤ったバージョン情報を使用したり、予期せぬ動作を引き起こしたりする可能性がありました。これは典型的なファイル操作における競合状態の一種であり、ファイルの作成と内容の書き込みがアトミックに行われない場合に発生します。

  2. 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のビルドシステムの堅牢性と効率性を高めるための、具体的かつ効果的な修正です。

関連リンク

参考にした情報源リンク

  • 提供されたコミット情報 (./commit_data/11669.txt)