[インデックス 18809] ファイルの概要
このコミットは、Go言語のランタイムにおけるfutexsleep
テストの修正に関するものです。具体的には、src/pkg/runtime/futex_test.go
ファイルが変更されています。
コミット
commit a594f7ddd719eaf5b610bd8ed34662ed43585761
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Sat Mar 8 07:34:40 2014 +0900
runtime: fix futexsleep test on freebsd/386
Fixes #7194.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/72310043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a594f7ddd719eaf5b610bd8ed34662ed43585761
元コミット内容
このコミットは、Goランタイムのfutexsleep
テストがFreeBSD/386アーキテクチャで正しく動作しない問題を修正します。具体的には、futexsleep
が予期せず早期に終了してしまうケースに対応するため、テストロジックが改善されています。これはGoイシュー#7194を解決するものです。
変更の背景
このコミットの背景には、Goランタイムのfutexsleep
テストが特定の環境、特にFreeBSD/386で失敗するという問題がありました。futexsleep
は、Linux、DragonFly BSD、FreeBSDなどのUnix系OSで利用される、ユーザー空間のロックや同期プリミティブを効率的に実装するためのカーネル機能であるfutex(Fast Userspace muTex)に関連するGoランタイムの関数です。
Goイシュー#7194("runtime: futexsleep test fails on freebsd/386")によると、このテストはFreeBSD/386環境で、特に「2038年問題」に関連するような非常に大きなタイムアウト値(1<<31 + 100
秒、つまり約68年後)を設定した場合に、予期せず早期に終了してしまうという報告がありました。これは、FreeBSD 10カーネルにおけるユーザー空間ミューテックスのtimedwait
のセマンティクス変更が原因である可能性が示唆されています。
この問題は、futexsleep
が期待通りに指定された時間スリープせず、早期にウェイクアップしてしまうことを意味し、ランタイムの同期メカニズムの信頼性に影響を与える可能性がありました。そのため、この特定の環境でのテストの挙動を修正し、テストが誤って失敗しないようにする必要がありました。
前提知識の解説
Futex (Fast Userspace muTex)
Futexは、Linuxカーネルが提供する同期プリミティブであり、ユーザー空間で高速なロックやセマフォを実装するために使用されます。Futexの主な目的は、競合が少ない場合にはカーネルへのシステムコールを回避し、ユーザー空間だけで同期処理を完結させることで、パフォーマンスを向上させることです。競合が発生した場合にのみ、カーネルに介入を要求します。
Futexの基本的な操作は以下の2つです。
FUTEX_WAIT
(またはfutex_sleep
): 指定されたメモリアドレス(futex変数)の値が期待する値と異なる場合、またはタイムアウトが発生するまで、現在のプロセス(またはスレッド)をスリープさせます。FUTEX_WAKE
(またはfutex_wakeup
): 指定されたメモリアドレス(futex変数)で待機しているプロセス(またはスレッド)をウェイクアップします。
Goランタイムでは、ゴルーチンのスケジューリング、ミューテックスの実装、チャネルの同期など、様々な低レベルの同期処理にfutexが利用されています。
2038年問題 (Year 2038 problem)
2038年問題は、Unix時間(エポック秒)を32ビット符号付き整数で表現しているシステムにおいて、2038年1月19日03時14分07秒 (UTC) を超えるとオーバーフローが発生し、時刻が負の値として解釈されてしまう問題です。これにより、時刻に関連する処理が誤動作する可能性があります。
このコミットのテストでは、1<<31 + 100
という非常に大きなタイムアウト値が使用されています。これは、32ビット符号付き整数の最大値(2^31 - 1
秒)を超える値であり、2038年問題の影響をシミュレート、またはその境界条件でのfutexsleep
の挙動を確認しようとしています。FreeBSD/386のような32ビットシステムでは、このような大きなタイムアウト値の扱いが問題となることがあります。
Goのruntime
パッケージ
runtime
パッケージは、Goプログラムの実行環境を管理する低レベルな機能を提供します。これには、ゴルーチンのスケジューリング、メモリ管理(ガベージコレクション)、システムコールインターフェースなどが含まれます。Futexsleep
やFutexwakeup
のような関数は、このパッケージ内で定義され、OSのfutex機能と直接連携して動作します。
GOOS
とGOARCH
Goのビルドシステムでは、GOOS
環境変数でターゲットOS(例: linux
, freebsd
)を、GOARCH
環境変数でターゲットアーキテクチャ(例: amd64
, 386
)を指定します。このコミットでは、freebsd/386
という特定の組み合わせで問題が発生していることが示されています。
技術的詳細
変更されたfutex_test.go
ファイルは、futexsleep
関数の動作を検証するためのテストコードです。このテストは、futexsleep
が指定されたタイムアウト期間が経過するまでスリープし続けることを確認することを目的としています。
元のテストコードでは、単一のfutexsleep
呼び出しに対して、非常に長いタイムアウト(2038年問題に関連する値)を設定し、time.After(time.Second)
で1秒後にウェイクアップを試みていました。もしfutexsleep
が1秒以内に終了してしまった場合、テストは失敗と判断されます。
しかし、FreeBSD/386環境では、この長いタイムアウトが原因でfutexsleep
が予期せず早期に終了してしまう問題がありました。これは、カーネルがこのような大きなタイムアウト値を正しく処理できない、または32ビットシステムでの時間表現の限界に起因する可能性があります。
新しいテストコードでは、この問題をより堅牢に扱うために以下の変更が加えられています。
-
複数のテストケースの導入:
beforeY2038
: 2038年問題の影響を受けない比較的短いタイムアウト(1日)を設定。afterY2038
: 2038年問題の影響を受ける可能性のある非常に長いタイムアウト(約68年後)を設定。 これにより、異なるタイムアウト値でのfutexsleep
の挙動を個別に検証できるようになりました。
-
テストゴルーチンの並列実行とチャネルによる結果収集:
- 各テストケースはそれぞれ独立したゴルーチンで実行されます。
- 各ゴルーチンは
futexsleep
が終了した際に、結果をチャネル(tt.ch
)に送信します。 - メインのテストゴルーチンは
select
文を使用して、これらのチャネルからの結果、または1秒のタイムアウトを待ちます。
-
FreeBSD/386の特殊なハンドリング:
afterY2038
テストケースが早期に終了した場合、runtime.GOOS == "freebsd" && runtime.GOARCH == "386"
という条件で、特別なログメッセージを出力し、テストを失敗としないようにしています。これは、FreeBSD 10カーネルのtimedwait
セマンティクスの変更により、この環境ではfutexsleep
が2038年以降のタイムアウトで正しく動作しない可能性があることを認識し、その挙動を許容するためです。- この変更は、特定の環境での既知の制限を考慮しつつ、他の環境での
futexsleep
の正しい動作を検証し続けるためのものです。
この修正により、FreeBSD/386環境でのテストの誤った失敗が回避され、同時に他の環境でのfutexsleep
の健全性が引き続き検証されるようになりました。
コアとなるコードの変更箇所
変更はsrc/pkg/runtime/futex_test.go
ファイルに集中しています。
--- a/src/pkg/runtime/futex_test.go
+++ b/src/pkg/runtime/futex_test.go
@@ -2,33 +2,70 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.\n
-// Futex is only available on Dragonfly, FreeBSD and Linux.
-// The race detector emits calls to split stack functions so it breaks the test.
+// Futex is only available on DragonFly BSD, FreeBSD and Linux.
+// The race detector emits calls to split stack functions so it breaks
+// the test.
+\n
// +build dragonfly freebsd linux
// +build !race
\n
package runtime_test
\n
import (\n-\t. \"runtime\"\n+\t\"runtime\"\n \t\"testing\"\n \t\"time\"\n )\n \n+type futexsleepTest struct {\n+\tmtx uint32\n+\tns int64\n+\tmsg string\n+\tch chan futexsleepTest\n+}\n+\n+var futexsleepTests = []futexsleepTest{\n+\tbeforeY2038: {mtx: 0, ns: 86400 * 1e9, msg: \"before the year 2038\", ch: make(chan futexsleepTest, 1)},\n+\tafterY2038: {mtx: 0, ns: (1<<31 + 100) * 1e9, msg: \"after the year 2038\", ch: make(chan futexsleepTest, 1)},\n+}\n+\n+const (\n+\tbeforeY2038 = iota\n+\tafterY2038\n+)\n+\n func TestFutexsleep(t *testing.T) {\n-\tch := make(chan bool, 1)\n-\tvar dummy uint32\n \tstart := time.Now()\n-\tgo func() {\n-\t\tEntersyscall()\n-\t\tFutexsleep(&dummy, 0, (1<<31+100)*1e9)\n-\t\tExitsyscall()\n-\t\tch <- true\n-\t}()\n-\tselect {\n-\tcase <-ch:\n-\t\tt.Errorf(\"futexsleep finished early after %s!\", time.Since(start))\n-\tcase <-time.After(time.Second):\n-\t\tFutexwakeup(&dummy, 1)\n+\tfor _, tt := range futexsleepTests {\n+\t\tgo func(tt futexsleepTest) {\n+\t\t\truntime.Entersyscall()\n+\t\t\truntime.Futexsleep(&tt.mtx, tt.mtx, tt.ns)\n+\t\t\truntime.Exitsyscall()\n+\t\t\ttt.ch <- tt\n+\t\t}(tt)\n+\t}\n+loop:\n+\tfor {\n+\t\tselect {\n+\t\tcase tt := <-futexsleepTests[beforeY2038].ch:\n+\t\t\tt.Errorf(\"futexsleep test %q finished early after %s\", tt.msg, time.Since(start))\n+\t\t\tbreak loop\n+\t\tcase tt := <-futexsleepTests[afterY2038].ch:\n+\t\t\t// Looks like FreeBSD 10 kernel has changed\n+\t\t\t// the semantics of timedwait on userspace\n+\t\t\t// mutex to make broken stuff look broken.\n+\t\t\tswitch {\n+\t\t\tcase runtime.GOOS == \"freebsd\" && runtime.GOARCH == \"386\":\n+\t\t\t\tt.Log(\"freebsd/386 may not work correctly after the year 2038, see golang.org/issue/7194\")\n+\t\t\tdefault:\n+\t\t\t\tt.Errorf(\"futexsleep test %q finished early after %s\", tt.msg, time.Since(start))\n+\t\t\t\tbreak loop\n+\t\t\t}\n+\t\tcase <-time.After(time.Second):\n+\t\t\tbreak loop\n+\t\t}\n+\t}\n+\tfor _, tt := range futexsleepTests {\n+\t\truntime.Futexwakeup(&tt.mtx, 1)\n \t}\n }\n```
## コアとなるコードの解説
### 変更点1: `runtime`パッケージのインポート方法
```diff
-\t. "runtime"
+\t"runtime"
これは、runtime
パッケージの関数を修飾なしで呼び出す(例: Futexsleep
)のではなく、runtime.Futexsleep
のように明示的に修飾して呼び出すように変更されたことを示します。これはコードの可読性と保守性を向上させる一般的なGoの慣習です。
変更点2: futexsleepTest
構造体とテストケースの定義
type futexsleepTest struct {
mtx uint32
ns int64
msg string
ch chan futexsleepTest
}
var futexsleepTests = []futexsleepTest{
beforeY2038: {mtx: 0, ns: 86400 * 1e9, msg: "before the year 2038", ch: make(chan futexsleepTest, 1)},
afterY2038: {mtx: 0, ns: (1<<31 + 100) * 1e9, msg: "after the year 2038", ch: make(chan futexsleepTest, 1)},
}
const (
beforeY2038 = iota
afterY2038
)
futexsleepTest
構造体は、各テストケースのパラメータ(mtx
: futex変数、ns
: スリープ時間(ナノ秒)、msg
: テストケースの説明、ch
: 結果を送信するためのチャネル)をカプセル化します。
futexsleepTests
は、beforeY2038
(1日)とafterY2038
(約68年)という2つの異なるタイムアウト値を持つテストケースを定義しています。これにより、短いタイムアウトと非常に長いタイムアウトの両方でfutexsleep
の挙動を検証できます。
変更点3: テストロジックの変更
func TestFutexsleep(t *testing.T) {
-\tch := make(chan bool, 1)
-\tvar dummy uint32
\tstart := time.Now()
-\tgo func() {\n-\t\tEntersyscall()\n-\t\tFutexsleep(&dummy, 0, (1<<31+100)*1e9)\n-\t\tExitsyscall()\n-\t\tch <- true\n-\t}()\n-\tselect {\n-\tcase <-ch:\n-\t\tt.Errorf(\"futexsleep finished early after %s!\", time.Since(start))\n-\tcase <-time.After(time.Second):\n-\t\tFutexwakeup(&dummy, 1)\n+\tfor _, tt := range futexsleepTests {\n+\t\tgo func(tt futexsleepTest) {\n+\t\t\truntime.Entersyscall()\n+\t\t\truntime.Futexsleep(&tt.mtx, tt.mtx, tt.ns)\n+\t\t\truntime.Exitsyscall()\n+\t\t\ttt.ch <- tt\n+\t\t}(tt)\n+\t}\n+loop:\n+\tfor {\n+\t\tselect {\n+\t\tcase tt := <-futexsleepTests[beforeY2038].ch:\n+\t\t\tt.Errorf(\"futexsleep test %q finished early after %s\", tt.msg, time.Since(start))\n+\t\t\tbreak loop\n+\t\tcase tt := <-futexsleepTests[afterY2038].ch:\n+\t\t\t// Looks like FreeBSD 10 kernel has changed\n+\t\t\t// the semantics of timedwait on userspace\n+\t\t\t// mutex to make broken stuff look broken.\n+\t\t\tswitch {\n+\t\t\tcase runtime.GOOS == \"freebsd\" && runtime.GOARCH == \"386\":\n+\t\t\t\tt.Log(\"freebsd/386 may not work correctly after the year 2038, see golang.org/issue/7194\")\n+\t\t\tdefault:\n+\t\t\t\tt.Errorf(\"futexsleep test %q finished early after %s\", tt.msg, time.Since(start))\n+\t\t\t\tbreak loop\n+\t\t\t}\n+\t\tcase <-time.After(time.Second):\n+\t\t\tbreak loop\n+\t\t}\n+\t}\n+\tfor _, tt := range futexsleepTests {\n+\t\truntime.Futexwakeup(&tt.mtx, 1)\n \t}\n }\n```
* **並列実行:** `futexsleepTests`の各テストケースは、それぞれ新しいゴルーチンで実行されます。これにより、複数の`futexsleep`呼び出しを同時にテストできます。
* **チャネルによる結果通知:** 各ゴルーチンは`futexsleep`が終了すると、対応する`futexsleepTest`構造体をチャネル`tt.ch`に送信します。
* **`select`文による監視:** メインのテスト関数は`select`文を使用して、以下のいずれかのイベントを待ちます。
* `beforeY2038`テストケースが早期に終了した場合。
* `afterY2038`テストケースが早期に終了した場合。
* `time.After(time.Second)`による1秒のタイムアウト。
* **FreeBSD/386の特殊処理:** `afterY2038`テストケースが早期に終了し、かつ`runtime.GOOS == "freebsd" && runtime.GOARCH == "386"`である場合、`t.Log`で警告メッセージを出力し、テストを失敗としないようにしています。これは、この特定の環境での既知の挙動を許容するためです。それ以外の環境で早期終了した場合は、エラーとして報告されます。
* **`Futexwakeup`の呼び出し:** `select`ループが終了した後、すべての`futexsleepTests`に対して`runtime.Futexwakeup`が呼び出されます。これは、もし`futexsleep`がまだスリープ状態であった場合に、それらをウェイクアップしてリソースを解放するためです。
この変更により、テストはより堅牢になり、特定の環境での既知の制限を考慮しつつ、`futexsleep`の正しい動作をより広範なシナリオで検証できるようになりました。
## 関連リンク
* Go Issue #7194: [https://golang.org/issue/7194](https://golang.org/issue/7194)
* Go CL 72310043: [https://golang.org/cl/72310043](https://golang.org/cl/72310043)
## 参考にした情報源リンク
* [The Futex synchronization primitive](https://lwn.net/Articles/178217/)
* [Year 2038 problem - Wikipedia](https://en.wikipedia.org/wiki/Year_2038_problem)
* [Go runtime package documentation](https://pkg.go.dev/runtime)
* [Go environment variables (GOOS, GOARCH)](https://go.dev/doc/install/source#environment)
* [FreeBSD 10 Release Notes (relevant changes to timedwait semantics might be mentioned here, though specific details would require deeper dive into FreeBSD kernel documentation)](https://www.freebsd.org/releases/10.0R/relnotes/) (General reference, specific details not directly found in a quick search)
* [Go source code for futex_test.go](https://github.com/golang/go/blob/master/src/runtime/futex_test.go) (for context before and after the commit)
* [Go source code for futex.go](https://github.com/golang/go/blob/master/src/runtime/futex.go) (for understanding `Futexsleep` and `Futexwakeup` implementation)