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

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

このコミットは、Go言語の標準ライブラリであるnetパッケージ内のdial_test.goファイルに対する変更です。dial_test.goは、ネットワーク接続の確立(ダイヤル)に関するテストケースを定義しており、特にこの変更はruntime.PollDescという内部構造体のメモリリークを検出するためのテストを有効にすることを目的としています。

コミット

このコミットは、runtime.PollDescのリークをテストする機能を有効にします。以前はスキップされていたテストが、特定の条件下で実行されるようになり、Goランタイムにおける潜在的なメモリリークの検出に貢献します。

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

https://github.com/golang/go/commit/41451dd92b03be8fe51db16903a634b9b224c20b

元コミット内容

net: enable test for runtime.PollDesc leak

Update #5219.

R=golang-dev, dvyukov, r
CC=golang-dev
https://golang.org/cl/8602047

変更の背景

この変更の背景には、Goランタイムにおけるruntime.PollDesc構造体のメモリリークの可能性がありました。runtime.PollDescは、GoのネットワークI/O操作において、ファイルディスクリプタ(ソケットなど)の状態を監視し、非同期I/Oを効率的に処理するための内部的なデータ構造です。ネットワーク接続の確立(net.Dialなど)が失敗した場合に、このPollDescが適切にクリーンアップされず、メモリリークを引き起こす可能性が指摘されていました(Issue #5219)。

このコミット以前は、この潜在的なリークを検出するためのテスト(TestDialPollDescLeak、後にTestDialFailPDLeakに改名)が、特定の変更(CL 8318044)がまだマージされていないという理由でスキップされていました。このコミットは、その前提が解消されたため、テストを有効にし、ランタイムの安定性とメモリ管理の健全性を確保することを目的としています。

前提知識の解説

runtime.PollDescとGoのネットワークI/O

Go言語のランタイムは、効率的なネットワークI/Oを実現するために、OSの提供する非同期I/Oメカニズム(Linuxのepoll、macOSのkqueue、WindowsのIOCPなど)を抽象化した「ネットワークポーラー(net poller)」を使用しています。runtime.PollDescは、このポーラーが監視する各ファイルディスクリプタ(ネットワークソケットなど)の状態を管理するための内部的なデータ構造です。

Goのプログラムがネットワーク操作(例: net.Dialconn.Readconn.Write)を行う際、対応するファイルディスクリプタがポーラーに登録され、PollDescインスタンスが作成されます。I/O操作が完了するか、エラーが発生した場合、PollDescは適切にクリーンアップされ、リソースが解放される必要があります。もしクリーンアップが適切に行われないと、PollDescオブジェクトがメモリ上に残り続け、メモリリークを引き起こします。

net.Dial関数

net.Dialは、Go言語でネットワーク接続を確立するための基本的な関数です。指定されたネットワークプロトコル(例: "tcp")とアドレス(例: "127.0.0.1:1")を使用して、リモートホストへの接続を試みます。接続が成功するとnet.Connインターフェースを実装するオブジェクトとnilエラーを返します。失敗した場合はnilとエラーオブジェクトを返します。

testingパッケージ

Goの標準ライブラリであるtestingパッケージは、ユニットテストやベンチマークテストを作成するためのフレームワークを提供します。

  • *testing.T: テスト関数に渡される構造体で、テストの失敗を報告したり、テストをスキップしたり、ログを出力したりするためのメソッドを提供します。
  • t.Skip(args ...interface{}): 現在のテストをスキップします。通常、特定の環境や条件が満たされない場合にテストを実行しないようにするために使用されます。
  • testing.Short(): -shortフラグが指定されているかどうかを返します。時間がかかるテストを、開発中の短いテスト実行時にはスキップするために使われることがあります。

flagパッケージ

flagパッケージは、コマンドライン引数を解析するための機能を提供します。

  • flag.Bool(name string, value bool, usage string) *bool: コマンドラインフラグを定義し、そのフラグが設定されているかどうかを示すブール値へのポインタを返します。このコミットでは、-pollerフラグを定義するために使用されています。

メモリリーク

メモリリークとは、プログラムが確保したメモリ領域を、そのメモリが不要になった後も解放せずに保持し続ける現象です。これにより、利用可能なメモリが徐々に減少し、最終的にはプログラムのパフォーマンス低下やクラッシュにつながる可能性があります。Goのようなガベージコレクションを持つ言語でも、参照が残り続ける限りオブジェクトは回収されないため、論理的なメモリリークは発生し得ます。runtime.PollDescのリークは、まさにこのような状況に該当します。

技術的詳細

このコミットは、src/pkg/net/dial_test.go内のTestDialPollDescLeak(変更後はTestDialFailPDLeak)テストの動作を変更します。

  1. テストのスキップ条件の変更:

    • 変更前は、t.Skip("Test skipped pending submission of CL 8318044")という行で無条件にテストがスキップされていました。これは、このテストが依存する特定の変更(CL 8318044)がまだGoのメインリポジトリにマージされていなかったためです。
    • 変更後は、if !*testPoller { t.Skip("test disabled; use -poller to enable") }という条件に変わりました。ここでtestPollerは新しく導入されたflag.Boolで、コマンドラインで-pollerフラグが指定された場合にのみtrueになります。これにより、開発者が明示的にこのテストを有効にしない限り、通常はスキップされるようになります。これは、このテストが特定のランタイムの挙動に依存するため、すべての環境で常に実行する必要がない、あるいは実行すると問題が発生する可能性があるためと考えられます。
    • また、if testing.Short() { t.Skip("skipping PollDesc leak test in -short mode") }という行も削除されました。これは、新しい-pollerフラグによる制御で十分と判断されたためか、あるいは-shortモードでのスキップが不要になったためと考えられます。
  2. Dialerの導入:

    • 変更前は、Dial("tcp", "127.0.0.1:1")のように直接net.Dial関数を呼び出していました。
    • 変更後は、d := &Dialer{Timeout: time.Nanosecond}というDialer構造体のインスタンスを作成し、そのd.Dialメソッドを呼び出すように変更されました。Dialerは、ネットワーク接続のタイムアウトなどの設定をカスタマイズできる構造体です。ここではTimeout: time.Nanosecondと非常に短いタイムアウトを設定することで、接続がほぼ確実に失敗するようにしています。これにより、リークテストの目的である「失敗したDial試行」を確実に再現できます。
  3. エラーメッセージの変更:

    • リークが検出された際のエラーメッセージが、t.Error("net.Dial leaked memory")からt.Error("detected possible memory leak in runtime")に変更されました。これは、リークがnet.Dial関数自体ではなく、より低レベルのruntime(特にruntime.PollDesc)で発生している可能性をより正確に示唆するためです。

このテストは、Dialの失敗を意図的に多数回繰り返し、その前後でruntime.PollDescの割り当て数を監視することで、リークが発生していないかを確認します。numPollDesc関数(このコミットには含まれていませんが、テスト内で使用されていると推測されます)が、現在のPollDescの数を返す役割を担っています。

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

--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -331,14 +331,13 @@ func numFD() int {
 	panic("numFDs not implemented on " + runtime.GOOS)
 }
 
+var testPoller = flag.Bool("poller", false, "platform supports runtime-integrated poller")
+
 // Assert that a failed Dial attempt does not leak
 // runtime.PollDesc structures
-func TestDialPollDescLeak(t *testing.T) {
-	// remove once CL 8318044 is submitted
-	t.Skip("Test skipped pending submission of CL 8318044")
-
-	if testing.Short() {
-		t.Skip("skipping PollDesc leak test in -short mode")
+func TestDialFailPDLeak(t *testing.T) {
+	if !*testPoller {
+		t.Skip("test disabled; use -poller to enable")
 	}
 
 	const loops = 10
@@ -352,10 +351,11 @@ func TestDialPollDescLeak(t *testing.T) {
 		old = new
 		return delta
 	}\n+\td := &Dialer{Timeout: time.Nanosecond} // don\'t bother TCP with handshaking
 \tfailcount := 0
 \tfor i := 0; i < loops; i++ {\
 \t\tfor i := 0; i < count; i++ {\
-\t\t\tconn, err := Dial("tcp", "127.0.0.1:1")
+\t\t\tconn, err := d.Dial("tcp", "127.0.0.1:1")
 \t\t\tif err == nil {\
 \t\t\t\tt.Error("dial should not succeed")
 \t\t\t\tconn.Close()
@@ -367,7 +367,7 @@ func TestDialPollDescLeak(t *testing.T) {
 \t\t}\n \t\t// there are always some allocations on the first loop
 \t\tif failcount > 3 {\
-\t\t\tt.Error("net.Dial leaked memory")
+\t\t\tt.Error("detected possible memory leak in runtime")
 \t\t\tt.FailNow()\
 \t\t}\n \t}\n```

## コアとなるコードの解説

1.  **`var testPoller = flag.Bool("poller", false, "platform supports runtime-integrated poller")`**:
    *   この行は、新しいコマンドラインフラグ`-poller`を定義します。デフォルト値は`false`です。このフラグは、テストを実行するプラットフォームがGoランタイムに統合されたポーラーをサポートしているかどうかを示すために使用されます。これにより、特定の環境でのみテストを有効にすることができます。

2.  **`func TestDialFailPDLeak(t *testing.T) { ... }`**:
    *   テスト関数の名前が`TestDialPollDescLeak`から`TestDialFailPDLeak`に変更されました。これは、テストの目的が「失敗したDial試行におけるPollDescリーク」であることをより明確に示しています。

3.  **`if !*testPoller { t.Skip("test disabled; use -poller to enable") }`**:
    *   この条件文は、`-poller`フラグが指定されていない場合(`*testPoller`が`false`の場合)にテストをスキップします。これにより、このテストはデフォルトでは実行されず、開発者が明示的に有効にする必要があります。これは、テストが特定のランタイムの挙動に依存するため、すべての環境で常に実行する必要がない、あるいは実行すると問題が発生する可能性があるためと考えられます。

4.  **`- t.Skip("Test skipped pending submission of CL 8318044")` と `- if testing.Short() { t.Skip("skipping PollDesc leak test in -short mode") }` の削除**:
    *   以前の無条件スキップと`-short`モードでのスキップが削除されました。これは、CL 8318044がマージされたこと、および新しい`-poller`フラグによる制御で十分と判断されたためです。

5.  **`d := &Dialer{Timeout: time.Nanosecond}`**:
    *   `net.Dialer`構造体のインスタンスを作成し、`Timeout`を`time.Nanosecond`(1ナノ秒)に設定しています。これにより、`Dial`の試行がほぼ瞬時にタイムアウトし、接続が確実に失敗する状況を作り出します。これは、リークテストが「失敗したDial試行」を対象としているため、その再現性を高めるための重要な変更です。

6.  **`conn, err := d.Dial("tcp", "127.0.0.1:1")`**:
    *   直接`net.Dial`を呼び出す代わりに、上で作成した`Dialer`インスタンス`d`の`Dial`メソッドを使用しています。これにより、設定したタイムアウトが適用され、接続失敗が保証されます。

7.  **`t.Error("detected possible memory leak in runtime")`**:
    *   リークが検出された際のエラーメッセージがより汎用的な表現に変更されました。これは、リークが`net.Dial`関数自体ではなく、Goランタイムのより深い部分で発生している可能性を正確に伝えるためです。

これらの変更により、`runtime.PollDesc`のメモリリークを検出するためのテストが、より制御された方法で実行できるようになり、Goランタイムの安定性向上に貢献しています。

## 関連リンク

*   Go Issue 5219: [https://github.com/golang/go/issues/5219](https://github.com/golang.org/cl/8602047) (元のコミットメッセージに記載されているCLへのリンクがIssueへのリンクと異なるため、Issue 5219を検索して確認しました。)
*   Go `net`パッケージドキュメント: [https://pkg.go.dev/net](https://pkg.go.dev/net)
*   Go `testing`パッケージドキュメント: [https://pkg.go.dev/testing](https://pkg.go.dev/testing)
*   Go `flag`パッケージドキュメント: [https://pkg.go.dev/flag](https://pkg.go.dev/flag)

## 参考にした情報源リンク

*   Go Issue 5219 (GitHub): [https://github.com/golang/go/issues/5219](https://github.com/golang.org/cl/8602047)
*   Goのネットワークポーラーに関する一般的な情報(Goの内部動作に関するブログ記事やドキュメントなど)
    *   (具体的なURLは検索結果によるため、ここでは一般的な説明に留めますが、実際の作業では検索結果から適切な情報源を選定します。)
*   Goのメモリ管理とガベージコレクションに関する情報