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

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

このコミットは、Go言語の標準ライブラリnetパッケージ内のテストファイルsrc/pkg/net/dial_test.goから、TestDialFailPDLeakというテストケースを削除するものです。このテストは、Goランタイムに統合されたネットワークポーラー関連の機能が、接続失敗時にruntime.PollDesc構造体をリークしないことを検証するために作成されましたが、runtime.MemStatsに依存していたため不安定(flakey)であり、その信頼性の問題から削除されました。

コミット

commit f40f0b26b6d129d48457bf8dfd9ee2f6cbdfdb3b
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Fri May 9 09:38:29 2014 +0900

    net: drop flakey TestDialFailPDLeak
    
    TestDialFailPDLeak was created for testing runtime-integrated netwrok
    poller stuff and used during Go 1.2 development cycle. Unfortunately
    it's still flakey because it depends on MemStats of runtime, not
    pollcache directly, and MemStats accounts and revises its own stats
    occasionally.
    
    For now the codepaths related to runtime-intergrated network poller
    are pretty stable, so removing this test case never suffers us.
    
    Fixes #6553.
    
    LGTM=josharian, iant
    R=iant, josharian
    CC=golang-codereviews
    https://golang.org/cl/98080043

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

https://github.com/golang/go/commit/f40f0b26b6d129d48457bf8dfd9ee2f6cbdfdb3b

元コミット内容

net: drop flakey TestDialFailPDLeak

このコミットは、TestDialFailPDLeakというテストケースを削除します。このテストは、Go 1.2の開発サイクル中にランタイム統合ネットワークポーラーのテストのために作成されましたが、runtime.MemStatsに依存しているため不安定でした。MemStatsは自身の統計を時折集計・修正するため、直接pollcacheをテストするのではなく、間接的なMemStatsの変動に依存していたことが不安定さの原因でした。現在、ランタイム統合ネットワークポーラーに関連するコードパスは十分に安定しているため、このテストケースを削除しても問題ないと判断されました。

変更の背景

この変更の背景には、GoランタイムのネットワークI/O処理におけるメモリ管理と、テストの信頼性の問題があります。

Go言語のランタイムは、効率的なネットワークI/Oのために「ネットワークポーラー」と呼ばれるメカニズムを内部に持っています。これは、OSの提供するI/O多重化機能(Linuxのepoll、macOS/BSDのkqueue、WindowsのIOCPなど)を抽象化し、Goのgoroutineスケジューラと連携して、多数のネットワーク接続を効率的に処理します。このポーラーは、各ネットワーク接続の状態を管理するためにruntime.PollDescのような内部構造体を使用します。

TestDialFailPDLeakテストは、ネットワーク接続の確立が失敗した場合に、これらのruntime.PollDesc構造体がメモリリークしないことを検証することを目的としていました。しかし、このテストはruntime.MemStats、つまりGoランタイム全体のメモリ統計情報に依存してリークを検出していました。

問題は、runtime.MemStatsが非常に高レベルな情報であり、その統計がリアルタイムで正確にPollDescの割り当て/解放を反映するわけではないという点にありました。MemStatsはガベージコレクションの実行や他のランタイム活動によって非同期に更新され、その値が変動することがあります。このため、テストが期待するPollDescのリークがない状態であっても、MemStatsの偶発的な変動によってテストが失敗する(flakeyになる)という問題が発生していました。

Go 1.2の開発サイクル中にこのテストが導入された時点では、ランタイム統合ネットワークポーラーのコードパスがまだ成熟しておらず、リークの可能性を検証する必要がありました。しかし、コミットメッセージにあるように、この時点では関連するコードパスが十分に安定しており、もはやこの不安定なテストを維持する必要がなくなったため、削除が決定されました。不安定なテストは、開発者がコード変更を行った際に、実際には問題がないにもかかわらずテストが失敗することで、誤ったアラートを発し、開発効率を低下させるため、削除されることが望ましいとされます。

前提知識の解説

1. Goランタイムとネットワークポーラー

