[インデックス 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
構造体がリークしないことを検証するために、以下のようなアプローチを取っていました。
- 多数の失敗する
Dial
呼び出し: 存在しないアドレスに対して、多数のDial
呼び出しを並行して実行します。これにより、多くのPollDesc
が一時的に割り当てられ、その後解放されることが期待されます。 runtime.MemStats
の監視:runtime.ReadMemStats
を呼び出して、テストの前後でSys
フィールド(OSから取得したメモリ総量)の差分を監視していました。テストのロジックは、Sys
の増加が一定の閾値を超えた場合にメモリリークを検出したと判断していました。
このアプローチが不安定であった主な理由は、runtime.MemStats
のSys
フィールドが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
アーキテクチャの場合も、テストが非常に時間がかかるためスキップします。
maxprocs
とloops
:runtime.GOMAXPROCS(0)
で現在の論理CPU数を取得し、それに基づいてテストのループ回数を設定していました。これは、並行処理の負荷を考慮したものです。count
定数:500
という値が設定されており、これはpollcache
のチャンクを十分に回転させるための数とコメントされています。allocPollDesc
はruntime/netpoll.goc
で定義されており、PollDesc
の割り当てに関連する内部関数です。runtime.MemStats
とsysdelta
関数:var old runtime.MemStats
で古いメモリ統計を保持します。runtime.ReadMemStats(&old)
で初期のメモリ統計を読み込みます。sysdelta
というクロージャが定義されており、これは現在のMemStats
を読み込み、old.Sys
とnew.Sys
の差分(delta
)を計算し、old
をnew
で更新する役割を持っていました。この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 issue #6553: https://github.com/golang/go/issues/6553 (このコミットが修正したイシュー)
- Go CL 98080043: https://golang.org/cl/98080043 (このコミットに対応するGerritの変更リスト)
参考にした情報源リンク
- 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
構造体がリークしないことを検証するために、以下のようなアプローチを取っていました。
- 多数の失敗する
Dial
呼び出し: 存在しないアドレスに対して、多数のDial
呼び出しを並行して実行します。これにより、多くのPollDesc
が一時的に割り当てられ、その後解放されることが期待されます。 runtime.MemStats
の監視:runtime.ReadMemStats
を呼び出して、テストの前後でSys
フィールド(OSから取得したメモリ総量)の差分を監視していました。テストのロジックは、Sys
の増加が一定の閾値を超えた場合にメモリリークを検出したと判断していました。
このアプローチが不安定であった主な理由は、runtime.MemStats
のSys
フィールドが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
アーキテクチャの場合も、テストが非常に時間がかかるためスキップします。
maxprocs
とloops
:runtime.GOMAXPROCS(0)
で現在の論理CPU数を取得し、それに基づいてテストのループ回数を設定していました。これは、並行処理の負荷を考慮したものです。count
定数:500
という値が設定されており、これはpollcache
のチャンクを十分に回転させるための数とコメントされています。allocPollDesc
はruntime/netpoll.goc
で定義されており、PollDesc
の割り当てに関連する内部関数です。runtime.MemStats
とsysdelta
関数:var old runtime.MemStats
で古いメモリ統計を保持します。runtime.ReadMemStats(&old)
で初期のメモリ統計を読み込みます。sysdelta
というクロージャが定義されており、これは現在のMemStats
を読み込み、old.Sys
とnew.Sys
の差分(delta
)を計算し、old
をnew
で更新する役割を持っていました。この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 issue #6553: https://github.com/golang/go/issues/6553 (このコミットが修正したイシュー)
- Go CL 98080043: https://golang.org/cl/98080043 (このコミットに対応するGerritの変更リスト)
参考にした情報源リンク
- 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のドキュメントや解説