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

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

このコミットは、Go言語のランタイムにおけるスタック使用量のテストに関する修正です。具体的には、TestStackMemというテスト関数において、スタック使用量の上限値を引き上げることで、テストの不安定性(flakiness)を解消することを目的としています。

コミット

commit 3c25cd27844fb3baf69db81199fbef4a9f9834c6
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Thu Jan 24 20:26:08 2013 +0400

    runtime: increase stack limit in a test
    Otherwise the test is flaky.
    Fixes #4698.

    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/7133071

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

https://github.com/golang/go/commit/3c25cd27844fb3baf69db81199fbef4a9f9834c6

元コミット内容

このコミットの元々の内容は、runtime: increase stack limit in a test (ランタイム: テストにおけるスタック制限の増加) という簡潔なものです。その理由として Otherwise the test is flaky. (さもなければテストが不安定になる) と述べられており、Fixes #4698. (Issue #4698 を修正) と関連するIssueが示されています。

変更の背景

この変更の背景には、Go言語のランタイムにおけるスタック管理と、それに関連するテストの不安定性があります。Goのランタイムは、ゴルーチン(goroutine)と呼ばれる軽量なスレッドのスタックを動的に管理します。ゴルーチンは最初は小さなスタックで開始し、必要に応じてスタックを拡張します。この動的なスタック管理は、メモリ効率を高める上で重要ですが、特定の条件下ではスタックの使用量が予想以上に増大し、テストが失敗する原因となることがあります。

TestStackMemというテストは、おそらくGoランタイムのスタック管理メカニズムが正しく機能しているか、特にスタックの使用量が適切に制限されているかを検証するためのものです。しかし、このテストが「flaky」(不安定)であるということは、実行環境やタイミングによって成功したり失敗したりする状況が発生していたことを示唆しています。これは、テストが想定するスタック使用量の上限が、実際のランタイムの挙動と完全に一致していなかったためと考えられます。

コミットメッセージにある Fixes #4698 は、この変更がGoのIssueトラッカーにおける特定の課題を解決することを示しています。Issue #4698の内容を確認すると、このテストの不安定性が報告されており、スタック使用量のチェックが厳しすぎることが原因であると指摘されている可能性が高いです。開発者は、テストの信頼性を向上させるために、スタック使用量の上限を現実的な値に調整する必要があると判断したと考えられます。

前提知識の解説

Go言語のゴルーチンとスタック

Go言語の並行処理の根幹をなすのが「ゴルーチン(goroutine)」です。ゴルーチンはOSのスレッドよりもはるかに軽量な実行単位であり、数百万個のゴルーチンを同時に実行することも可能です。各ゴルーチンは独自のスタックを持っていますが、このスタックは固定サイズではなく、必要に応じて動的に伸縮します。

  • スタックの動的伸縮: Goのランタイムは、ゴルーチンが関数呼び出しを行う際にスタックが不足しそうになると、自動的にスタックを拡張します。逆に、スタックの使用量が減ると、ランタイムはスタックを縮小してメモリを解放することもあります。このメカニズムにより、メモリの無駄を最小限に抑えつつ、深い再帰呼び出しなどにも対応できます。
  • スタックの初期サイズ: ゴルーチンは通常、非常に小さなスタック(例えば数KB)で開始します。これにより、多数のゴルーチンを起動しても初期メモリ消費を抑えることができます。
  • スタックの最大サイズ: ゴルーチンのスタックには、システムやアーキテクチャによって上限が設けられています。これは、無限にスタックが拡張されることを防ぎ、暴走したプログラムがシステムメモリを枯渇させるのを防ぐためです。

テストの不安定性(Flakiness)

ソフトウェアテストにおける「flakiness」とは、同じコードに対して同じテストを複数回実行した際に、成功と失敗がランダムに発生する現象を指します。これは非常に厄介な問題であり、CI/CDパイプラインの信頼性を損ない、開発者の生産性を低下させます。flakinessの一般的な原因としては以下のようなものがあります。

  • 並行処理の競合状態(Race Conditions): 複数のゴルーチンやスレッドが共有リソースにアクセスする際に、実行順序によって結果が変わる場合。
  • 時間依存性(Time Dependencies): テストが特定の時間内に完了することを期待しているが、システム負荷やスケジューリングによって実行時間が変動する場合。
  • 外部依存性(External Dependencies): データベース、ネットワークサービス、ファイルシステムなど、テストが依存する外部リソースの状態が不安定な場合。
  • リソースの制約(Resource Constraints): メモリ、CPU、ファイルディスクリプタなどのリソースが不足し、テストが失敗する場合。今回のケースでは、スタックメモリの制約がこれに該当します。
  • 不適切なアサーション: テストのアサーションが、許容される変動範囲を考慮せずに厳しすぎる場合。

今回のコミットは、スタック使用量に関するテストが、Goランタイムの動的なスタック管理の特性や、テスト実行時の微妙な環境差によって、想定よりもスタックが使用されることがあり、その結果としてテストが不安定になっていた可能性が高いです。

技術的詳細

このコミットは、src/pkg/runtime/stack_test.go ファイル内の TestStackMem 関数におけるスタック使用量のチェックロジックを変更しています。

元のコードでは、スタックの使用量 s1.StackInuse1<<20 (1メガバイト、1MB) を超える場合にテストが失敗するようになっていました。

