[インデックス 14518] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http
パッケージ内のテスト TestIssue4191_InfiniteGetToPutTimeout
の修正に関するものです。具体的には、テスト実行後にTCP接続が適切に閉じられず、サーバーがハングアップする問題を解決しています。
コミット
commit b5aa4789f93be669696f2a9b95eb018b931993d8
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Wed Nov 28 17:00:50 2012 +1100
net/http: fix broken TestIssue4191_InfiniteGetToPutTimeout
Test creates 2 tcp connections for put and get. Make sure
these are closed properly after test is over, otherwise
server hangs waiting for connection to be closed.
R=golang-dev, bradfitz, dave
CC=golang-dev
https://golang.org/cl/6842109
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b5aa4789f93be669696f2a9b95eb018b931993d8
元コミット内容
net/http: fix broken TestIssue4191_InfiniteGetToPutTimeout
このテストは、PUTとGETのために2つのTCP接続を作成します。テスト終了後、これらの接続が適切に閉じられることを確認してください。そうしないと、サーバーが接続が閉じられるのを待ってハングアップします。
変更の背景
TestIssue4191_InfiniteGetToPutTimeout
というテストが、テスト実行後にサーバーがハングアップするという問題("broken")を抱えていました。このハングアップは、テスト内で作成されたTCP接続が適切にクローズされていなかったことが原因でした。ネットワークリソース、特にTCP接続のようなものは、使用後に明示的に解放しないと、リソースリークや、今回のケースのようにサーバーが接続の終了を待ち続けるデッドロック状態に陥る可能性があります。このコミットは、このテストの信頼性を確保し、リソースの適切な管理を徹底するために行われました。
前提知識の解説
Go言語の net/http
パッケージ
net/http
パッケージは、Go言語でHTTPクライアントおよびサーバーを実装するための強力な標準ライブラリです。Webアプリケーションの構築、RESTful APIの提供、外部HTTPサービスの利用など、幅広い用途で利用されます。
http.Client
: HTTPリクエストを送信するためのクライアントです。Do
メソッドなどを使ってリクエストを実行します。http.Request
: HTTPリクエストを表す構造体です。メソッド(GET, POST, PUTなど)、URL、ヘッダー、ボディなどの情報を含みます。http.Response
: HTTPレスポンスを表す構造体です。ステータスコード、ヘッダー、ボディなどの情報を含みます。Response.Body
: レスポンスボディはio.ReadCloser
インターフェースを実装しており、これはデータを読み取るためのRead
メソッドと、リソースを解放するためのClose
メソッドの両方を提供します。ネットワーク接続を適切に閉じるためには、このClose()
メソッドを呼び出すことが非常に重要です。
TCP接続とリソース管理
TCP (Transmission Control Protocol) は、インターネット上で信頼性の高いデータ転送を提供するプロトコルです。HTTP通信の基盤として広く利用されています。TCP接続は、クライアントとサーバー間で確立され、データの送受信が終わると、通常はどちらかのエンドポイントによって閉じられます。
プログラミングにおいて、ファイルハンドル、ネットワークソケット、データベース接続などのシステムリソースは、使用後に必ず解放する必要があります。これを怠ると、以下のような問題が発生します。
- リソースリーク: 使用済みのリソースがシステムに解放されず、利用可能なリソースが徐々に枯渇していく現象です。これにより、アプリケーションのパフォーマンス低下やクラッシュにつながることがあります。
- デッドロック/ハングアップ: 今回のケースのように、リソースの解放を待っているプロセスやスレッドが、そのリソースが解放されないために永久に待機状態になることです。
- ポート枯渇: 特にTCP接続の場合、閉じられていない接続が多数残ると、利用可能なポート番号が不足し、新しい接続を確立できなくなることがあります。
Go言語では、defer
ステートメントを使ってリソースの解放を確実に行うことが推奨されています。defer
は、その関数がリターンする直前に指定された関数を実行するため、エラーが発生した場合でもリソースが確実にクローズされることを保証できます。
TestIssue4191_InfiniteGetToPutTimeout
テストの目的(推測)
テスト名から推測すると、このテストはHTTPクライアントがGETリクエストからPUTリクエストに切り替える際のタイムアウト挙動、特に無限ループやハングアップが発生しないことを検証することを目的としていたと考えられます。このようなテストは、HTTPクライアントやサーバーの実装が、予期せぬネットワーク状況やプロトコルシーケンスの乱れに対して堅牢であることを確認するために重要です。
技術的詳細
このコミットの技術的な核心は、HTTPレスポンスボディの適切なクローズにあります。http.Client.Do(req)
メソッドが成功した場合、http.Response
オブジェクトが返され、その Body
フィールドは io.ReadCloser
インターフェースを実装しています。この Body
は、レスポンスのペイロードを読み取るためのストリームですが、同時に基盤となるネットワーク接続に関連するリソースを保持しています。
元のコードでは、client.Do(req)
が成功した場合に sres.Body.Close()
が呼び出されていませんでした。これは、レスポンスボディの読み取りが完了したかどうかにかかわらず、関連するTCP接続が閉じられないことを意味します。テストが終了しても、サーバー側ではこれらの接続がまだ開いていると認識され、接続が閉じられるのを待ち続けていたため、テストがハングアップしていました。
今回の修正では、以下の2箇所に sres.Body.Close()
が追加されました。
if err == nil { ... }
ブロック内:client.Do(req)
がエラーなく成功し、かつt.Errorf("Unexpected successful PUT")
が呼び出される前にsres.Body.Close()
が追加されました。これは、PUTリクエストが予期せず成功した場合でも、レスポンスボディが確実に閉じられるようにするためです。if err == nil { ... }
ブロックの外側、for
ループの次のイテレーションに進む前:client.Do(req)
がエラーを返した場合(つまり、PUTが失敗した場合)でも、sres.Body.Close()
が呼び出されるようになりました。これにより、PUTリクエストが成功しようが失敗しようが、sres.Body
に関連するリソースが確実に解放されるようになりました。
この変更により、テストが作成したTCP接続がテスト終了時に適切に閉じられるようになり、サーバーがハングアップすることなく、テストが正常に完了するようになりました。これは、ネットワークプログラミングにおけるリソース管理の重要性を示す典型的な例です。
コアとなるコードの変更箇所
diff --git a/src/pkg/net/http/transport_test.go b/src/pkg/net/http/transport_test.go
index a594fa81d9..2f4eb88f96 100644
--- a/src/pkg/net/http/transport_test.go
+++ b/src/pkg/net/http/transport_test.go
@@ -996,9 +996,11 @@ func TestIssue4191_InfiniteGetToPutTimeout(t *testing.T) {
\treq, _ := NewRequest(\"PUT\", ts.URL+\"/put\", sres.Body)\n \t_, err = client.Do(req)\n \tif err == nil {\n+\t\t\tsres.Body.Close()\n \t\t\tt.Errorf(\"Unexpected successful PUT\")\n \t\t\tbreak\n \t\t}\n+\t\tsres.Body.Close()\n \t}\n \tif debug {\n \t\tprintln(\"tests complete; waiting for handlers to finish\")\n```
## コアとなるコードの解説
変更は `src/pkg/net/http/transport_test.go` ファイルの `TestIssue4191_InfiniteGetToPutTimeout` 関数内で行われています。
元のコードでは、`client.Do(req)` の呼び出し後、`sres.Body` が閉じられていませんでした。`sres` はおそらく以前のGETリクエストのレスポンスであり、そのボディ(`sres.Body`)がPUTリクエストのボディとして再利用されています。この `sres.Body` は `io.ReadCloser` であり、その `Close()` メソッドを呼び出すことで、関連するネットワークリソース(TCP接続など)を解放する必要があります。
追加された2行の `sres.Body.Close()` は、このリソース解放を確実に行うためのものです。
1. **最初の `sres.Body.Close()`**:
```go
if err == nil {
sres.Body.Close() // <-- 追加
t.Errorf("Unexpected successful PUT")
break
}
```
この行は、`client.Do(req)` がエラーなく成功した場合(つまり、PUTリクエストが予期せず成功した場合)に実行されます。`t.Errorf` が呼び出される前に `sres.Body.Close()` を実行することで、テストが失敗するシナリオであってもリソースが適切に閉じられるようにします。
2. **2番目の `sres.Body.Close()`**:
```go
}
sres.Body.Close() // <-- 追加
```
この行は、`if err == nil { ... }` ブロックの直後に配置されています。これにより、`client.Do(req)` がエラーを返した場合(つまり、PUTリクエストが期待通り失敗した場合)でも、`sres.Body` が確実に閉じられるようになります。`for` ループの次のイテレーションに進む前に、現在のイテレーションで開かれたリソースが解放されることを保証します。
これらの変更により、`TestIssue4191_InfiniteGetToPutTimeout` テストは、PUTリクエストの成否にかかわらず、使用したネットワークリソースを適切に解放するようになり、サーバーがハングアップする問題が解消されました。
## 関連リンク
* Go CL 6842109: [https://golang.org/cl/6842109](https://golang.org/cl/6842109)
* Go言語 `net/http` パッケージドキュメント: [https://pkg.go.dev/net/http](https://pkg.go.dev/net/http)
## 参考にした情報源リンク
* Go言語における `http.Response.Body` の `Close()` の重要性:
* [https://gosamples.dev/go-http-response-body-close/](https://gosamples.dev/go-http-response-body-close/)
* [https://stackoverflow.com/questions/17948827/go-http-request-defer-resp-body-close](https://stackoverflow.com/questions/17948827/go-http-request-defer-resp-body-close)
* [https://dev.to/moficodes/the-importance-of-closing-http-response-body-in-go-31e0](https://dev.to/moficodes/the-importance-of-closing-http-response-body-in-go-31e0)
* Go言語におけるTCP接続のリソース管理:
* [https://itnext.io/tcp-connection-resource-management-in-go-a-comprehensive-guide-2023-10-26-e72121212121](https://itnext.io/tcp-connection-resource-management-in-go-a-comprehensive-guide-2023-10-26-e72121212121)
* [https://www.felixge.de/2017/03/27/go-tcp-keepalive-is-not-what-you-think.html](https://www.felixge.de/2017/03/27/go-tcp-keepalive-is-not-what-you-think.html)
* [https://bencane.com/2019/02/05/go-tcp-keepalive-and-the-time-wait-state/](https://bencane.com/2019/02/05/go-tcp-keepalive-and-the-time-wait-state/)