Go言語のランタイムは、非常に効率的な並行処理モデルを提供します。その中核をなすのがgoroutineとスケジューラです。ネットワークI/Oは本質的にブロッキング操作ですが、Goはこれを非ブロッキングI/OとOSのI/O多重化機能(epoll, kqueueなど)を組み合わせて、多数のネットワーク接続を単一のスレッドで効率的に処理します。このメカニズムが「ネットワークポーラー」です。

  • 非ブロッキングI/O: ネットワーク操作(read, write, accept, connectなど)は、即座に制御を返すように設定されます。データが準備できていない場合、操作はエラーを返します。
  • I/O多重化: OSが提供するepoll(Linux)、kqueue(macOS/BSD)、IOCP(Windows)などのシステムコールを利用して、複数のファイルディスクリプタ(ネットワークソケットも含む)の状態変化(読み込み可能、書き込み可能など)を効率的に監視します。
  • runtime.PollDesc: ネットワークポーラーが個々のファイルディスクリプタ(ソケット)の状態を管理するために使用する内部構造体です。各ソケットには対応するPollDescがあり、そのソケットが読み込み可能になったり、書き込み可能になったりしたときに、どのgoroutineを起こすべきかなどの情報が格納されています。
  • pollcache: PollDesc構造体の再利用を目的としたキャッシュです。これにより、頻繁なPollDescの割り当てと解放によるメモリオーバーヘッドを削減します。

2. runtime.MemStats

runtime.MemStatsは、Goプログラムのメモリ使用状況に関する詳細な統計情報を提供する構造体です。runtime.ReadMemStats関数を呼び出すことで、この構造体に現在のメモリ統計が書き込まれます。含まれる情報には、ヒープの割り当て量、オブジェクトの数、ガベージコレクションの統計などが含まれます。

  • Sysフィールド: MemStats構造体の一部で、GoランタイムがOSから取得したメモリの総量を示します。これには、ヒープ、スタック、Goランタイムの内部データ構造などが含まれます。
  • 不安定性(Flakiness): テストが「flakey」であるとは、同じコードベースに対して実行しても、時には成功し、時には失敗するという一貫性のない振る舞いをすることを指します。これは、テストが外部要因(実行環境、タイミング、非同期処理の完了順序など)に依存している場合に発生しやすく、テストの信頼性を損ないます。

3. Dial関数と接続失敗

GoのnetパッケージにおけるDial関数(例: net.Dial("tcp", "127.0.0.1:1"))は、指定されたネットワークアドレスへの接続を試みます。このテストでは、存在しないアドレス(例: 127.0.0.1:1)への接続を試みることで、意図的に接続失敗を発生させていました。接続が失敗した場合でも、関連するリソース(この場合はruntime.PollDesc)が適切に解放され、メモリリークが発生しないことが重要です。

技術的詳細

TestDialFailPDLeakテストは、Dialが失敗した際にruntime.PollDesc構造体がリークしないことを検証するために、以下のようなアプローチを取っていました。

  1. 多数の失敗するDial呼び出し: 存在しないアドレスに対して、多数のDial呼び出しを並行して実行します。これにより、多くのPollDescが一時的に割り当てられ、その後解放されることが期待されます。
  2. runtime.MemStatsの監視: runtime.ReadMemStatsを呼び出して、テストの前後でSysフィールド(OSから取得したメモリ総量)の差分を監視していました。テストのロジックは、Sysの増加が一定の閾値を超えた場合にメモリリークを検出したと判断していました。

このアプローチが不安定であった主な理由は、runtime.MemStatsSysフィールドがPollDescの割り当て/解放と直接的かつ即座に同期しないことにあります。

  • 間接的な測定: SysはGoランタイム全体のメモリ使用量を示すものであり、特定の内部構造体(PollDesc)の割り当て/解放を直接反映するものではありません。PollDescが解放されても、そのメモリがすぐにOSに返還されるとは限りません。Goランタイムはメモリを再利用するために保持することがあります。
  • 非同期な更新: MemStatsの統計は、ガベージコレクションの実行や他のランタイムの内部処理によって非同期に更新されます。テストがMemStatsを読み取った瞬間に、すべてのメモリ解放が反映されているとは限りません。特に、短期間に大量の割り当てと解放が行われるようなシナリオでは、統計の遅延や不正確さが顕著になる可能性があります。
  • ガベージコレクションの影響: PollDescが不要になったとしても、実際にメモリが解放され、MemStatsに反映されるのはガベージコレクションが実行された後になります。テストの実行タイミングによっては、ガベージコレクションがまだ実行されておらず、Sysの値が期待通りに減少していないように見えることがありました。