-	if s1.StackInuse > 1<<20 {
-		t.Fatalf("Stack inuse: want %v, got %v", 1<<20, s1.StackInuse)
+	if s1.StackInuse > 4<<20 {
+		t.Fatalf("Stack inuse: want %v, got %v", 4<<20, s1.StackInuse)

変更後、この上限値は 4<<20 (4メガバイト、4MB) に引き上げられています。

この変更の技術的な意味合いは以下の通りです。

  1. スタック使用量の現実的な許容範囲の拡大: Goランタイムのスタック管理は非常に動的であり、特定の操作(例えば、深い関数呼び出し、特定のデータ構造の割り当て、コンパイラの最適化、または異なるアーキテクチャやOSでの実行)によって、一時的にスタックの使用量が変動することがあります。元の1MBという上限は、これらの変動を十分に考慮しておらず、テストが「偽陽性」(false positive)として失敗する原因となっていました。4MBへの引き上げは、より現実的なスタック使用量の変動を許容し、テストがランタイムの正常な動作範囲内で失敗しないようにするための調整です。

  2. テストの信頼性向上: テストの不安定性は、開発プロセスにおいて大きな障害となります。不安定なテストは、実際のバグを隠蔽したり、開発者がテスト結果を信用しなくなり、重要な警告を見逃す原因となったりします。この変更は、テストが正しくない理由で失敗するのを防ぎ、本当に問題がある場合にのみ失敗するようにすることで、テストスイート全体の信頼性を向上させます。

  3. Goランタイムの進化とスタック管理: Goの初期バージョンでは、スタックの動的伸縮の挙動や、特定の操作におけるスタック使用量のピークが、まだ十分に最適化されていなかった可能性があります。このコミットが行われた2013年1月は、Go言語がまだ比較的新しい時期であり、ランタイムの最適化や安定化が活発に行われていた時期です。この変更は、当時のランタイムの挙動を反映し、テストがその挙動に適合するように調整されたものと考えられます。

  4. Fixes #4698 の重要性: Issue #4698は、このテストの不安定性を具体的に報告していたものです。このIssueの内容を詳細に確認できれば、どのような状況でテストが失敗していたのか、そしてなぜ4MBという値が選ばれたのかについて、より深い洞察が得られるでしょう。一般的に、このようなスタック関連のIssueは、特定の再帰パターン、大きなローカル変数、または特定のライブラリの使用によって引き起こされることがあります。

この修正は、Goランタイムのスタック管理の根本的なロジックを変更するものではなく、その挙動を検証するテストの「許容範囲」を調整するものです。これにより、ランタイムの健全性を確認しつつ、テストの誤検出を減らすというバランスの取れたアプローチが実現されています。

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

変更は src/pkg/runtime/stack_test.go ファイルの以下の部分です。

--- a/src/pkg/runtime/stack_test.go
+++ b/src/pkg/runtime/stack_test.go
@@ -1571,7 +1571,7 @@ func TestStackMem(t *testing.T) {
 	if consumed > estimate {
 		t.Fatalf("Stack mem: want %v, got %v", estimate, consumed)
 	}
-	if s1.StackInuse > 1<<20 {
-		t.Fatalf("Stack inuse: want %v, got %v", 1<<20, s1.StackInuse)
+	if s1.StackInuse > 4<<20 {
+		t.Fatalf("Stack inuse: want %v, got %v", 4<<20, s1.StackInuse)
 	}
 }

コアとなるコードの解説

この変更は、TestStackMem 関数内のスタック使用量をチェックする条件式を修正しています。

  • s1.StackInuse: これは、テスト対象のゴルーチン(または関連するコンテキスト)が実際に使用しているスタックのバイト数を表す変数です。
  • 1<<20: これはビットシフト演算子で、1 を20ビット左にシフトすることを意味します。これは 2^20 と同じであり、結果は 1,048,576 となります。これは1メガバイト(1MB)に相当します。
  • 4<<20: 同様に、4 を20ビット左にシフトすることを意味し、4 * 2^20 となります。結果は 4,194,304 となり、これは4メガバイト(4MB)に相当します。

元のコードでは、s1.StackInuse が1MBを超えるとテストが失敗していました。しかし、このコミットではその閾値が4MBに引き上げられました。

この変更の意図は、Goランタイムのスタック管理が動的であり、特定の条件下でスタック使用量が一時的に1MBを超えることがあっても、それが異常な状態ではないと判断されたためです。テストが厳しすぎるために、ランタイムの正常な挙動を誤って「バグ」として検出してしまう「偽陽性」を防ぐことが目的です。4MBという新しい閾値は、当時のGoランタイムのスタック使用量の実態をより正確に反映し、テストの信頼性を向上させるために選ばれた値と考えられます。

関連リンク

  • Go言語のIssueトラッカー: https://github.com/golang/go/issues
  • Go言語の公式ドキュメント: https://go.dev/doc/
  • Go言語のスタック管理に関する議論(一般的な情報源):
    • "Go's work-stealing scheduler" (Goのスケジューラに関する記事でスタック管理に触れられている場合がある)
    • "Goroutine stack size" (Goのスタックサイズに関する議論)

参考にした情報源リンク

  • https://github.com/golang/go/commit/3c25cd27844fb3baf69db81199fbef4a9f9834c6
  • Go言語のIssue #4698 (このコミットが修正したIssue。詳細な情報はこのIssueページで確認できるはずです。)
  • Go言語のスタック管理に関する一般的な知識 (Goの公式ドキュメントやブログ記事など)
  • ソフトウェアテストにおける「flakiness」に関する一般的な情報 (ソフトウェアテストの書籍や記事など)
  • ビットシフト演算子に関する一般的な知識 (プログラミングの基礎知識)