[インデックス 19000] ファイルの概要
コミット
このコミットは、Go言語のtime
パッケージにおけるテストのタイムアウト値を調整するものです。具体的には、TestOverflowSleep
というテスト関数内で使用されているタイムアウト値を、25ミリ秒から1秒に延長しています。これは、過負荷状態のマシン上でのスケジューラのジッター(揺らぎ)によって、テストが不安定になるのを防ぐためです。
- Author: Andrew Gerrand adg@golang.org
- Date: Wed Apr 2 08:23:35 2014 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2f3776ac2765a6d9cede9894efafb980c670ddb1
元コミット内容
time: increase timeout in negative sleep duration test
There's enough jitter in the scheduler on overloaded machines
that 25ms is not enough.
LGTM=dave
R=golang-codereviews, gobot, rsc, dave
CC=golang-codereviews
https://golang.org/cl/83300044
変更の背景
この変更の背景には、Goのテストスイートが過負荷状態のシステム上で実行された際に、特定のテストが不安定になる(flakyになる)という問題がありました。特にtime
パッケージのTestOverflowSleep
テストは、非常に短いタイムアウト(25ミリ秒)を使用しており、OSのスケジューラやGoランタイムのスケジューラがゴルーチン(Goの軽量スレッド)にCPU時間を割り当てる際の不確実性(ジッター)によって、期待通りのタイミングでイベントが発生しないことがありました。
テストが不安定であることは、CI/CDパイプラインにおいて誤った失敗を報告し、開発者の生産性を低下させる原因となります。このコミットは、このような不安定性を解消し、テストの信頼性を向上させることを目的としています。25ミリ秒では、スケジューラのジッターを吸収するには短すぎると判断され、より長いタイムアウトが必要とされました。
前提知識の解説
Goのtime
パッケージとtime.After
関数
Go言語の標準ライブラリであるtime
パッケージは、時間に関する機能を提供します。その中でもtime.After
関数は、指定された期間が経過した後に値が送信されるチャネルを返します。これは、特定の操作にタイムアウトを設定したり、遅延実行を実装したりする際によく使用されます。
func After(d Duration) <-chan Time
After
は、期間d
が経過した後に現在の時刻を送信するチャネルを返します。
Duration
型
time.Duration
は、ナノ秒単位で時間を表現する型です。これはint64
のエイリアスであり、時間の長さを表すために使用されます。例えば、25 * time.Millisecond
は25ミリ秒を表します。
select
ステートメント
Goのselect
ステートメントは、複数の通信操作(チャネルの送受信)を待機するために使用されます。いずれかのチャネル操作が可能になると、そのケースが実行されます。複数の操作が可能な場合は、ランダムに1つが選択されます。タイムアウトを実装する際によく使われます。
select {
case <-ch1:
// ch1から値を受信
case value := <-ch2:
// ch2から値を受信
case ch3 <- value:
// ch3へ値を送信
case <-time.After(timeoutDuration):
// タイムアウト
default:
// どのチャネル操作もすぐにできない場合
}
テストにおけるタイムアウトと安定性
ソフトウェアテストにおいて、タイムアウトは特定の操作が完了するまでの最大時間を設定するために重要です。特に並行処理やネットワーク操作、システムリソースに依存するテストでは、デッドロックや無限ループを防ぐためにタイムアウトが不可欠です。しかし、タイムアウト値が短すぎると、テストが実行される環境(CPU負荷、OSスケジューリング、I/O遅延など)の変動によって、テストが不安定になる(flaky test)ことがあります。flaky testは、コードの変更がないにもかかわらず、テストが成功したり失敗したりする現象を指し、開発者の信頼を損ない、CI/CDパイプラインの効率を低下させます。
スケジューラとジッター
- OSスケジューラ: オペレーティングシステムは、複数のプロセスやスレッドにCPU時間を割り当てます。この割り当ては、システムの負荷や他のプロセスの活動によって変動し、予測不可能な遅延(ジッター)を引き起こすことがあります。
- Goランタイムスケジューラ: Goランタイムは、OSスレッド上でゴルーチンをスケジューリングします。Goのスケジューラも、多数のゴルーチンが存在したり、GC(ガベージコレクション)が実行されたり、システムコールがブロックされたりすると、ゴルーチンの実行タイミングにジッターを生じさせることがあります。
- ジッターの影響: 短いタイムアウトを設定している場合、これらのスケジューラのジッターによって、期待されるイベントがタイムアウト期間内に発生しないことがあり、テストが失敗する原因となります。
1<<63 - 1
と-1 << 63
これらはGoのint64
型の最大値と最小値を表現するビット演算です。
1<<63 - 1
:int64
の最大値(math.MaxInt64
に相当)。符号付き64ビット整数の最上位ビットが0で、残りのビットがすべて1の場合。-1 << 63
:int64
の最小値(math.MinInt64
に相当)。符号付き64ビット整数の最上位ビットが1で、残りのビットがすべて0の場合。 これらの値は、time.Duration
がint64
を基にしているため、オーバーフローやアンダーフローの挙動をテストする際に使用されます。
技術的詳細
このコミットは、src/pkg/time/sleep_test.go
ファイル内のTestOverflowSleep
関数を変更しています。このテストの目的は、time.After
関数に非常に大きなDuration
値(オーバーフローする可能性のある値)や負のDuration
値を渡した場合の挙動が正しいことを確認することです。
元のコードでは、負のタイムアウトをテストするselect
ブロック内で、タイムアウトとしてtimeout
定数(25ミリ秒)を使用していました。
// Original code snippet
func TestOverflowSleep(t *testing.T) {
const timeout = 25 * Millisecond // <-- ここで25ミリ秒が定義されていた
// ...
const neg = Duration(-1 << 63)
select {
case <-After(neg):
// OK
case <-After(timeout): // <-- ここで25ミリ秒のタイムアウトが使われていた
t.Fatalf("negative timeout didn't fire")
}
}
この25ミリ秒という短いタイムアウトが、過負荷状態のシステムにおけるスケジューラのジッターによって、After(neg)
チャネルが発火する前に誤ってタイムアウトケースが選択されてしまう原因となっていました。After(neg)
(負の期間)は、即座にチャネルが発火することが期待されますが、スケジューリングの遅延により、ごくわずかな時間でも遅れると、25ミリ秒のタイムアウトに間に合わないことがあったのです。
コミットによる変更は、この問題を解決するために、負のタイムアウトのテストケースにおけるタイムアウト値を大幅に延長しました。具体的には、25ミリ秒から1秒に変更されています。
// Modified code snippet
func TestOverflowSleep(t *testing.T) {
// const timeout = 25 * Millisecond // <-- この定数は削除された
// ...
const neg = Duration(-1 << 63)
select {
case <-After(neg):
// OK
case <-After(1 * Second): // <-- タイムアウトが1秒に延長された
t.Fatalf("negative timeout didn't fire")
}
}
これにより、After(neg)
チャネルが発火するまでのわずかな遅延が、1秒という長いタイムアウト期間内に収まるようになり、テストの不安定性が解消されました。最初のAfter(timeout)
ケース(big
な値のテスト)は、元々25ミリ秒でタイムアウトすることが期待されており、その挙動は変更されていません。
コアとなるコードの変更箇所
diff --git a/src/pkg/time/sleep_test.go b/src/pkg/time/sleep_test.go
index ce2dc36322..03f8e732c9 100644
--- a/src/pkg/time/sleep_test.go
+++ b/src/pkg/time/sleep_test.go
@@ -347,19 +347,18 @@ func TestReset(t *testing.T) {
// Test that sleeping for an interval so large it overflows does not
// result in a short sleep duration.
func TestOverflowSleep(t *testing.T) {
- const timeout = 25 * Millisecond
const big = Duration(int64(1<<63 - 1))
select {\n \tcase <-After(big):\n \t\tt.Fatalf(\"big timeout fired\")
-\tcase <-After(timeout):\n+\tcase <-After(25 * Millisecond):\
\t\t// OK
\t}\
const neg = Duration(-1 << 63)\
select {\
\tcase <-After(neg):\
\t\t// OK
-\tcase <-After(timeout):\
+\tcase <-After(1 * Second):\
\t\tt.Fatalf(\"negative timeout didn\'t fire\")
\t}\
}\
コアとなるコードの解説
変更はsrc/pkg/time/sleep_test.go
ファイル内のTestOverflowSleep
関数に集中しています。
-
const timeout = 25 * Millisecond
の削除: 元のコードでは、timeout
という定数で25ミリ秒を定義していました。この定数は削除され、その値が直接使用される形に変更されました。これは、定数として保持する必要性が薄れたため、またはコードの簡潔化のためと考えられます。 -
case <-After(timeout):
からcase <-After(25 * Millisecond):
への変更:big
なDuration
値(int64
の最大値)をtime.After
に渡した場合のテストケースです。このケースでは、big
なタイムアウトが発火する前に、25ミリ秒でタイムアウトすることが期待されます。定数timeout
が削除されたため、その値が直接25 * Millisecond
として記述されました。この部分の論理的な挙動は変更されていません。 -
case <-After(timeout):
からcase <-After(1 * Second):
への変更: これがこのコミットの最も重要な変更点です。neg
なDuration
値(int64
の最小値)をtime.After
に渡した場合のテストケースです。After(neg)
は即座に発火することが期待されます。もし発火しない場合、テストはt.Fatalf
で失敗します。 元のコードでは、この「即座に発火する」ことを確認するためのタイムアウトとしてtimeout
(25ミリ秒)を使用していました。しかし、前述の通り、スケジューラのジッターにより25ミリ秒では不十分な場合がありました。 この変更により、タイムアウトが1 * Second
(1秒)に大幅に延長されました。これにより、After(neg)
チャネルが発火するまでのわずかな遅延が、この新しい長いタイムアウト期間内に確実に収まるようになり、テストの不安定性が解消されました。1秒という値は、一般的なシステム負荷やスケジューラのジッターを十分に吸収できると判断された値です。
この変更は、テストのロバスト性(堅牢性)を高め、Goランタイムのテストスイート全体の信頼性を向上させることに貢献しています。
関連リンク
- Go CL 83300044: https://golang.org/cl/83300044
参考にした情報源リンク
- コミット情報:
./commit_data/19000.txt
- GitHubコミットページ: https://github.com/golang/go/commit/2f3776ac2765a6d9cede9894efafb980c670ddb1
- Go言語の
time
パッケージに関する一般的な知識 - Go言語の
select
ステートメントに関する一般的な知識 - オペレーティングシステムおよびGoランタイムのスケジューリングに関する一般的な知識```markdown
[インデックス 19000] ファイルの概要
コミット
このコミットは、Go言語のtime
パッケージにおけるテストのタイムアウト値を調整するものです。具体的には、TestOverflowSleep
というテスト関数内で使用されているタイムアウト値を、25ミリ秒から1秒に延長しています。これは、過負荷状態のマシン上でのスケジューラのジッター(揺らぎ)によって、テストが不安定になるのを防ぐためです。
- Author: Andrew Gerrand adg@golang.org
- Date: Wed Apr 2 08:23:35 2014 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/2f3776ac2765a6d9cede9894efafb980c670ddb1
元コミット内容
time: increase timeout in negative sleep duration test
There's enough jitter in the scheduler on overloaded machines
that 25ms is not enough.
LGTM=dave
R=golang-codereviews, gobot, rsc, dave
CC=golang-codereviews
https://golang.org/cl/83300044
変更の背景
この変更の背景には、Goのテストスイートが過負荷状態のシステム上で実行された際に、特定のテストが不安定になる(flakyになる)という問題がありました。特にtime
パッケージのTestOverflowSleep
テストは、非常に短いタイムアウト(25ミリ秒)を使用しており、OSのスケジューラやGoランタイムのスケジューラがゴルーチン(Goの軽量スレッド)にCPU時間を割り当てる際の不確実性(ジッター)によって、期待通りのタイミングでイベントが発生しないことがありました。
テストが不安定であることは、CI/CDパイプラインにおいて誤った失敗を報告し、開発者の生産性を低下させる原因となります。このコミットは、このような不安定性を解消し、テストの信頼性を向上させることを目的としています。25ミリ秒では、スケジューラのジッターを吸収するには短すぎると判断され、より長いタイムアウトが必要とされました。
前提知識の解説
Goのtime
パッケージとtime.After
関数
Go言語の標準ライブラリであるtime
パッケージは、時間に関する機能を提供します。その中でもtime.After
関数は、指定された期間が経過した後に値が送信されるチャネルを返します。これは、特定の操作にタイムアウトを設定したり、遅延実行を実装したりする際によく使用されます。
func After(d Duration) <-chan Time
After
は、期間d
が経過した後に現在の時刻を送信するチャネルを返します。
Duration
型
time.Duration
は、ナノ秒単位で時間を表現する型です。これはint64
のエイリアスであり、時間の長さを表すために使用されます。例えば、25 * time.Millisecond
は25ミリ秒を表します。
select
ステートメント
Goのselect
ステートメントは、複数の通信操作(チャネルの送受信)を待機するために使用されます。いずれかのチャネル操作が可能になると、そのケースが実行されます。複数の操作が可能な場合は、ランダムに1つが選択されます。タイムアウトを実装する際によく使われます。
select {
case <-ch1:
// ch1から値を受信
case value := <-ch2:
// ch2から値を受信
case ch3 <- value:
// ch3へ値を送信
case <-time.After(timeoutDuration):
// タイムアウト
default:
// どのチャネル操作もすぐにできない場合
}
テストにおけるタイムアウトと安定性
ソフトウェアテストにおいて、タイムアウトは特定の操作が完了するまでの最大時間を設定するために重要です。特に並行処理やネットワーク操作、システムリソースに依存するテストでは、デッドロックや無限ループを防ぐためにタイムアウトが不可欠です。しかし、タイムアウト値が短すぎると、テストが実行される環境(CPU負荷、OSスケジューリング、I/O遅延など)の変動によって、テストが不安定になる(flaky test)ことがあります。flaky testは、コードの変更がないにもかかわらず、テストが成功したり失敗したりする現象を指し、開発者の信頼を損ない、CI/CDパイプラインの効率を低下させます。
スケジューラとジッター
- OSスケジューラ: オペレーティングシステムは、複数のプロセスやスレッドにCPU時間を割り当てます。この割り当ては、システムの負荷や他のプロセスの活動によって変動し、予測不可能な遅延(ジッター)を引き起こすことがあります。
- Goランタイムスケジューラ: Goランタイムは、OSスレッド上でゴルーチンをスケジューリングします。Goのスケジューラも、多数のゴルーチンが存在したり、GC(ガベージコレクション)が実行されたり、システムコールがブロックされたりすると、ゴルーチンの実行タイミングにジッターを生じさせることがあります。
- ジッターの影響: 短いタイムアウトを設定している場合、これらのスケジューラのジッターによって、期待されるイベントがタイムアウト期間内に発生しないことがあり、テストが失敗する原因となります。
1<<63 - 1
と-1 << 63
これらはGoのint64
型の最大値と最小値を表現するビット演算です。
1<<63 - 1
:int64
の最大値(math.MaxInt64
に相当)。符号付き64ビット整数の最上位ビットが0で、残りのビットがすべて1の場合。-1 << 63
:int64
の最小値(math.MinInt64
に相当)。符号付き64ビット整数の最上位ビットが1で、残りのビットがすべて0の場合。 これらの値は、time.Duration
がint64
を基にしているため、オーバーフローやアンダーフローの挙動をテストする際に使用されます。
技術的詳細
このコミットは、src/pkg/time/sleep_test.go
ファイル内のTestOverflowSleep
関数を変更しています。このテストの目的は、time.After
関数に非常に大きなDuration
値(オーバーフローする可能性のある値)や負のDuration
値を渡した場合の挙動が正しいことを確認することです。
元のコードでは、負のタイムアウトをテストするselect
ブロック内で、タイムアウトとしてtimeout
定数(25ミリ秒)を使用していました。
// Original code snippet
func TestOverflowSleep(t *testing.T) {
const timeout = 25 * Millisecond // <-- ここで25ミリ秒が定義されていた
// ...
const neg = Duration(-1 << 63)
select {
case <-After(neg):
// OK
case <-After(timeout): // <-- ここで25ミリ秒のタイムアウトが使われていた
t.Fatalf("negative timeout didn't fire")
}
}
この25ミリ秒という短いタイムアウトが、過負荷状態のシステムにおけるスケジューラのジッターによって、After(neg)
チャネルが発火する前に誤ってタイムアウトケースが選択されてしまう原因となっていました。After(neg)
(負の期間)は、即座にチャネルが発火することが期待されますが、スケジューリングの遅延により、ごくわずかな時間でも遅れると、25ミリ秒のタイムアウトに間に合わないことがあったのです。
コミットによる変更は、この問題を解決するために、負のタイムアウトのテストケースにおけるタイムアウト値を大幅に延長しました。具体的には、25ミリ秒から1秒に変更されています。
// Modified code snippet
func TestOverflowSleep(t *testing.T) {
// const timeout = 25 * Millisecond // <-- この定数は削除された
// ...
const neg = Duration(-1 << 63)
select {
case <-After(neg):
// OK
case <-After(1 * Second): // <-- タイムアウトが1秒に延長された
t.Fatalf("negative timeout didn't fire")
}
}
これにより、After(neg)
チャネルが発火するまでのわずかな遅延が、1秒という長いタイムアウト期間内に収まるようになり、テストの不安定性が解消されました。最初のAfter(timeout)
ケース(big
な値のテスト)は、元々25ミリ秒でタイムアウトすることが期待されており、その挙動は変更されていません。
コアとなるコードの変更箇所
diff --git a/src/pkg/time/sleep_test.go b/src/pkg/time/sleep_test.go
index ce2dc36322..03f8e732c9 100644
--- a/src/pkg/time/sleep_test.go
+++ b/src/pkg/time/sleep_test.go
@@ -347,19 +347,18 @@ func TestReset(t *testing.T) {
// Test that sleeping for an interval so large it overflows does not
// result in a short sleep duration.
func TestOverflowSleep(t *testing.T) {
- const timeout = 25 * Millisecond
const big = Duration(int64(1<<63 - 1))
select {\n \tcase <-After(big):\n \t\tt.Fatalf(\"big timeout fired\")
-\tcase <-After(timeout):\n+\tcase <-After(25 * Millisecond):\
\t\t// OK
\t}\
const neg = Duration(-1 << 63)\
select {\
\tcase <-After(neg):\
\t\t// OK
-\tcase <-After(timeout):\
+\tcase <-After(1 * Second):\
\t\tt.Fatalf(\"negative timeout didn\'t fire\")
\t}\
}\
コアとなるコードの解説
変更はsrc/pkg/time/sleep_test.go
ファイル内のTestOverflowSleep
関数に集中しています。
-
const timeout = 25 * Millisecond
の削除: 元のコードでは、timeout
という定数で25ミリ秒を定義していました。この定数は削除され、その値が直接使用される形に変更されました。これは、定数として保持する必要性が薄れたため、またはコードの簡潔化のためと考えられます。 -
case <-After(timeout):
からcase <-After(25 * Millisecond):
への変更:big
なDuration
値(int64
の最大値)をtime.After
に渡した場合のテストケースです。このケースでは、big
なタイムアウトが発火する前に、25ミリ秒でタイムアウトすることが期待されます。定数timeout
が削除されたため、その値が直接25 * Millisecond
として記述されました。この部分の論理的な挙動は変更されていません。 -
case <-After(timeout):
からcase <-After(1 * Second):
への変更: これがこのコミットの最も重要な変更点です。neg
なDuration
値(int64
の最小値)をtime.After
に渡した場合のテストケースです。After(neg)
は即座に発火することが期待されます。もし発火しない場合、テストはt.Fatalf
で失敗します。 元のコードでは、この「即座に発火する」ことを確認するためのタイムアウトとしてtimeout
(25ミリ秒)を使用していました。しかし、前述の通り、スケジューラのジッターにより25ミリ秒では不十分な場合がありました。 この変更により、タイムアウトが1 * Second
(1秒)に大幅に延長されました。これにより、After(neg)
チャネルが発火するまでのわずかな遅延が、この新しい長いタイムアウト期間内に確実に収まるようになり、テストの不安定性が解消されました。1秒という値は、一般的なシステム負荷やスケジューラのジッターを十分に吸収できると判断された値です。
この変更は、テストのロバスト性(堅牢性)を高め、Goランタイムのテストスイート全体の信頼性を向上させることに貢献しています。
関連リンク
- Go CL 83300044: https://golang.org/cl/83300044
参考にした情報源リンク
- コミット情報:
./commit_data/19000.txt
- GitHubコミットページ: https://github.com/golang/go/commit/2f3776ac2765a6d9cede9894efafb980c670ddb1
- Go言語の
time
パッケージに関する一般的な知識 - Go言語の
select
ステートメントに関する一般的な知識 - オペレーティングシステムおよびGoランタイムのスケジューリングに関する一般的な知識