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

[インデックス 15645] ファイルの概要

このコミットは、Go言語のランタイムにおけるTestStackMemテストの不安定性(flakiness)を改善するためのものです。特に、GOMAXPROCS=4設定のWindows環境で発生していたテストの失敗を解消することを目的としています。

コミット

commit 091d7210c7bbcc7497a48a41be091e1957d17717
Author: Russ Cox <rsc@golang.org>
Date:   Fri Mar 8 11:25:21 2013 -0500

    runtime: make TestStackMem a little less flaky

    Have seen failures with GOMAXPROCS=4 on Windows.

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/7626043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/091d7210c7bbcc7497a48a41be091e1957d17717

元コミット内容

runtime: make TestStackMem a little less flaky

このコミットは、TestStackMemというテストが時折失敗する(flakyである)問題を修正します。特にWindows環境でGOMAXPROCS=4の場合に失敗が確認されていました。

変更の背景

ソフトウェア開発において、テストはコードの品質と信頼性を保証するために不可欠です。しかし、テストの中には「flaky test(フレイキーテスト)」と呼ばれるものがあります。これは、コードの変更がないにもかかわらず、実行するたびに成功したり失敗したりする不安定なテストを指します。フレイキーテストは、開発者の生産性を低下させ、CI/CDパイプラインの信頼性を損ない、実際のバグを見逃す原因となるため、非常に厄介な問題です。

このコミットの背景には、Go言語のランタイムにおけるTestStackMemという特定のテストが、特にWindows環境でGOMAXPROCSが4に設定されている場合に、このような不安定な挙動を示していたという問題があります。TestStackMemは、Goのランタイムがどのようにスタックメモリを管理し、再利用するかを検証するテストであると推測されます。複数のゴルーチンが並行して動作し、スタックの割り当てと解放が頻繁に行われるようなシナリオで、タイミングの問題やリソースの競合が原因でテストが不安定になっていたと考えられます。

前提知識の解説

Flaky Test (フレイキーテスト)

前述の通り、フレイキーテストは、同じコードベースに対して実行されるたびに、成功と失敗を繰り返すテストです。主な原因としては以下が挙げられます。

  • 並行処理の競合状態 (Race Conditions): 複数のスレッドやゴルーチンが共有リソースに同時にアクセスし、その実行順序によって結果が変わる場合。
  • 外部依存性: データベース、ネットワーク、ファイルシステムなどの外部サービスが不安定であるか、テスト実行環境によって挙動が異なる場合。
  • 時間依存性: テストが特定の時間的制約(例: タイムアウト、スリープ時間)に依存しており、実行環境の負荷やスケジューリングによってタイミングがずれる場合。
  • リソースリーク: テストがリソース(メモリ、ファイルハンドルなど)を適切に解放せず、後続のテストや再実行時に影響を与える場合。

Goのランタイムとスタックメモリ

Go言語は、軽量な並行処理の単位である「ゴルーチン(goroutine)」を特徴としています。ゴルーチンは、OSのスレッドよりもはるかに軽量であり、数百万個のゴルーチンを同時に実行することも可能です。各ゴルーチンは独自のスタックを持っていますが、Goのランタイムはスタックのサイズを動的に増減させる「可変スタック(resizable stack)」の仕組みを持っています。これにより、必要なメモリを効率的に利用し、スタックオーバーフローを防ぎます。

TestStackMemのようなテストは、このスタックメモリの割り当て、解放、再利用のメカニズムが正しく機能しているかを検証します。特に、多数のゴルーチンが生成・終了を繰り返すような状況で、メモリが適切に管理されているかを確認します。

GOMAXPROCS

GOMAXPROCSは、Goプログラムが同時に実行できるOSスレッドの最大数を制御する環境変数です。デフォルトでは、利用可能なCPUコア数に設定されます。GOMAXPROCSの値を変更することで、GoスケジューラがゴルーチンをどのようにOSスレッドにマッピングし、並行実行するかを調整できます。

このコミットでGOMAXPROCS=4が問題とされているのは、複数のOSスレッドが同時に動作することで、ゴルーチンのスケジューリングやスタックメモリの管理において、より複雑な競合状態やタイミングの問題が発生しやすくなるためと考えられます。特にWindows環境では、OSのスケジューリング特性がLinuxなどと異なるため、特定の問題が顕在化することがあります。

技術的詳細

