[インデックス 18813] ファイルの概要
このコミットは、Go言語のランタイムにおけるfutexsleep
テストの不安定性(flakiness)を修正するものです。具体的には、GOMAXPROCS
が1より大きい場合に発生する可能性のある、futexsleep
の予期せぬウェイクアップ(spurious wakeups)によるテストの失敗を回避するための変更が加えられています。
コミット
commit 4888781f2485e276938867c5d9fe6f5d0477520b
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Mon Mar 10 12:20:16 2014 +0900
runtime: fix flakiness on futexsleep test
Fixes #7496.
LGTM=jsing
R=golang-codereviews, jsing
CC=golang-codereviews
https://golang.org/cl/72840043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4888781f2485e276938867c5d9fe6f5d0477520b
元コミット内容
runtime: fix flakiness on futexsleep test
Fixes #7496.
LGTM=jsing
R=golang-codereviews, jsing
CC=golang-codereviews
https://golang.org/cl/72840043
変更の背景
このコミットは、Goランタイムのfutexsleep
テストが不安定であるという問題(flakiness)を解決するために導入されました。不安定なテストとは、コードの変更がないにもかかわらず、実行するたびに成功したり失敗したりするテストのことです。このようなテストは、実際のバグを隠蔽したり、開発者がテスト結果を信頼できなくしたりするため、開発プロセスにおいて非常に厄介な問題となります。
コミットメッセージには「Fixes #7496」とあり、これはGoのIssueトラッカーにおける7496番の課題を修正したことを示しています。この課題は、futexsleep
テストが特定の条件下で失敗する現象を報告していたと考えられます。特に、GOMAXPROCS
が1より大きい環境(つまり、複数のOSスレッドがGoのランタイムスケジューラによって利用される環境)で問題が発生していたようです。
問題の根本原因は、futexsleep
がEINTR
(システムコールがシグナルによって中断されたことを示すエラー)やその他のシグナルを適切に処理しないことにありました。これにより、futexsleep
が予期せずウェイクアップ(spurious wakeups)することがあり、テストが期待する動作と異なる結果を返すことがありました。
前提知識の解説
Futex (Fast Userspace Mutex)
Futex(Fast Userspace Mutex)は、Linuxカーネルが提供する同期プリミティブであり、ユーザー空間のプログラムが効率的にロックや待機/通知のメカニズムを実装するために使用されます。Futexは、競合が少ない場合にはカーネルへのシステムコールを回避し、ユーザー空間で直接操作を完結させることで、高いパフォーマンスを実現します。競合が発生した場合にのみ、カーネルに介入を要求します。
futexsleep
は、特定のメモリアドレス(futex変数)の値が期待する値と異なる場合に、現在のスレッドをスリープさせる操作です。このスリープは、別のスレッドが同じfutex変数を操作し、futexwake
を呼び出すことで解除されます。
GOMAXPROCS
GOMAXPROCS
は、Goランタイムが同時に実行できるOSスレッドの最大数を制御する環境変数または関数です。Goのスケジューラは、この設定値に基づいて、GoルーチンをOSスレッドにマッピングし、実行します。
GOMAXPROCS=1
の場合:Goランタイムは単一のOSスレッドのみを使用します。この場合、Goルーチンの並行実行は、その単一スレッド上での協調的マルチタスクによって実現されます。GOMAXPROCS > 1
の場合:Goランタイムは複数のOSスレッドを使用し、Goルーチンをこれらのスレッドに分散して実行します。これにより、真の並列実行(マルチコアプロセッサ上での同時実行)が可能になります。
EINTR (Interrupted System Call)
EINTR
は、システムコールが完了する前にシグナルによって中断されたことを示すエラーコードです。多くのシステムコール(特にブロックする可能性のあるもの、例えばI/O操作や待機操作)は、シグナルを受信するとEINTR
エラーを返して中断されることがあります。プログラムは通常、EINTR
を受け取った場合にシステムコールを再試行するか、適切にエラーを処理する必要があります。
Spurious Wakeups (予期せぬウェイクアップ)
Spurious wakeups(予期せぬウェイクアップ)とは、条件変数を待機しているスレッドが、その条件が実際に満たされていないにもかかわらず、ウェイクアップされる現象を指します。これは、マルチスレッドプログラミングにおける一般的な問題であり、POSIXスレッドの条件変数など、多くの同期プリミティブで発生する可能性があります。
予期せぬウェイクアップは、以下のような理由で発生することがあります。
- カーネルスケジューラの動作: OSのスケジューラが、パフォーマンス最適化のために、待機中のスレッドを「誤って」ウェイクアップすることがあります。
- 複数のウェイクアップ: 複数のスレッドが同じ条件を待機している場合、1つの
signal
またはbroadcast
呼び出しが、意図したよりも多くのスレッドをウェイクアップすることがあります。 - シグナル:
EINTR
のように、システムコールがシグナルによって中断され、待機状態から抜け出すことがあります。
予期せぬウェイクアップに対処するためには、ウェイクアップされたスレッドが常に条件を再確認するループ(例: while
ループ内で条件をチェックする)を使用することが重要です。
技術的詳細
このコミットの技術的詳細は、futexsleep
テストがGOMAXPROCS > 1
の環境で不安定になる原因と、その回避策にあります。
futexsleep
は、低レベルの同期プリミティブであり、通常はカーネルのfutex
システムコールを直接利用します。このシステムコールは、EINTR
のようなシグナルによって中断される可能性があります。Goランタイムが複数のOSスレッドを使用している場合(GOMAXPROCS > 1
)、Goルーチンはこれらのスレッド間で自由に移動したり、OSからのシグナルを受け取ったりする可能性が高まります。
問題は、futexsleep
のテストが、特定の時間だけスリープすることを期待しているにもかかわらず、EINTR
やその他のシグナルによって予期せず早くウェイクアップしてしまうことにありました。これにより、テストが期待するスリープ時間を満たさずに終了し、テストが失敗するという現象が発生していました。
このコミットの解決策は、futexsleep
テストの実行時に、GOMAXPROCS
が1より大きい場合にはテストをスキップするというものです。これは、futexsleep
がEINTR
やその他のシグナルを適切に処理しないという根本的な問題に対する直接的な修正ではなく、テストの不安定性を回避するための実用的なワークアラウンドです。つまり、GOMAXPROCS > 1
の環境では、futexsleep
の動作が不安定になる可能性があるため、そのテストを一時的に無効にすることで、CI/CDパイプラインの安定性を確保しています。
このアプローチは、問題の根本的な解決にはなりませんが、テストの不安定性によって開発の妨げになることを防ぎます。将来的には、futexsleep
の実装自体がEINTR
やシグナルを適切に処理するように改善されるか、より堅牢なテストメカニズムが導入される可能性があります。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/futex_test.go
ファイルにあります。
--- a/src/pkg/runtime/futex_test.go
+++ b/src/pkg/runtime/futex_test.go
@@ -35,6 +35,12 @@ const (
)
func TestFutexsleep(t *testing.T) {
+ if runtime.GOMAXPROCS(0) > 1 {
+ // futexsleep doesn't handle EINTR or other signals,
+ // so spurious wakeups may happen.
+ t.Skip("skipping; GOMAXPROCS>1")
+ }
+
start := time.Now()
for _, tt := range futexsleepTests {
go func(tt futexsleepTest) {
コアとなるコードの解説
追加されたコードは、TestFutexsleep
関数の冒頭に配置されています。
if runtime.GOMAXPROCS(0) > 1 {
// futexsleep doesn't handle EINTR or other signals,
// so spurious wakeups may happen.
t.Skip("skipping; GOMAXPROCS>1")
}
runtime.GOMAXPROCS(0)
: この関数呼び出しは、現在のGOMAXPROCS
の値を返します。引数に0
を渡すことで、値を変更せずに現在の設定値を取得できます。> 1
:GOMAXPROCS
が1より大きいかどうかをチェックします。これは、Goランタイムが複数のOSスレッドを使用している状態を意味します。t.Skip("skipping; GOMAXPROCS>1")
:GOMAXPROCS
が1より大きい場合、testing
パッケージのSkip
メソッドを呼び出します。このメソッドが呼び出されると、現在のテストはスキップされ、テストスイートは次のテストの実行に移ります。引数として渡された文字列は、テストがスキップされた理由としてテスト結果に表示されます。- コメント:
// futexsleep doesn't handle EINTR or other signals, // so spurious wakeups may happen.
このコメントは、テストをスキップする理由を明確に説明しています。futexsleep
がEINTR
や他のシグナルを処理しないため、予期せぬウェイクアップが発生する可能性があることを示唆しています。
この変更により、GOMAXPROCS
が1より大きい環境でTestFutexsleep
が実行されると、テストはすぐにスキップされ、不安定な失敗を回避できるようになります。これは、テストの信頼性を向上させるための実用的な修正です。
関連リンク
- Go言語の
futex
に関する議論や実装の詳細については、Goのソースコードリポジトリや関連する設計ドキュメントを参照することが推奨されます。 GOMAXPROCS
に関する公式ドキュメントやブログ記事も、Goランタイムのスケジューリングについて理解を深める上で役立ちます。
参考にした情報源リンク
- Go言語の公式ドキュメント
- Linuxカーネルのfutexに関するドキュメント
- マルチスレッドプログラミングにおけるspurious wakeupsに関する一般的な情報源(例: POSIXスレッドのドキュメント)
- Go言語のIssueトラッカー(#7496) - ただし、今回の検索では直接的な情報は見つかりませんでした。
- Go言語のコードレビューシステム(golang.org/cl/72840043) - こちらも今回の検索では直接的な情報は見つかりませんでした。