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

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

このコミットは、Go言語の標準ライブラリ net/http パッケージにおけるテストの安定性向上と、テスト失敗時のデバッグ情報の改善を目的としています。具体的には、ゴルーチンリークチェッカーの不安定性(flakiness)を解消し、リークが検出された際により詳細な出力が得られるように変更が加えられています。

コミット

commit 40cd845eea345d7ae84324c8d37cd0680b243773
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Mar 27 10:07:22 2013 -0700

    net/http: improve test leak checker flakiness
    
    And make it have more useful output on failure.
    
    Update #5005
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/8016046

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

https://github.com/golang/go/commit/40cd845eea345d7ae84324c8d37cd0680b243773

元コミット内容

net/http: improve test leak checker flakiness And make it have more useful output on failure. Update #5005

変更の背景

Go言語のテストスイートでは、テスト実行後に予期せぬゴルーチン(goroutine)が残存していないかを確認する「ゴルーチンリークチェッカー」が組み込まれています。これは、テストがリソースを適切にクリーンアップしているか、あるいはデッドロックなどの問題でゴルーチンが終了しない状態になっていないかを検出するために重要です。

しかし、このリークチェッカーが不安定(flaky)であるという問題がありました。つまり、コード自体に問題がないにもかかわらず、テストの実行環境やタイミングによってリークが誤検出されたり、逆に実際のリークを見逃したりする可能性があったと考えられます。このような不安定なテストは、開発者がコードの変更に自信を持てなくさせ、CI/CDパイプラインの信頼性を低下させる原因となります。

また、リークが検出された際の出力が不十分であり、どのゴルーチンがリークしているのか、その原因は何なのかを特定しにくいという課題も存在しました。このコミットは、これらの問題を解決し、テストの信頼性とデバッグ効率を向上させることを目的としています。

コミットメッセージにある Update #5005 は、関連するIssue番号を示していますが、Go言語の公式リポジトリではこの番号のIssueは確認できませんでした。これは、内部的なトラッキング番号であるか、あるいは後に番号が変更された可能性が考えられます。

前提知識の解説

  • ゴルーチン (Goroutine): Go言語における軽量な並行処理の単位です。OSのスレッドよりもはるかに軽量で、数千、数万のゴルーチンを同時に実行することが可能です。ゴルーチンはGoランタイムによってスケジューリングされます。
  • スタックトレース (Stack Trace): プログラムの実行中にエラーや特定のイベントが発生した際に、その時点での関数呼び出しの履歴を記録したものです。これにより、どの関数がどの関数を呼び出し、最終的に問題が発生した場所に至ったのかを追跡できます。
  • テストリークチェッカー: テストが終了した後に、テスト中に開始されたにもかかわらず終了していないゴルーチンや、開かれたままのリソース(ネットワーク接続、ファイルディスクリプタなど)がないかを検出するメカニズムです。これにより、テストがシステムの状態を汚染したり、後続のテストに影響を与えたりするのを防ぎます。
  • runtime.Stack(): Go言語の標準ライブラリ runtime パッケージに含まれる関数で、現在のゴルーチンのスタックトレース、またはすべてのゴルーチンのスタックトレースを取得するために使用されます。
  • strings.Contains(): Go言語の標準ライブラリ strings パッケージに含まれる関数で、ある文字列が別の文字列の部分文字列として含まれているかどうかを判定します。
  • strings.Join(): スライス内の文字列を結合して一つの文字列を生成する関数です。

技術的詳細

このコミットは、src/pkg/net/http/z_last_test.go ファイル内の afterTest 関数に変更を加えています。この関数は、net/http パッケージのテストスイートにおいて、各テストの実行後に呼び出されるクリーンアップおよびリークチェックのロジックを担っています。

変更の核心は、ゴルーチンのスタックトレースを取得する方法と、それを分析してリークを検出する方法の改善です。

  1. スタックトレース取得方法の変更: 変更前は、runtime.Stack(buf, true) を使用して、事前に確保されたバッファ buf にすべてのゴルーチンのスタックトレースを書き込んでいました。この方法では、バッファのサイズが不十分な場合、すべてのスタックトレースを取得できない可能性がありました。 変更後は、strings.Join(interestingGoroutines(), "\n\n") を使用しています。これは、interestingGoroutines() という関数が返す「興味深い」ゴルーチンのリストを結合してスタックトレース文字列を生成します。interestingGoroutines() は、テストに関連する可能性のある特定のゴルーチンのみをフィルタリングして返す内部関数であると推測されます。これにより、不要なゴルーチンのスタックトレースを除外することで、出力のノイズを減らし、関連性の高い情報に焦点を当てることができます。また、バッファサイズの制約もなくなります。

  2. リーク検出ロジックの改善: 変更前は、badSubstring マップに定義された部分文字列がスタックトレースに含まれているかをチェックし、リークの種類を特定していました。このロジック自体は変更されていませんが、スタックトレースの取得方法が変わったことで、より正確で関連性の高いスタックトレースに対してチェックが行われるようになります。

  3. エラー出力の改善: 変更前は、t.Errorf("Test appears to have leaked %s:\\n%s", bad, strings.Join(gs, "\n\n")) のように、interestingGoroutines() が返すゴルーチンリスト gs を直接エラーメッセージに含めていました。 変更後は、t.Errorf("Test appears to have leaked %s:\\n%s", bad, stacks) のように、strings.Join(interestingGoroutines(), "\n\n") で生成された stacks 変数を直接エラーメッセージに含めるようになりました。これにより、エラーメッセージに表示されるスタックトレースが、よりフィルタリングされ、関連性の高いものになるため、デバッグが容易になります。

  4. リトライロジックの維持: for i := 0; i < 4; i++ ループと time.Sleep(250 * time.Millisecond) は、リークチェッカーが不安定な場合に備えて、複数回チェックを試みるためのリトライメカニズムです。これは、テスト終了直後にゴルーチンがまだ完全に終了していない可能性があるため、少し待ってから再チェックすることで誤検出を減らす目的があります。このロジックは変更されていません。

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