このコミットは、TestStackMemテストの不安定性を解消するために、主に2つの変更を加えています。

  1. BatchCountの削減:

    • 変更前: BatchCount = 512
    • 変更後: BatchCount = 256 BatchCountは、テスト内でゴルーチンのバッチ処理を繰り返す回数を定義している定数です。この値を半分に削減することで、テスト全体で生成・終了されるゴルーチンの総数を減らし、スタックメモリの割り当てと解放のサイクルを緩和します。これにより、特にリソースが限られている環境や、スケジューリングの競合が発生しやすい環境でのテストの負荷を軽減し、不安定な挙動を抑制する効果が期待できます。
  2. time.Sleepの追加:

    • ゴルーチンがチャネルcを介して終了準備ができたことを通知した後、time.Sleep(10 * time.Millisecond)が追加されました。
    • コメントには「The goroutines have signaled via c that they are ready to exit. Give them a chance to exit by sleeping. If we don't wait, we might not reuse them on the next batch.」とあります。 これは、ゴルーチンが終了シグナルを送った後、実際にOSスレッドから解放され、そのスタックメモリが再利用可能になるまでにわずかな時間差があることを考慮したものです。テストが次のバッチのゴルーチンをすぐに開始しようとすると、まだ完全に終了していない(またはOSによってクリーンアップされていない)ゴルーチンのリソースを再利用しようとして問題が発生する可能性があります。10ミリ秒の短いスリープを入れることで、これらのゴルーチンが完全に終了し、そのリソースがシステムに返却されるための猶予を与えます。これにより、次のバッチで新しいゴルーチンがスムーズに開始され、スタックメモリの再利用が適切に行われるようになり、テストの安定性が向上します。

これらの変更は、テストの実行タイミングとリソース管理の側面から、フレイキーテストの根本原因に対処しようとしています。BatchCountの削減はテストの規模を縮小し、time.Sleepの追加は並行処理におけるタイミングの競合を緩和します。

コアとなるコードの変更箇所

