[インデックス 11827] ファイルの概要
このコミットは、Go言語のコマンドラインツール cmd/go におけるテスト実行時のタイムアウト処理に関する改善です。具体的には、go test コマンドに渡される -timeout フラグの値を cmd/go 自身が適切に解釈し、テスト全体の実行時間に対する内部的な「キルタイムアウト」を調整するように変更されています。これにより、テストが予期せずハングアップした場合でも、より正確な時間で強制終了できるようになります。
コミット
commit d318ab22647205c4d6d3aa32bb027a434f7b16d3
Author: Russ Cox <rsc@golang.org>
Date: Sun Feb 12 23:19:24 2012 -0500
cmd/go: respect test -timeout flag
I thought that -timeout was per-test, but it is for the
whole program execution, so cmd/go can adjust its timer
(also for whole program execution) accordingly.
Fixes #2993.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5650070
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d318ab22647205c4d6d3aa32bb027a434f7b16d3
元コミット内容
cmd/go: respect test -timeout flag
I thought that -timeout was per-test, but it is for the
whole program execution, so cmd/go can adjust its timer
(also for whole program execution) accordingly.
Fixes #2993.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5650070
変更の背景
この変更の背景には、go test コマンドの -timeout フラグの挙動に関する誤解と、それによって発生する可能性のある問題がありました。コミットメッセージによると、当初開発者は -timeout フラグが個々のテスト関数に対して適用されるものと考えていたようです。しかし、実際にはこのフラグは go test プロセス全体の実行時間に対して適用されるものでした。
この認識のずれが問題となるのは、cmd/go 自身がテストバイナリのハングアップを検出するために内部的なタイムアウト(「キルタイムアウト」)を設定している点です。もしユーザーが -timeout フラグで短い時間を指定しているにもかかわらず、cmd/go の内部タイムアウトがそれよりもはるかに長い時間(例えばデフォルトの10分)に設定されている場合、ユーザーが期待するよりもテストプロセスが長く実行され続ける可能性がありました。特に、テストが無限ループに陥ったり、デッドロックしたりした場合、ユーザーが指定したタイムアウトで終了せず、ビルドシステムやCI/CDパイプラインをブロックしてしまう恐れがありました。
このコミットは、この問題を解決するために、cmd/go がユーザーが指定した -timeout フラグの値を読み取り、その値に基づいて自身の内部的なキルタイムアウトを調整するように修正しています。これにより、ユーザーの意図がより正確に反映され、テストの信頼性と効率が向上します。コミットメッセージにある Fixes #2993 は、この問題がGoプロジェクトのイシュートラッカー(またはGerritの変更リスト)で報告されていたことを示しています。
前提知識の解説
go test コマンド
go test はGo言語の標準的なテスト実行ツールです。Goのテストは、_test.go で終わるファイルに記述されたテスト関数(TestXxx)、ベンチマーク関数(BenchmarkXxx)、および例(ExampleXxx)を実行します。
go test -timeout フラグ
-timeout duration フラグは、テストバイナリ全体の実行時間に対するタイムアウトを設定します。指定された duration(例: 10s, 1m, 1h30m)を超えてテストが実行された場合、go test プロセスは強制終了されます。このフラグは、テストが無限ループに陥ったり、外部リソースの応答待ちでハングアップしたりするのを防ぐために重要です。
time.ParseDuration 関数
Go言語の time パッケージに含まれる time.ParseDuration(s string) (Duration, error) 関数は、文字列で表現された期間(例: "300ms", "1.5h", "2h45m")を time.Duration 型の値に変換します。この関数は、ユーザーからの入力や設定ファイルから時間情報を読み取る際に非常に便利です。
テストの「キルタイムアウト」
テストの「キルタイムアウト」とは、テストプロセスが予期せずハングアップした場合に、それを強制的に終了させるための最終的なデッドラインを指します。go test コマンド自体がテストバイナリを起動し、その実行を監視しています。テストバイナリが応答しなくなった場合や、指定されたタイムアウトを超過した場合に、go test はテストバイナリを強制終了する責任があります。この「キルタイムアウト」は、ビルドシステムやCI/CD環境において、テストが無限に実行され続けることを防ぎ、リソースの枯渇やパイプラインの停止を回避するために不可欠な安全機構です。
技術的詳細
このコミットの技術的な核心は、cmd/go がユーザー指定の -timeout フラグの値を読み取り、その値に基づいて自身の内部的なテストキルタイムアウト testKillTimeout を動的に調整する点にあります。
変更前は、testKillTimeout は 10 * time.Minute という固定値でハードコードされていました。これは、ユーザーが -timeout で短い時間を指定しても、cmd/go 自身は最大10分間テストの終了を待つ可能性があることを意味していました。
変更後、src/cmd/go/test.go の runTest 関数内で、testTimeout 変数(-timeout フラグの値が格納される)が time.ParseDuration を使って解析されます。
// If a test timeout was given and is parseable, set our kill timeout
// to that timeout plus one minute. This is a backup alarm in case
// the test wedges with a goroutine spinning and its background
// timer does not get a chance to fire.
if dt, err := time.ParseDuration(testTimeout); err == nil {
testKillTimeout = dt + 1*time.Minute
}
このコードスニペットは以下のロジックを実装しています。
testTimeout(文字列) をtime.Duration型に変換しようと試みます。- 変換が成功した場合(
err == nil)、testKillTimeoutの値をdt + 1*time.Minuteに設定します。ここでdtはユーザーが指定したタイムアウト期間です。 + 1*time.Minuteという加算は、ユーザーが指定したタイムアウトに加えて1分間の猶予期間を設けることを意味します。これは、コミットメッセージにもあるように、「テストがゴルーチンをスピンさせてウェッジし、そのバックグラウンドタイマーが発火する機会を得られない場合のバックアップアラーム」として機能します。つまり、テストバイナリ自身のタイムアウト機構が何らかの理由で機能しなかった場合でも、cmd/goが少し遅れて確実にテストを強制終了するためのセーフティネットです。
この調整により、cmd/go の内部的なキルタイムアウトがユーザーの意図により近づき、テストのハングアップに対する応答性が向上します。
また、src/cmd/go/testflag.go では、-timeout フラグの値を testTimeout 変数に適切に格納するための変更が加えられています。
case "timeout":
testTimeout = value
これにより、go test コマンドに -timeout フラグが渡された際に、その値が cmd/go の内部で利用可能になります。
コアとなるコードの変更箇所
diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go
index bc2af619c2..1633244556 100644
--- a/src/cmd/go/test.go
+++ b/src/cmd/go/test.go
@@ -198,10 +198,13 @@ var (
testX bool // -x flag
testV bool // -v flag
testFiles []string // -file flag(s) TODO: not respected
+ testTimeout string // -timeout flag
testArgs []string
testBench bool
testStreamOutput bool // show output as it is generated
testShowPass bool // show passing output
+
+ testKillTimeout = 10 * time.Minute
)
func runTest(cmd *Command, args []string) {
@@ -217,6 +220,14 @@ func runTest(cmd *Command, args []string) {
fatalf("cannot use -c flag with multiple packages")
}
+ // If a test timeout was given and is parseable, set our kill timeout
+ // to that timeout plus one minute. This is a backup alarm in case
+ // the test wedges with a goroutine spinning and its background
+ // timer does not get a chance to fire.
+ if dt, err := time.ParseDuration(testTimeout); err == nil {
+ testKillTimeout = dt + 1*time.Minute
+ }
+
// show passing test output (after buffering) with -v flag.
// must buffer because tests are running in parallel, and
// otherwise the output will get mixed.
@@ -540,9 +551,7 @@ func (b *builder) runTest(a *action) error {
// This is a last-ditch deadline to detect and
// stop wedged test binaries, to keep the builders
// running.
- const deadline = 10 * time.Minute
-
- tick := time.NewTimer(deadline)
+ tick := time.NewTimer(testKillTimeout)
if err == nil {
done := make(chan error)
go func() {
diff --git a/src/cmd/go/testflag.go b/src/cmd/go/testflag.go
index 8913b9b504..7c9b7f16dd 100644
--- a/src/cmd/go/testflag.go
+++ b/src/cmd/go/testflag.go
@@ -133,6 +133,8 @@ func testFlags(args []string) (packageNames, passToTest []string) {
case "bench":
// record that we saw the flag; don't care about the value
testBench = true
+ case "timeout":
+ testTimeout = value
}
if extraWord {
i++
コアとなるコードの解説
src/cmd/go/test.go の変更
-
testTimeout変数の追加:varブロックにtestTimeout string // -timeout flagが追加されました。これは、go testコマンドに渡された-timeoutフラグの値を文字列として保持するための変数です。 -
testKillTimeoutの初期化:testKillTimeout = 10 * time.Minuteが追加されました。これは、cmd/goがテストバイナリを強制終了する際のデフォルトのタイムアウトを10分に設定しています。この値は、ユーザーが-timeoutフラグを指定しない場合に適用されます。 -
runTest関数内のタイムアウト調整ロジック:runTest関数内に以下のコードブロックが追加されました。// If a test timeout was given and is parseable, set our kill timeout // to that timeout plus one minute. This is a backup alarm in case // the test wedges with a goroutine spinning and its background // timer does not get a chance to fire. if dt, err := time.ParseDuration(testTimeout); err == nil { testKillTimeout = dt + 1*time.Minute }この部分が、ユーザーが指定した
-timeoutの値を解析し、testKillTimeoutを動的に設定する主要なロジックです。time.ParseDurationで文字列のタイムアウト値をtime.Duration型に変換し、成功すればその値に1分を加算してtestKillTimeoutに代入します。これにより、cmd/goの内部タイムアウトがユーザーの意図に沿って調整されます。 -
time.NewTimerの引数変更:func (b *builder) runTest(a *action) error内で、time.NewTimerの引数がconst deadline = 10 * time.MinuteからtestKillTimeoutに変更されました。- const deadline = 10 * time.Minute - - tick := time.NewTimer(deadline) + tick := time.NewTimer(testKillTimeout)これにより、テストの監視タイマーが、固定値ではなく、ユーザーの指定またはデフォルト値に基づいて動的に設定された
testKillTimeoutの値を使用するようになります。
src/cmd/go/testflag.go の変更
-timeoutフラグの処理追加:testFlags関数内のswitchステートメントにcase "timeout":が追加されました。
この変更により、case "timeout": testTimeout = valuego testコマンドの引数を解析する際に-timeoutフラグが認識され、その値(value)がtestTimeoutグローバル変数に格納されるようになります。これにより、test.goでその値を利用できるようになります。
これらの変更により、cmd/go は go test -timeout フラグの意図を正確に反映し、テストの実行管理をより堅牢に行えるようになりました。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/d318ab22647205c4d6d3aa32bb027a434f7b16d3
- 関連するGo Gerrit Change-ID (CL):
https://golang.org/cl/5650070- 注: このCLリンクは、Goプロジェクトがかつて使用していたGerritコードレビューシステムへの古い参照である可能性があり、現在の
go.dev/clの形式とは異なります。直接アクセスできない場合がありますが、コミットメッセージに記載されている元の参照としてここに含めます。Fixes #2993は、Goプロジェクトの内部的なイシュートラッカーまたはGerritの変更リスト番号を指していると考えられます。
- 注: このCLリンクは、Goプロジェクトがかつて使用していたGerritコードレビューシステムへの古い参照である可能性があり、現在の
参考にした情報源リンク
- Go言語の公式ドキュメント (
go testコマンド、timeパッケージなど) - Go言語のソースコード (特に
src/cmd/go/test.goおよびsrc/cmd/go/testflag.go) - GoプロジェクトのGerritシステムに関する一般的な情報 (GoプロジェクトはGitHubのIssueトラッカーと並行してGerritをコードレビューに利用しています)
- Web検索による
golang/go issue 2993およびgolang.org/cl/5650070の調査結果 (これらの検索結果は、#2993がGitHubのIssueではなくGerritのCLである可能性が高いこと、および古いCLリンクが直接アクセスできない場合があることを示唆しています。)