--- a/src/pkg/net/http/z_last_test.go
+++ b/src/pkg/net/http/z_last_test.go
@@ -65,8 +65,6 @@ func afterTest(t *testing.T) {
 	if testing.Short() {
 		return
 	}
-	buf := make([]byte, 1<<20)
-	var stacks string
 	var bad string
 	badSubstring := map[string]string{
 		").readLoop(":                                  "a Transport",
@@ -75,9 +73,10 @@ func afterTest(t *testing.T) {
 		"timeoutHandler":                               "a TimeoutHandler",
 		"net.(*netFD).connect(":                        "a timing out dial",
 	}
+	var stacks string
 	for i := 0; i < 4; i++ {
 		bad = ""
-		stacks = string(buf[:runtime.Stack(buf, true)])
+		stacks = strings.Join(interestingGoroutines(), "\n\n")
 		for substr, what := range badSubstring {
 			if strings.Contains(stacks, substr) {
 				bad = what
@@ -90,6 +89,5 @@ func afterTest(t *testing.T) {
 		// shutting down, so give it some time.
 		time.Sleep(250 * time.Millisecond)
 	}
-	gs := interestingGoroutines()
-	t.Errorf("Test appears to have leaked %s:\\n%s", bad, strings.Join(gs, "\n\n"))
+	t.Errorf("Test appears to have leaked %s:\\n%s", bad, stacks)
 }

コアとなるコードの解説

  • - buf := make([]byte, 1<<20)- var stacks string の削除: 以前は、runtime.Stack のために1MBのバッファ buf を確保し、その結果を stacks 変数に格納していました。このバッファは、すべてのゴルーチンのスタックトレースを格納するために使われていましたが、interestingGoroutines() を使う新しいアプローチでは不要になったため削除されました。

  • + var stacks string の移動: stacks 変数の宣言がループの直前に移動されました。これは、stacks 変数がループ内で再利用されるため、スコープを適切に設定し、初期化を明確にするためです。

  • - stacks = string(buf[:runtime.Stack(buf, true)]) の変更: この行は、runtime.Stack を呼び出してすべてのゴルーチンのスタックトレースを buf に書き込み、それを文字列に変換していました。

  • + stacks = strings.Join(interestingGoroutines(), "\n\n") への変更: この新しい行は、interestingGoroutines() 関数を呼び出します。この関数は、テストに関連する可能性のある特定のゴルーチン(例えば、HTTPサーバーの処理に関連するゴルーチンなど)のスタックトレースを文字列のスライスとして返すと推測されます。その結果を strings.Join を使って \n\n で結合し、一つの大きな文字列 stacks として格納します。これにより、不要なゴルーチンの情報が排除され、より関連性の高い情報のみがリークチェックの対象となります。

  • - gs := interestingGoroutines() の削除: 以前は、エラーメッセージの生成のために再度 interestingGoroutines() を呼び出し、その結果を gs に格納していました。

  • - t.Errorf("Test appears to have leaked %s:\\n%s", bad, strings.Join(gs, "\n\n")) の変更: 以前は、gs を再度 strings.Join で結合してエラーメッセージに含めていました。

  • + t.Errorf("Test appears to have leaked %s:\\n%s", bad, stacks) への変更: 新しいコードでは、既に strings.Join(interestingGoroutines(), "\n\n") で生成された stacks 変数を直接エラーメッセージに渡しています。これにより、冗長な処理が削減され、エラー出力の効率が向上します。また、stacks には既にフィルタリングされた「興味深い」ゴルーチンの情報が含まれているため、エラーメッセージがより簡潔で有用になります。

これらの変更により、ゴルーチンリークチェッカーは、より的を絞ったゴルーチンを監視し、リークが検出された場合には、より関連性の高いスタックトレース情報を提供できるようになり、テストの不安定性が軽減され、デバッグが容易になります。

関連リンク

参考にした情報源リンク