--- a/src/pkg/runtime/stack_test.go
+++ b/src/pkg/runtime/stack_test.go
@@ -1533,7 +1533,7 @@ func stack5000() (uintptr, uintptr) { var buf [5000]byte; use(buf[:]); return St
 func TestStackMem(t *testing.T) {
 	const (
 		BatchSize      = 32
-		BatchCount     = 512
+		BatchCount     = 256
 		ArraySize      = 1024
 		RecursionDepth = 128
 	)
@@ -1562,6 +1562,11 @@ func TestStackMem(t *testing.T) {
 		for i := 0; i < BatchSize; i++ {
 			<-c
 		}
+
+		// The goroutines have signaled via c that they are ready to exit.
+		// Give them a chance to exit by sleeping. If we don't wait, we
+		// might not reuse them on the next batch.
+		time.Sleep(10 * time.Millisecond)
 	}
 	s1 := new(MemStats)
 	ReadMemStats(s1)

コアとなるコードの解説

変更はsrc/pkg/runtime/stack_test.goファイル内のTestStackMem関数に集中しています。

  1. BatchCountの変更:

    -		BatchCount     = 512
    +		BatchCount     = 256
    

    BatchCount定数の値が512から256に減らされました。これは、TestStackMemが実行するゴルーチンのバッチ処理の総回数を減らすことを意味します。テストの実行回数を減らすことで、テストが完了するまでの時間や、システムにかかる負荷が軽減され、不安定な挙動が発生する可能性が低減されます。

  2. time.Sleepの追加:

    		for i := 0; i < BatchSize; i++ {
    			<-c
    		}
    +
    +		// The goroutines have signaled via c that they are ready to exit.
    +		// Give them a chance to exit by sleeping. If we don't wait, we
    +		// might not reuse them on the next batch.
    +		time.Sleep(10 * time.Millisecond)
    	}
    

    ゴルーチンがチャネルcを通じて終了準備ができたことを通知した後、time.Sleep(10 * time.Millisecond)が追加されました。このスリープは、ゴルーチンが実際に終了し、そのリソース(特にスタックメモリ)がランタイムによって再利用可能になるまでの短い時間を与えます。これにより、次のバッチで新しいゴルーチンが開始される際に、まだ完全にクリーンアップされていない古いゴルーチンのリソースを誤って再利用しようとするような競合状態を回避し、テストの信頼性を高めます。

これらの変更は、テストの実行特性を調整し、並行処理におけるタイミングの依存性を緩和することで、TestStackMemのフレイキーな挙動を修正することを目的としています。

関連リンク

  • Go言語の公式ドキュメント: https://golang.org/doc/
  • Goの並行処理(ゴルーチンとチャネル)に関するドキュメント: https://go.dev/tour/concurrency/1
  • Goのランタイムに関する情報(スタック管理など)は、Goのソースコードや関連する設計ドキュメントで詳細に解説されています。

参考にした情報源リンク

[インデックス 15645] ファイルの概要

このコミットは、Go言語のランタイムにおけるTestStackMemテストの不安定性(flakiness)を改善するためのものです。特に、GOMAXPROCS=4設定のWindows環境で発生していたテストの失敗を解消することを目的としています。

コミット

commit 091d7210c7bbcc7497a48a41be091e1957d17717
Author: Russ Cox <rsc@golang.org>
Date:   Fri Mar 8 11:25:21 2013 -0500

    runtime: make TestStackMem a little less flaky

    Have seen failures with GOMAXPROCS=4 on Windows.

    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/7626043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/091d7210c7bbcc7497a48a41be091e1957d17717

元コミット内容

runtime: make TestStackMem a little less flaky

このコミットは、TestStackMemというテストが時折失敗する(flakyである)問題を修正します。特にWindows環境でGOMAXPROCS=4の場合に失敗が確認されていました。

変更の背景

ソフトウェア開発において、テストはコードの品質と信頼性を保証するために不可欠です。しかし、テストの中には「flaky test(フレイキーテスト)」と呼ばれるものがあります。これは、コードの変更がないにもかかわらず、実行するたびに成功したり失敗したりする不安定なテストを指します。フレイキーテストは、開発者の生産性を低下させ、CI/CDパイプラインの信頼性を損ない、実際のバグを見逃す原因となるため、非常に厄介な問題です。

このコミットの背景には、Go言語のランタイムにおけるTestStackMemという特定のテストが、特にWindows環境でGOMAXPROCSが4に設定されている場合に、このような不安定な挙動を示していたという問題があります。TestStackMemは、Goのランタイムがどのようにスタックメモリを管理し、再利用するかを検証するテストであると推測されます。複数のゴルーチンが並行して動作し、スタックの割り当てと解放が頻繁に行われるようなシナリオで、タイミングの問題やリソースの競合が原因でテストが不安定になっていたと考えられます。

前提知識の解説

Flaky Test (フレイキーテスト)

前述の通り、フレイキーテストは、同じコードベースに対して実行されるたびに、成功と失敗を繰り返すテストです。主な原因としては以下が挙げられます。

  • 並行処理の競合状態 (Race Conditions): 複数のスレッドやゴルーチンが共有リソースに同時にアクセスし、その実行順序によって結果が変わる場合。
  • 外部依存性: データベース、ネットワーク、ファイルシステムなどの外部サービスが不安定であるか、テスト実行環境によって挙動が異なる場合。
  • 時間依存性: テストが特定の時間的制約(例: タイムアウト、スリープ時間)に依存しており、実行環境の負荷やスケジューリングによってタイミングがずれる場合。
  • リソースリーク: テストがリソース(メモリ、ファイルハンドルなど)を適切に解放せず、後続のテストや再実行時に影響を与える場合。

Goのランタイムとスタックメモリ

Go言語は、軽量な並行処理の単位である「ゴルーチン(goroutine)」を特徴としています。ゴルーチンは、OSのスレッドよりもはるかに軽量であり、数百万個のゴルーチンを同時に実行することも可能です。各ゴルーチンは独自のスタックを持っていますが、Goのランタイムはスタックのサイズを動的に増減させる「可変スタック(resizable stack)」の仕組みを持っています。これにより、必要なメモリを効率的に利用し、スタックオーバーフローを防ぎます。

TestStackMemのようなテストは、このスタックメモリの割り当て、解放、再利用のメカニズムが正しく機能しているかを検証します。特に、多数のゴルーチンが生成・終了を繰り返すような状況で、メモリが適切に管理されているかを確認します。

GOMAXPROCS

GOMAXPROCSは、Goプログラムが同時に実行できるOSスレッドの最大数を制御する環境変数です。デフォルトでは、利用可能なCPUコア数に設定されます。GOMAXPROCSの値を変更することで、GoスケジューラがゴルーチンをどのようにOSスレッドにマッピングし、並行実行するかを調整できます。

このコミットでGOMAXPROCS=4が問題とされているのは、複数のOSスレッドが同時に動作することで、ゴルーチンのスケジューリングやスタックメモリの管理において、より複雑な競合状態やタイミングの問題が発生しやすくなるためと考えられます。特にWindows環境では、OSのスケジューリング特性がLinuxなどと異なるため、特定の問題が顕在化することがあります。Web検索の結果からも、GOMAXPROCSの大きな値がランタイムテストの不安定性を引き起こす可能性が示唆されています。

技術的詳細

このコミットは、TestStackMemテストの不安定性を解消するために、主に2つの変更を加えています。

  1. BatchCountの削減:

    • 変更前: BatchCount = 512
    • 変更後: BatchCount = 256 BatchCountは、テスト内でゴルーチンのバッチ処理を繰り返す回数を定義している定数です。この値を半分に削減することで、テスト全体で生成・終了されるゴルーチンの総数を減らし、スタックメモリの割り当てと解放のサイクルを緩和します。これにより、特にリソースが限られている環境や、スケジューリングの競合が発生しやすい環境でのテストの負荷を軽減し、不安定な挙動を抑制する効果が期待できます。
  2. time.Sleepの追加:

    • ゴルーチンがチャネルcを介して終了準備ができたことを通知した後、time.Sleep(10 * time.Millisecond)が追加されました。
    • コメントには「The goroutines have signaled via c that they are ready to exit. Give them a chance to exit by sleeping. If we don't wait, we might not reuse them on the next batch.」とあります。 これは、ゴルーチンが終了シグナルを送った後、実際にOSスレッドから解放され、そのスタックメモリが再利用可能になるまでにわずかな時間差があることを考慮したものです。テストが次のバッチのゴルーチンをすぐに開始しようとすると、まだ完全に終了していない(またはOSによってクリーンアップされていない)ゴルーチンのリソースを再利用しようとして問題が発生する可能性があります。10ミリ秒の短いスリープを入れることで、これらのゴルーチンが完全に終了し、そのリソースがシステムに返却されるための猶予を与えます。これにより、次のバッチで新しいゴルーチンがスムーズに開始され、スタックメモリの再利用が適切に行われるようになり、テストの安定性が向上します。

これらの変更は、テストの実行タイミングとリソース管理の側面から、フレイキーテストの根本原因に対処しようとしています。BatchCountの削減はテストの規模を縮小し、time.Sleepの追加は並行処理におけるタイミングの競合を緩和します。

コアとなるコードの変更箇所

--- a/src/pkg/runtime/stack_test.go
+++ b/src/pkg/runtime/stack_test.go
@@ -1533,7 +1533,7 @@ func stack5000() (uintptr, uintptr) { var buf [5000]byte; use(buf[:]); return St
 func TestStackMem(t *testing.T) {
 	const (
 		BatchSize      = 32
-		BatchCount     = 512
+		BatchCount     = 256
 		ArraySize      = 1024
 		RecursionDepth = 128
 	)
@@ -1562,6 +1562,11 @@ func TestStackMem(t *testing.T) {
 		for i := 0; i < BatchSize; i++ {
 			<-c
 		}
+
+		// The goroutines have signaled via c that they are ready to exit.
+		// Give them a chance to exit by sleeping. If we don't wait, we
+		// might not reuse them on the next batch.
+		time.Sleep(10 * time.Millisecond)
 	}
 	s1 := new(MemStats)
 	ReadMemStats(s1)

コアとなるコードの解説

変更はsrc/pkg/runtime/stack_test.goファイル内のTestStackMem関数に集中しています。

  1. BatchCountの変更:

    -		BatchCount     = 512
    +		BatchCount     = 256
    

    BatchCount定数の値が512から256に減らされました。これは、TestStackMemが実行するゴルーチンのバッチ処理の総回数を減らすことを意味します。テストの実行回数を減らすことで、テストが完了するまでの時間や、システムにかかる負荷が軽減され、不安定な挙動が発生する可能性が低減されます。

  2. time.Sleepの追加:

    		for i := 0; i < BatchSize; i++ {
    			<-c
    		}
    +
    +		// The goroutines have signaled via c that they are ready to exit.
    +		// Give them a chance to exit by sleeping. If we don't wait, we
    +		// might not reuse them on the next batch.
    +		time.Sleep(10 * time.Millisecond)
    	}
    

    ゴルーチンがチャネルcを通じて終了準備ができたことを通知した後、time.Sleep(10 * time.Millisecond)が追加されました。このスリープは、ゴルーチンが実際に終了し、そのリソース(特にスタックメモリ)がランタイムによって再利用可能になるまでの短い時間を与えます。これにより、次のバッチで新しいゴルーチンが開始される際に、まだ完全にクリーンアップされていない古いゴルーチンのリソースを誤って再利用しようとするような競合状態を回避し、テストの信頼性を高めます。

これらの変更は、テストの実行特性を調整し、並行処理におけるタイミングの依存性を緩和することで、TestStackMemのフレイキーな挙動を修正することを目的としています。

関連リンク

  • Go言語の公式ドキュメント: https://golang.org/doc/
  • Goの並行処理(ゴルーチンとチャネル)に関するドキュメント: https://go.dev/tour/concurrency/1
  • Goのランタイムに関する情報(スタック管理など)は、Goのソースコードや関連する設計ドキュメントで詳細に解説されています。

参考にした情報源リンク