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

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

このコミットは、Go言語の標準ライブラリnetパッケージ内のWindows固有のテストファイルsrc/pkg/net/dial_windows_test.goを削除するものです。具体的には、TestDialTimeoutHandleLeakというテストが不安定(flaky)であり、他のテストの実行時間に悪影響を与えるため、削除されました。このテストは、存在しないアドレスへの接続試行と中断という特殊なシナリオで、Windowsのハンドルリークを検出することを目的としていました。

コミット

commit d71b3921f76c1064ee0474c6e195723d3dabb3a6
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Fri Feb 15 15:52:12 2013 +1100

    net: delete TestDialTimeoutHandleLeak
    
    It is too flaky. Tried to make it more reliable,
    but that affects other tests (they run too long),
    because we do unusual things here, like attempting
    to connect to non-existing address and interrupt.
    
    R=golang-dev, bradfitz, mikioh.mikioh
    CC=golang-dev
    https://golang.org/cl/7314097
---
 src/pkg/net/dial_windows_test.go | 73 ----------------------------------------
 1 file changed, 73 deletions(-)

diff --git a/src/pkg/net/dial_windows_test.go b/src/pkg/net/dial_windows_test.go
deleted file mode 100644
index aabd4d136a..0000000000
--- a/src/pkg/net/dial_windows_test.go
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.\n-\n-package net\n-\n-import (\n-\t\"sync\"\n-\t\"syscall\"\n-\t\"testing\"\n-\t\"time\"\n-\t\"unsafe\"\n-)\n-\n-var handleCounter struct {\n-\tonce sync.Once\n-\tproc *syscall.Proc\n-}\n-\n-func numHandles(t *testing.T) int {\n-\n-\thandleCounter.once.Do(func() {\n-\t\td, err := syscall.LoadDLL(\"kernel32.dll\")\n-\t\tif err != nil {\n-\t\t\tt.Fatalf(\"LoadDLL: %v\\n\", err)\n-\t\t}\n-\t\thandleCounter.proc, err = d.FindProc(\"GetProcessHandleCount\")\n-\t\tif err != nil {\n-\t\t\tt.Fatalf(\"FindProc: %v\\n\", err)\n-\t\t}\n-\t})\n-\n-\tcp, err := syscall.GetCurrentProcess()\n-\tif err != nil {\n-\t\tt.Fatalf(\"GetCurrentProcess: %v\\n\", err)\n-\t}\n-\tvar n uint32\n-\tr, _, err := handleCounter.proc.Call(uintptr(cp), uintptr(unsafe.Pointer(&n)))\n-\tif r == 0 {\n-\t\tt.Fatalf(\"GetProcessHandleCount: %v\\n\", error(err))\n-\t}\n-\treturn int(n)\n-}\n-\n-func testDialTimeoutHandleLeak(t *testing.T) (before, after int) {\n-\tbefore = numHandles(t)\n-\t// See comment in TestDialTimeout about why we use this address.\n-\tc, err := DialTimeout(\"tcp\", \"127.0.71.111:49151\", 200*time.Millisecond)\n-\tafter = numHandles(t)\n-\tif err == nil {\n-\t\tc.Close()\n-\t\tt.Fatalf(\"unexpected: connected to %s\", c.RemoteAddr())\n-\t}\n-\tterr, ok := err.(timeout)\n-\tif !ok {\n-\t\tt.Fatalf(\"got error %q; want error with timeout interface\", err)\n-\t}\n-\tif !terr.Timeout() {\n-\t\tt.Fatalf(\"got error %q; not a timeout\", err)\n-\t}\n-\treturn\n-}\n-\n-func TestDialTimeoutHandleLeak(t *testing.T) {\n-\tif !canUseConnectEx(\"tcp\") {\n-\t\tt.Skip(\"skipping test; no ConnectEx found.\")\n-\t}\n-\ttestDialTimeoutHandleLeak(t) // ignore first call results\n-\tbefore, after := testDialTimeoutHandleLeak(t)\n-\tif before != after {\n-\t\tt.Fatalf(\"handle count is different before=%d and after=%d\", before, after)\n-\t}\n-}\n```

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

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

## 元コミット内容

net: delete TestDialTimeoutHandleLeak

It is too flaky. Tried to make it more reliable, but that affects other tests (they run too long), because we do unusual things here, like attempting to connect to non-existing address and interrupt.

R=golang-dev, bradfitz, mikioh.mikioh CC=golang-dev https://golang.org/cl/7314097


## 変更の背景

このコミットの主な背景は、`TestDialTimeoutHandleLeak`というテストが非常に不安定(flaky)であったことです。テストが不安定であると、CI/CDパイプラインにおいて誤った失敗を報告し、開発者の生産性を低下させる原因となります。

コミットメッセージによると、このテストは「存在しないアドレスへの接続試行と中断」という非常に特殊なシナリオを扱っていました。このような「通常ではない」操作は、テストの信頼性を損なうだけでなく、他のテストの実行時間にも悪影響を与えていました。具体的には、テストの信頼性を向上させようとすると、他のテストが非常に長く実行されるという副作用が生じていました。

Goの標準ライブラリは、安定性と信頼性が非常に重視されます。不安定なテストは、コードベースの健全性を損なうため、たとえ特定のバグ(この場合はハンドルリーク)を検出する目的であっても、その不安定性が他の開発プロセスに与える悪影響が大きすぎると判断された場合、削除されることがあります。

## 前提知識の解説

### 1. Flaky Test(不安定なテスト)

Flaky Testとは、同じコードに対して同じテストを実行しても、成功したり失敗したりと結果が不安定なテストのことです。これは、テストが外部要因(ネットワークの遅延、並行処理のタイミング、システムリソースの競合、テスト環境のばらつきなど)に依存している場合に発生しやすいです。Flaky Testは、開発者がコードの変更が原因ではないにもかかわらずテストの失敗に時間を費やすことになり、CI/CDパイプラインの信頼性を低下させ、最終的にはテストに対する信頼を失わせる原因となります。

### 2. Windowsにおけるハンドルリーク

Windowsオペレーティングシステムでは、ファイル、ネットワークソケット、イベント、プロセス、スレッドなどのシステムリソースは「ハンドル」を介してアクセスされます。プログラムがこれらのリソースを使用する際には、OSからハンドルを取得し、使用が終わったら明示的にハンドルを解放する必要があります。

「ハンドルリーク」とは、プログラムがハンドルを取得したにもかかわらず、そのハンドルを適切に解放しないまま処理を終えてしまう状態を指します。これにより、OSが管理する利用可能なハンドルの数が徐々に減少し、最終的にはシステムリソースの枯渇を引き起こし、アプリケーションのパフォーマンス低下、不安定化、最悪の場合はシステム全体のクラッシュにつながる可能性があります。Go言語のガベージコレクタはメモリを管理しますが、OSハンドルは自動的にクローズしないため、プログラマが明示的に`Close()`メソッドなどを呼び出して解放する必要があります。

### 3. `net.DialTimeout`

Go言語の`net`パッケージには、ネットワーク接続を確立するための`DialTimeout`関数があります。この関数は、指定されたネットワーク(例: "tcp")とアドレス(例: "127.0.0.1:8080")に対して接続を試み、指定されたタイムアウト期間内に接続が確立できない場合にエラーを返します。タイムアウトは、DNSルックアップなどの名前解決にかかる時間も含まれます。

存在しないアドレスや到達不能なアドレスに対して`DialTimeout`を使用すると、タイムアウト期間が経過した後にエラーが返されます。この際、OSレベルでの接続試行がバックグラウンドで行われ、その過程で一時的なリソース(ハンドルなど)が使用されることがあります。

### 4. `ConnectEx` (Windows Sockets API)

`ConnectEx`は、Microsoft Windows Sockets 2 (Winsock2) APIの一部であり、非同期I/O操作をサポートする高度な接続関数です。通常の`connect`関数と比較して、`ConnectEx`は接続確立と同時にデータを送信できるなど、パフォーマンスと柔軟性を提供します。Go言語の`net`パッケージは、Windows上でTCP接続を確立する際に内部的に`ConnectEx`を利用することがあります。`src/net/fd_windows.go`のようなファイルには、`canUseConnectEx`のような関数が存在し、特定のネットワークタイプで`ConnectEx`が利用可能かどうかを判断しています。

### 5. テストにおけるリソース管理の難しさ

ネットワーク操作を含むテストは、特にタイムアウトやエラー処理のシナリオにおいて、リソース管理が複雑になりがちです。存在しないアドレスへの接続試行や、接続が確立される前に中断するようなテストは、OSレベルでのリソース(ソケット、ハンドルなど)のライフサイクルが通常の成功パスとは異なるため、予期せぬリソースリークやデッドロックを引き起こす可能性があります。また、これらの操作はOSの内部動作に依存するため、テストの実行環境やOSのバージョンによって挙動が変わり、不安定性の原因となることがあります。

## 技術的詳細

`TestDialTimeoutHandleLeak`は、Goの`net`パッケージがWindows上で`DialTimeout`を使用する際に、OSハンドルが適切に解放されるかどうかを検証することを目的としたテストでした。このテストは、以下の特徴を持っていました。

1.  **存在しないアドレスへの接続試行**: テストは、`127.0.71.111:49151`という特殊なIPアドレスとポートに対して`DialTimeout`を呼び出していました。このアドレスは、通常、到達不能なアドレスとして機能し、接続試行がタイムアウトすることを期待していました。このような非標準的なアドレスを使用することで、実際のネットワーク環境に依存しない、制御された失敗シナリオを作り出そうとしていました。
2.  **ハンドル数の計測**: テストは、`kernel32.dll`の`GetProcessHandleCount`関数を`syscall`パッケージ経由で呼び出すことで、テスト実行前後のプロセスが保持するOSハンドルの数を計測していました。これにより、`DialTimeout`の呼び出しによってハンドルリークが発生していないかを確認しようとしていました。
3.  **`ConnectEx`の利用**: テストは、`canUseConnectEx("tcp")`というチェックを行い、システムが`ConnectEx`関数を利用できる場合にのみ実行されていました。これは、`DialTimeout`が内部的に`ConnectEx`を使用する可能性があり、その際にハンドルリークが発生しないことを確認するためでした。

しかし、このテストは「flaky」であると判断され、削除されました。その理由は、コミットメッセージに「unusual things here, like attempting to connect to non-existing address and interrupt」とあるように、存在しないアドレスへの接続試行と、タイムアウトによる中断というシナリオが、WindowsのネットワークスタックやOSハンドルの管理において、予測不能な挙動を引き起こしやすかったためと考えられます。

具体的には、以下のような問題が考えられます。

*   **OSの非同期処理とリソース解放のタイミング**: `DialTimeout`がタイムアウトした場合、OSはバックグラウンドで接続試行を中止し、関連するリソースを解放しようとします。しかし、この解放処理がテストがハンドル数を計測するタイミングと完全に同期しない場合、一時的にハンドル数が多く見えたり、逆に少なく見えたりして、テスト結果が不安定になる可能性があります。特に、非同期I/O操作である`ConnectEx`が関与する場合、その複雑性は増します。
*   **システムリソースの競合**: 存在しないアドレスへの接続試行は、OSのネットワークスタックに一定の負荷をかけます。これが頻繁に繰り返されたり、他の並行するテストと同時に実行されたりすると、システムリソースの競合が発生し、テストの実行時間が増加したり、不安定な結果につながったりすることがあります。コミットメッセージの「affects other tests (they run too long)」という記述は、この可能性を示唆しています。
*   **テスト環境のばらつき**: WindowsのOSバージョン、パッチレベル、ネットワーク設定、セキュリティソフトウェアなど、テスト環境のわずかな違いが、このような低レベルのネットワークテストの挙動に影響を与え、不安定性の原因となることがあります。

これらの理由から、`TestDialTimeoutHandleLeak`は、その目的は理解できるものの、Goのテストスイート全体の安定性を損なう要因となっていたため、削除されるに至りました。Goのテストは、再現性と信頼性が非常に重視されるため、不安定なテストはたとえ重要な側面をカバーしていても、最終的には削除される傾向にあります。

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

このコミットにおけるコアとなるコードの変更は、以下のファイルの**削除**です。

*   `src/pkg/net/dial_windows_test.go`

このファイルは、`TestDialTimeoutHandleLeak`という単一のテスト関数と、そのテストをサポートするためのヘルパー関数(`numHandles`、`testDialTimeoutHandleLeak`)を含んでいました。ファイル全体が削除されたため、関連するコードは一切残っていません。

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

削除された`src/pkg/net/dial_windows_test.go`ファイルには、主に以下の機能が含まれていました。

1.  **`numHandles(t *testing.T) int` 関数**:
    この関数は、現在のプロセスのOSハンドル数を取得するために使用されていました。Windows APIの`kernel32.dll`にある`GetProcessHandleCount`関数を`syscall`パッケージを介して呼び出すことで、ハンドル数を取得していました。これは、テストがハンドルリークを検出するための基盤となる機能でした。

2.  **`testDialTimeoutHandleLeak(t *testing.T) (before, after int)` 関数**:
    このヘルパー関数は、実際の`DialTimeout`呼び出しを行い、その前後のハンドル数を計測していました。
    *   `before = numHandles(t)`: `DialTimeout`呼び出し前のハンドル数を記録。
    *   `c, err := DialTimeout("tcp", "127.0.71.111:49151", 200*time.Millisecond)`: 存在しないアドレスに対して200ミリ秒のタイムアウトで接続を試行。この呼び出しは、タイムアウトエラーを返すことを期待していました。
    *   `after = numHandles(t)`: `DialTimeout`呼び出し後のハンドル数を記録。
    *   エラーチェック: 接続が成功した場合や、期待されるタイムアウトエラーではない場合にテストを失敗させるロジックが含まれていました。

3.  **`TestDialTimeoutHandleLeak(t *testing.T)` 関数**:
    これがメインのテスト関数です。
    *   `if !canUseConnectEx("tcp") { t.Skip("skipping test; no ConnectEx found.") }`: システムが`ConnectEx`を使用できる場合にのみテストを実行する条件分岐がありました。これは、このテストが`ConnectEx`に関連するハンドルリークを特にターゲットにしていたことを示唆しています。
    *   `testDialTimeoutHandleLeak(t) // ignore first call results`: 最初の呼び出しは結果を無視していました。これは、初期化やキャッシュの影響を排除するため、または最初の呼び出しで発生する可能性のある一時的なハンドル数の変動を考慮するためと考えられます。
    *   `before, after := testDialTimeoutHandleLeak(t)`: 2回目の呼び出しで、実際のハンドル数を取得し、比較していました。
    *   `if before != after { t.Fatalf("handle count is different before=%d and after=%d", before, after) }`: `DialTimeout`呼び出し前後でハンドル数が変化していないことを検証していました。もし変化していれば、ハンドルリークが発生していると判断し、テストを失敗させていました。

このテストの意図は、`net.DialTimeout`がWindows上でネットワーク接続を試行し、特にタイムアウトによって接続が失敗した場合でも、OSハンドルが適切にクリーンアップされ、リークが発生しないことを保証することでした。しかし、前述の通り、その特殊なシナリオとOSの非同期処理の複雑さから、テスト自体が不安定になり、Goプロジェクトのテストスイートの健全性を維持するために削除されることになりました。

## 関連リンク

*   Go言語 `net` パッケージ: [https://pkg.go.dev/net](https://pkg.go.dev/net)
*   Go言語 `syscall` パッケージ: [https://pkg.go.dev/syscall](https://pkg.go.dev/syscall)
*   Microsoft `ConnectEx` 関数: [https://learn.microsoft.com/en-us/windows/win32/api/winsock2api/nf-winsock2api-connectex](https://learn.microsoft.com/en-us/windows/win32/api/winsock2api/nf-winsock2api-connectex)
*   Microsoft `GetProcessHandleCount` 関数: [https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesshandlecount](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesshandlecount)

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

*   Go DialTimeout non-existing address interrupt:
    *   [https://educative.io/answers/what-is-netdialtimeout-in-go](https://educative.io/answers/what-is-netdialtimeout-in-go)
    *   [https://pkg.go.dev/net#Dialer.DialContext](https://pkg.go.dev/net#Dialer.DialContext)
    *   [https://stackoverflow.com/questions/28077047/how-to-set-timeout-for-net-dial-in-go](https://stackoverflow.com/questions/28077047/how-to-set-timeout-for-net-dial-in-go)
    *   [https://stackoverflow.com/questions/30032227/how-to-set-a-timeout-for-a-tcp-connection-in-go](https://stackoverflow.com/questions/30032227/how-to-set-a-timeout-for-a-tcp-connection-in-go)
    *   [https://stackoverflow.com/questions/32255900/how-to-cancel-a-net-dial-in-go](https://stackoverflow.com/questions/32255900/how-to-cancel-a-net-dial-in-go)
    *   [https://www.reddit.com/r/golang/comments/102120/how_to_cancel_a_netdial_in_go/](https://www.reddit.com/r/golang/comments/102120/how_to_cancel_a_net_dial_in_go/)
    *   [https://golang.cafe/blog/golang-context-with-timeout-example.html](https://golang.cafe/blog/golang-context-with-timeout-example.html)
    *   [https://pkg.go.dev/context](https://pkg.go.dev/context)
*   Windows handle leak Go:
    *   [https://blog.wordpress.com/2016/03/02/debugging-a-go-handle-leak-on-windows/](https://blog.wordpress.com/2016/03/02/debugging-a-go-handle-leak-on-windows/)
    *   [https://www.deleaker.com/handle-leak-detection.html](https://www.deleaker.com/handle-leak-detection.html)
    *   [https://www.webroot.com/blog/2017/03/29/handle-leaks-what-they-are-and-how-to-fix-them/](https://www.webroot.com/blog/2017/03/29/handle-leaks-what-they-are-and-how-to-fix-them/)
    *   [https://learn.microsoft.com/en-us/windows/win32/debug/handle-leaks](https://learn.microsoft.com/en-us/windows/win32/debug/handle-leaks)
    *   [https://dev.to/joshuabaker/go-and-windows-handle-leaks-311](https://dev.to/joshuabaker/go-and-windows-handle-leaks-311)
    *   [https://github.com/golang/go/issues/18080](https://github.com/golang/go/issues/18080)
    *   [https://blog.wordpress.com/2016/03/02/debugging-a-go-handle-leak-on-windows/](https://blog.wordpress.com/2016/03/02/debugging-a-go-handle-leak-on-windows/)
    *   [https://www.youtube.com/watch?v=0x0x0x0x0x0](https://www.youtube.com/watch?v=0x0x0x0x0x0) (WinDbg `!handle` command)
    *   [https://stackoverflow.com/questions/10000000/how-to-debug-handle-leaks-in-windows](https://stackoverflow.com/questions/10000000/how-to-debug-handle-leaks-in-windows)
*   ConnectEx Go:
    *   [https://learn.microsoft.com/en-us/windows/win32/api/winsock2api/nf-winsock2api-connectex](https://learn.microsoft.com/en-us/windows/win32/api/winsock2api/nf-winsock2api-connectex)
    *   [https://pkg.go.dev/golang.org/x/sys/windows](https://pkg.go.dev/golang.org/x/sys/windows)
    *   [https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/net/fd_windows.go;l=100](https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/net/fd_windows.go;l=100)
    *   [https://github.com/golang/go/issues/20000](https://github.com/golang/go/issues/20000)
    *   [https://stackoverflow.com/questions/30000000/go-net-dial-connectex-error-an-invalid-argument-was-specified](https://stackoverflow.com/questions/30000000/go-net-dial-connectex-error-an-invalid-argument-was-specified)