コミットメッセージにある「MemStats accounts and revises its own stats occasionally.」という記述は、この非同期性や統計の調整プロセスを指しています。これにより、テストは真のリークがない場合でも、MemStatsの変動によって誤ってリークを報告し、「flakey」な振る舞いを示していました。

Go 1.2の開発サイクル中にこのテストが導入されたのは、ネットワークポーラーのコードがまだ新しく、メモリリークの可能性を厳密に検証する必要があったためです。しかし、時間が経ち、関連するコードパスが成熟し、安定性が確認されたため、この不安定なテストを維持するメリットがなくなりました。安定したコードベースでは、このような間接的で不安定なテストは、開発者の生産性を阻害する要因となるため、削除が妥当と判断されました。

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

変更はsrc/pkg/net/dial_test.goファイルに集中しており、具体的にはTestDialFailPDLeak関数全体が削除されています。

--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -425,60 +425,6 @@ func numFD() int {
 	panic("numFDs not implemented on " + runtime.GOOS)
 }
 
-// Assert that a failed Dial attempt does not leak
-// runtime.PollDesc structures
-func TestDialFailPDLeak(t *testing.T) {
-	if testing.Short() {
-		t.Skip("skipping test in short mode")
-	}
-	if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
-		// Just skip the test because it takes too long.
-		t.Skipf("skipping test on %q/%q", runtime.GOOS, runtime.GOARCH)
-	}
-
-	maxprocs := runtime.GOMAXPROCS(0)
-	loops := 10 + maxprocs
-	// 500 is enough to turn over the chunk of pollcache.
-	// See allocPollDesc in runtime/netpoll.goc.
-	const count = 500
-	var old runtime.MemStats // used by sysdelta
-	runtime.ReadMemStats(&old)
-	sysdelta := func() uint64 {
-		var new runtime.MemStats
-		runtime.ReadMemStats(&new)
-		delta := old.Sys - new.Sys
-		old = new
-		return delta
-	}
-	d := &Dialer{Timeout: time.Nanosecond} // don't bother TCP with handshaking
-	failcount := 0
-	for i := 0; i < loops; i++ {
-		var wg sync.WaitGroup
-		for i := 0; i < count; i++ {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				if c, err := d.Dial("tcp", "127.0.0.1:1"); err == nil {
-					t.Error("dial should not succeed")
-					c.Close()
-				}
-			}()
-		}
-		wg.Wait()
-		if t.Failed() {
-			t.FailNow()
-		}
-		if delta := sysdelta(); delta > 0 {
-			failcount++
-		}
-		// there are always some allocations on the first loop
-		if failcount > maxprocs+2 {
-			t.Error("detected possible memory leak in runtime")
-			t.FailNow()
-		}
-	}
-}
-
 func TestDialer(t *testing.T) {
 	ln, err := Listen("tcp4", "127.0.0.1:0")
 	if err != nil {

コアとなるコードの解説

削除されたTestDialFailPDLeak関数は、以下の主要な要素で構成されていました。

  • テストのスキップ条件:
    • testing.Short()モードの場合、テストをスキップします。
    • windows/386アーキテクチャの場合も、テストが非常に時間がかかるためスキップします。
  • maxprocsloops: runtime.GOMAXPROCS(0)で現在の論理CPU数を取得し、それに基づいてテストのループ回数を設定していました。これは、並行処理の負荷を考慮したものです。
  • count定数: 500という値が設定されており、これはpollcacheのチャンクを十分に回転させるための数とコメントされています。allocPollDescruntime/netpoll.gocで定義されており、PollDescの割り当てに関連する内部関数です。
  • runtime.MemStatssysdelta関数:
    • var old runtime.MemStatsで古いメモリ統計を保持します。
    • runtime.ReadMemStats(&old)で初期のメモリ統計を読み込みます。
    • sysdeltaというクロージャが定義されており、これは現在のMemStatsを読み込み、old.Sysnew.Sysの差分(delta)を計算し、oldnewで更新する役割を持っていました。このdeltaが、OSから取得したメモリの増加量を示していました。
  • Dialerの設定: d := &Dialer{Timeout: time.Nanosecond}として、タイムアウトを非常に短く設定したDialerを作成していました。これは、TCPハンドシェイクを待たずに即座に接続失敗を発生させるためです。
  • 並行Dial呼び出し:
    • loops回繰り返される外側のループ。
    • 内側のループではcount回、go func() { ... }()でgoroutineを起動し、並行してd.Dial("tcp", "127.0.0.1:1")を呼び出していました。127.0.0.1:1は通常、接続できないアドレスであり、これによりDialは失敗します。
    • wg.Wait()で、すべての並行Dial呼び出しが完了するのを待っていました。
  • リーク検出ロジック:
    • if delta := sysdelta(); delta > 0 { failcount++ }sysdeltaが正の値(OSから取得したメモリが増加した)であれば、failcountをインクリメントしていました。
    • if failcount > maxprocs+2 { t.Error("detected possible memory leak in runtime"); t.FailNow() }failcountが特定の閾値(maxprocs+2)を超えた場合、メモリリークを検出したと判断し、テストを失敗させていました。最初のループでは常にいくらかの割り当てがあるため、failcountの閾値が設定されていました。

このテストの削除は、この複雑で不安定なリーク検出ロジックが、もはやGoランタイムのネットワークポーラーの安定性を検証する上で必要ないと判断されたことを意味します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (runtimeパッケージ、netパッケージなど)
  • Go言語のソースコード (特にsrc/runtime/netpoll.go, src/runtime/mstats.goなど)
  • Go言語のテストに関する一般的な情報 (flakey testsなど)
  • Goのネットワークポーラーに関する技術記事や解説
  • epoll, kqueue, IOCPなどのOSのI/O多重化メカニズムに関する情報
  • runtime.MemStatsに関するGoのドキュメントや解説# [インデックス 19301] ファイルの概要

このコミットは、Go言語の標準ライブラリnetパッケージ内のテストファイルsrc/pkg/net/dial_test.goから、TestDialFailPDLeakというテストケースを削除するものです。このテストは、Goランタイムに統合されたネットワークポーラー関連の機能が、接続失敗時にruntime.PollDesc構造体をリークしないことを検証するために作成されましたが、runtime.MemStatsに依存していたため不安定(flakey)であり、その信頼性の問題から削除されました。

コミット

commit f40f0b26b6d129d48457bf8dfd9ee2f6cbdfdb3b
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Fri May 9 09:38:29 2014 +0900

    net: drop flakey TestDialFailPDLeak
    
    TestDialFailPDLeak was created for testing runtime-integrated netwrok
    poller stuff and used during Go 1.2 development cycle. Unfortunately
    it's still flakey because it depends on MemStats of runtime, not
    pollcache directly, and MemStats accounts and revises its own stats
    occasionally.
    
    For now the codepaths related to runtime-intergrated network poller
    are pretty stable, so removing this test case never suffers us.
    
    Fixes #6553.
    
    LGTM=josharian, iant
    R=iant, josharian
    CC=golang-codereviews
    https://golang.org/cl/98080043

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

https://github.com/golang/go/commit/f40f0b26b6d129d48457bf8dfd9ee2f6cbdfdb3b

元コミット内容

net: drop flakey TestDialFailPDLeak

このコミットは、TestDialFailPDLeakというテストケースを削除します。このテストは、Go 1.2の開発サイクル中にランタイム統合ネットワークポーラーのテストのために作成されましたが、runtime.MemStatsに依存しているため不安定でした。MemStatsは自身の統計を時折集計・修正するため、直接pollcacheをテストするのではなく、間接的なMemStatsの変動に依存していたことが不安定さの原因でした。現在、ランタイム統合ネットワークポーラーに関連するコードパスは十分に安定しているため、このテストケースを削除しても問題ないと判断されました。

変更の背景

この変更の背景には、GoランタイムのネットワークI/O処理におけるメモリ管理と、テストの信頼性の問題があります。

Go言語のランタイムは、効率的な並行処理モデルを提供します。その中核をなすのがgoroutineとスケジューラです。ネットワークI/Oは本質的にブロッキング操作ですが、Goはこれを非ブロッキングI/OとOSのI/O多重化機能(Linuxのepoll、macOS/BSDのkqueue、WindowsのIOCPなど)を組み合わせて、多数のネットワーク接続を単一のスレッドで効率的に処理します。このメカニズムが「ネットワークポーラー」です。

このポーラーは、各ネットワーク接続の状態を管理するためにruntime.PollDescのような内部構造体を使用します。TestDialFailPDLeakテストは、ネットワーク接続の確立が失敗した場合に、これらのruntime.PollDesc構造体がメモリリークしないことを検証することを目的としていました。

しかし、このテストはruntime.MemStats、つまりGoランタイム全体のメモリ統計情報に依存してリークを検出していました。問題は、runtime.MemStatsが非常に高レベルな情報であり、その統計がリアルタイムで正確にPollDescの割り当て/解放を反映するわけではないという点にありました。MemStatsはガベージコレクションの実行や他のランタイム活動によって非同期に更新され、その値が変動することがあります。このため、テストが期待するPollDescのリークがない状態であっても、MemStatsの偶発的な変動によってテストが失敗する(flakeyになる)という問題が発生していました。

Go 1.2の開発サイクル中にこのテストが導入された時点では、ランタイム統合ネットワークポーラーのコードパスがまだ成熟しておらず、リークの可能性を検証する必要がありました。しかし、コミットメッセージにあるように、この時点では関連するコードパスが十分に安定しており、もはやこの不安定なテストを維持する必要がなくなったため、削除が決定されました。不安定なテストは、開発者がコード変更を行った際に、実際には問題がないにもかかわらずテストが失敗することで、誤ったアラートを発し、開発効率を低下させるため、削除されることが望ましいとされます。

前提知識の解説

1. Goランタイムとネットワークポーラー

Go言語のランタイムは、非常に効率的な並行処理モデルを提供します。その中核をなすのがgoroutineとスケジューラです。ネットワークI/Oは本質的にブロッキング操作ですが、Goはこれを非ブロッキングI/OとOSのI/O多重化機能(epoll, kqueueなど)を組み合わせて、多数のネットワーク接続を単一のスレッドで効率的に処理します。このメカニズムが「ネットワークポーラー」です。

  • 非ブロッキングI/O: ネットワーク操作(read, write, accept, connectなど)は、即座に制御を返すように設定されます。データが準備できていない場合、操作はエラーを返します。
  • I/O多重化: OSが提供するepoll(Linux)、kqueue(macOS/BSD)、IOCP(Windows)などのシステムコールを利用して、複数のファイルディスクリプタ(ネットワークソケットも含む)の状態変化(読み込み可能、書き込み可能など)を効率的に監視します。
  • runtime.PollDesc: ネットワークポーラーが個々のファイルディスクリプタ(ソケット)の状態を管理するために使用する内部構造体です。各ソケットには対応するPollDescがあり、そのソケットが読み込み可能になったり、書き込み可能になったりしたときに、どのgoroutineを起こすべきかなどの情報が格納されています。
  • pollcache: PollDesc構造体の再利用を目的としたキャッシュです。これにより、頻繁なPollDescの割り当てと解放によるメモリオーバーヘッドを削減します。

2. runtime.MemStats

runtime.MemStatsは、Goプログラムのメモリ使用状況に関する詳細な統計情報を提供する構造体です。runtime.ReadMemStats関数を呼び出すことで、この構造体に現在のメモリ統計が書き込まれます。含まれる情報には、ヒープの割り当て量、オブジェクトの数、ガベージコレクションの統計などが含まれます。

  • Sysフィールド: MemStats構造体の一部で、GoランタイムがOSから取得したメモリの総量を示します。これには、ヒープ、スタック、Goランタイムの内部データ構造などが含まれます。
  • 不安定性(Flakiness): テストが「flakey」であるとは、同じコードベースに対して実行しても、時には成功し、時には失敗するという一貫性のない振る舞いをすることを指します。これは、テストが外部要因(実行環境、タイミング、非同期処理の完了順序など)に依存している場合に発生しやすく、テストの信頼性を損ないます。

3. Dial関数と接続失敗

GoのnetパッケージにおけるDial関数(例: net.Dial("tcp", "127.0.0.1:1"))は、指定されたネットワークアドレスへの接続を試みます。このテストでは、存在しないアドレス(例: 127.0.0.1:1)への接続を試みることで、意図的に接続失敗を発生させていました。接続が失敗した場合でも、関連するリソース(この場合はruntime.PollDesc)が適切に解放され、メモリリークが発生しないことが重要です。

技術的詳細

TestDialFailPDLeakテストは、Dialが失敗した際にruntime.PollDesc構造体がリークしないことを検証するために、以下のようなアプローチを取っていました。

  1. 多数の失敗するDial呼び出し: 存在しないアドレスに対して、多数のDial呼び出しを並行して実行します。これにより、多くのPollDescが一時的に割り当てられ、その後解放されることが期待されます。
  2. runtime.MemStatsの監視: runtime.ReadMemStatsを呼び出して、テストの前後でSysフィールド(OSから取得したメモリ総量)の差分を監視していました。テストのロジックは、Sysの増加が一定の閾値を超えた場合にメモリリークを検出したと判断していました。

このアプローチが不安定であった主な理由は、runtime.MemStatsSysフィールドがPollDescの割り当て/解放と直接的かつ即座に同期しないことにあります。

  • 間接的な測定: SysはGoランタイム全体のメモリ使用量を示すものであり、特定の内部構造体(PollDesc)の割り当て/解放を直接反映するものではありません。PollDescが解放されても、そのメモリがすぐにOSに返還されるとは限りません。Goランタイムはメモリを再利用するために保持することがあります。
  • 非同期な更新: MemStatsの統計は、ガベージコレクションの実行や他のランタイムの内部処理によって非同期に更新されます。テストがMemStatsを読み取った瞬間に、すべてのメモリ解放が反映されているとは限りません。特に、短期間に大量の割り当てと解放が行われるようなシナリオでは、統計の遅延や不正確さが顕著になる可能性があります。
  • ガベージコレクションの影響: PollDescが不要になったとしても、実際にメモリが解放され、MemStatsに反映されるのはガベージコレクションが実行された後になります。テストの実行タイミングによっては、ガベージコレクションがまだ実行されておらず、Sysの値が期待通りに減少していないように見えることがありました。

コミットメッセージにある「MemStats accounts and revises its own stats occasionally.」という記述は、この非同期性や統計の調整プロセスを指しています。これにより、テストは真のリークがない場合でも、MemStatsの変動によって誤ってリークを報告し、「flakey」な振る舞いを示していました。

Go 1.2の開発サイクル中にこのテストが導入されたのは、ネットワークポーラーのコードがまだ新しく、メモリリークの可能性を厳密に検証する必要があったためです。しかし、時間が経ち、関連するコードパスが成熟し、安定性が確認されたため、この不安定なテストを維持するメリットがなくなりました。安定したコードベースでは、このような間接的で不安定なテストは、開発者の生産性を阻害する要因となるため、削除が妥当と判断されました。

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

変更はsrc/pkg/net/dial_test.goファイルに集中しており、具体的にはTestDialFailPDLeak関数全体が削除されています。

--- a/src/pkg/net/dial_test.go
+++ b/src/pkg/net/dial_test.go
@@ -425,60 +425,6 @@ func numFD() int {
 	panic("numFDs not implemented on " + runtime.GOOS)
 }
 
-// Assert that a failed Dial attempt does not leak
-// runtime.PollDesc structures
-func TestDialFailPDLeak(t *testing.T) {
-	if testing.Short() {
-		t.Skip("skipping test in short mode")
-	}
-	if runtime.GOOS == "windows" && runtime.GOARCH == "386" {
-		// Just skip the test because it takes too long.
-		t.Skipf("skipping test on %q/%q", runtime.GOOS, runtime.GOARCH)
-	}
-
-	maxprocs := runtime.GOMAXPROCS(0)
-	loops := 10 + maxprocs
-	// 500 is enough to turn over the chunk of pollcache.
-	// See allocPollDesc in runtime/netpoll.goc.
-	const count = 500
-	var old runtime.MemStats // used by sysdelta
-	runtime.ReadMemStats(&old)
-	sysdelta := func() uint64 {
-		var new runtime.MemStats
-		runtime.ReadMemStats(&new)
-		delta := old.Sys - new.Sys
-		old = new
-		return delta
-	}
-	d := &Dialer{Timeout: time.Nanosecond} // don't bother TCP with handshaking
-	failcount := 0
-	for i := 0; i < loops; i++ {
-		var wg sync.WaitGroup
-		for i := 0; i < count; i++ {
-			wg.Add(1)
-			go func() {
-				defer wg.Done()
-				if c, err := d.Dial("tcp", "127.0.0.1:1"); err == nil {
-					t.Error("dial should not succeed")
-					c.Close()
-				}
-			}()
-		}
-		wg.Wait()
-		if t.Failed() {
-			t.FailNow()
-		}
-		if delta := sysdelta(); delta > 0 {
-			failcount++
-		}
-		// there are always some allocations on the first loop
-		if failcount > maxprocs+2 {
-			t.Error("detected possible memory leak in runtime")
-			t.FailNow()
-		}
-	}
-}
-
 func TestDialer(t *testing.T) {
 	ln, err := Listen("tcp4", "127.0.0.1:0")
 	if err != nil {

コアとなるコードの解説

削除されたTestDialFailPDLeak関数は、以下の主要な要素で構成されていました。

  • テストのスキップ条件:
    • testing.Short()モードの場合、テストをスキップします。
    • windows/386アーキテクチャの場合も、テストが非常に時間がかかるためスキップします。
  • maxprocsloops: runtime.GOMAXPROCS(0)で現在の論理CPU数を取得し、それに基づいてテストのループ回数を設定していました。これは、並行処理の負荷を考慮したものです。
  • count定数: 500という値が設定されており、これはpollcacheのチャンクを十分に回転させるための数とコメントされています。allocPollDescruntime/netpoll.gocで定義されており、PollDescの割り当てに関連する内部関数です。
  • runtime.MemStatssysdelta関数:
    • var old runtime.MemStatsで古いメモリ統計を保持します。
    • runtime.ReadMemStats(&old)で初期のメモリ統計を読み込みます。
    • sysdeltaというクロージャが定義されており、これは現在のMemStatsを読み込み、old.Sysnew.Sysの差分(delta)を計算し、oldnewで更新する役割を持っていました。このdeltaが、OSから取得したメモリの増加量を示していました。
  • Dialerの設定: d := &Dialer{Timeout: time.Nanosecond}として、タイムアウトを非常に短く設定したDialerを作成していました。これは、TCPハンドシェイクを待たずに即座に接続失敗を発生させるためです。
  • 並行Dial呼び出し:
    • loops回繰り返される外側のループ。
    • 内側のループではcount回、go func() { ... }()でgoroutineを起動し、並行してd.Dial("tcp", "127.0.0.1:1")を呼び出していました。127.0.0.1:1は通常、接続できないアドレスであり、これによりDialは失敗します。
    • wg.Wait()で、すべての並行Dial呼び出しが完了するのを待っていました。
  • リーク検出ロジック:
    • if delta := sysdelta(); delta > 0 { failcount++ }sysdeltaが正の値(OSから取得したメモリが増加した)であれば、failcountをインクリメントしていました。
    • if failcount > maxprocs+2 { t.Error("detected possible memory leak in runtime"); t.FailNow() }failcountが特定の閾値(maxprocs+2)を超えた場合、メモリリークを検出したと判断し、テストを失敗させていました。最初のループでは常にいくらかの割り当てがあるため、failcountの閾値が設定されていました。

このテストの削除は、この複雑で不安定なリーク検出ロジックが、もはやGoランタイムのネットワークポーラーの安定性を検証する上で必要ないと判断されたことを意味します。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (runtimeパッケージ、netパッケージなど)
  • Go言語のソースコード (特にsrc/runtime/netpoll.go, src/runtime/mstats.goなど)
  • Go言語のテストに関する一般的な情報 (flakey testsなど)
  • Goのネットワークポーラーに関する技術記事や解説
  • epoll, kqueue, IOCPなどのOSのI/O多重化メカニズムに関する情報
  • runtime.MemStatsに関するGoのドキュメントや解説