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

[インデックス 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のランタイムスケジューラによって利用される環境)で問題が発生していたようです。

問題の根本原因は、futexsleepEINTR(システムコールがシグナルによって中断されたことを示すエラー)やその他のシグナルを適切に処理しないことにありました。これにより、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スレッドの条件変数など、多くの同期プリミティブで発生する可能性があります。

予期せぬウェイクアップは、以下のような理由で発生することがあります。

  1. カーネルスケジューラの動作: OSのスケジューラが、パフォーマンス最適化のために、待機中のスレッドを「誤って」ウェイクアップすることがあります。
  2. 複数のウェイクアップ: 複数のスレッドが同じ条件を待機している場合、1つのsignalまたはbroadcast呼び出しが、意図したよりも多くのスレッドをウェイクアップすることがあります。
  3. シグナル: EINTRのように、システムコールがシグナルによって中断され、待機状態から抜け出すことがあります。

予期せぬウェイクアップに対処するためには、ウェイクアップされたスレッドが常に条件を再確認するループ(例: whileループ内で条件をチェックする)を使用することが重要です。

技術的詳細

このコミットの技術的詳細は、futexsleepテストがGOMAXPROCS > 1の環境で不安定になる原因と、その回避策にあります。

futexsleepは、低レベルの同期プリミティブであり、通常はカーネルのfutexシステムコールを直接利用します。このシステムコールは、EINTRのようなシグナルによって中断される可能性があります。Goランタイムが複数のOSスレッドを使用している場合(GOMAXPROCS > 1)、Goルーチンはこれらのスレッド間で自由に移動したり、OSからのシグナルを受け取ったりする可能性が高まります。

問題は、futexsleepのテストが、特定の時間だけスリープすることを期待しているにもかかわらず、EINTRやその他のシグナルによって予期せず早くウェイクアップしてしまうことにありました。これにより、テストが期待するスリープ時間を満たさずに終了し、テストが失敗するという現象が発生していました。

このコミットの解決策は、futexsleepテストの実行時に、GOMAXPROCSが1より大きい場合にはテストをスキップするというものです。これは、futexsleepEINTRやその他のシグナルを適切に処理しないという根本的な問題に対する直接的な修正ではなく、テストの不安定性を回避するための実用的なワークアラウンドです。つまり、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. このコメントは、テストをスキップする理由を明確に説明しています。futexsleepEINTRや他のシグナルを処理しないため、予期せぬウェイクアップが発生する可能性があることを示唆しています。

この変更により、GOMAXPROCSが1より大きい環境でTestFutexsleepが実行されると、テストはすぐにスキップされ、不安定な失敗を回避できるようになります。これは、テストの信頼性を向上させるための実用的な修正です。

関連リンク

  • Go言語のfutexに関する議論や実装の詳細については、Goのソースコードリポジトリや関連する設計ドキュメントを参照することが推奨されます。
  • GOMAXPROCSに関する公式ドキュメントやブログ記事も、Goランタイムのスケジューリングについて理解を深める上で役立ちます。

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Linuxカーネルのfutexに関するドキュメント
  • マルチスレッドプログラミングにおけるspurious wakeupsに関する一般的な情報源(例: POSIXスレッドのドキュメント)
  • Go言語のIssueトラッカー(#7496) - ただし、今回の検索では直接的な情報は見つかりませんでした。
  • Go言語のコードレビューシステム(golang.org/cl/72840043) - こちらも今回の検索では直接的な情報は見つかりませんでした。