[インデックス 10634] ファイルの概要
このコミットは、Go言語の標準ライブラリであるnet/http
パッケージ内のTransport
コンポーネントにおける、接続終了に関する警告の精度を向上させるための変更です。具体的には、アイドル状態のHTTPチャネルで予期せぬレスポンスが受信された際の警告ロジックが改善され、以前の脆弱で移植性の低い、そしてos.EINVAL
の削除によってスパム的になっていた挙動が修正されています。
コミット
commit 130e2943a320f012757518787b0c9dbf182ecb3a
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Tue Dec 6 16:38:02 2011 -0800
http: make Transport warning about connections closing more accurate
It was fragile and non-portable, and then became spammy with
the os.EINVAL removal. Now it just uses the length of the
Peek return value instead.
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/5453065
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/130e2943a320f012757518787b0c9dbf182ecb3a
元コミット内容
http: make Transport warning about connections closing more accurate
It was fragile and non-portable, and then became spammy with
the os.EINVAL removal. Now it just uses the length of the
Peek return value instead.
変更の背景
このコミットの主な背景には、net/http
パッケージのTransport
が、アイドル状態のHTTP接続がリモート側によって閉じられた際に生成する警告メッセージの信頼性と移植性の問題がありました。
以前の実装では、接続が閉じられたことを検出するためにremoteSideClosed
というヘルパー関数が使用されていました。この関数は、特定のOS固有のエラーコード(特にWindowsにおけるsyscall.Errno(10058)
、つまりWSAECONNRESET
に相当)をチェックすることで、リモート側からの接続リセットを判断していました。しかし、このアプローチは以下の問題点を抱えていました。
- 脆弱性 (Fragile): エラーコードに直接依存するため、OSやGoのバージョンアップによってエラーの挙動が変わると、予期せぬ動作を引き起こす可能性がありました。
- 非移植性 (Non-portable):
syscall.Errno(10058)
のような特定のエラーコードはWindowsに固有のものであり、他のOS(Linux, macOSなど)では異なるエラーコードが返されるか、そもそも同様の概念が存在しないため、コードの移植性が損なわれていました。 os.EINVAL
の削除によるスパム化: Go言語の進化の過程で、http.Transport
におけるos.EINVAL
(無効な引数)エラーの扱いが見直され、より汎用的なエラーハンドリングから除外される変更がありました。これにより、以前はos.EINVAL
が返されていた状況で、remoteSideClosed
が正しく機能しなくなり、結果として不要な警告メッセージが大量にログに出力される「スパム的」な挙動が発生するようになりました。これは、開発者にとってノイズとなり、真に重要な警告を見落とす原因となっていました。
これらの問題を解決するため、より堅牢で移植性の高い方法で接続終了を検出する必要がありました。新しいアプローチでは、Peek
メソッドの戻り値の長さを利用することで、実際にデータが読み取れるかどうかを判断し、それに基づいて警告を出すかどうかのロジックを決定しています。これにより、OS固有のエラーコードに依存することなく、より正確かつ汎用的に接続状態を判断できるようになりました。また、Windows固有のtransport_windows.go
ファイルが削除されたことも、この変更の背景にあります。
前提知識の解説
このコミットを理解するためには、以下のGo言語のnet/http
パッケージに関する知識が必要です。
net/http
パッケージ: Go言語の標準ライブラリで、HTTPクライアントとサーバーの実装を提供します。WebアプリケーションやAPIクライアントを構築する際に広く利用されます。http.Transport
:net/http
パッケージの重要なコンポーネントの一つで、HTTPリクエストの実際の送信とHTTPレスポンスの受信を担当します。TCP接続の確立、TLSハンドシェイク、HTTP/1.1のキープアライブ(持続的接続)管理、プロキシの利用などを抽象化します。クライアントが複数のリクエストを同じサーバーに送信する際に、既存のTCP接続を再利用することでパフォーマンスを向上させます。persistConn
:http.Transport
の内部で、単一のHTTP/1.1持続的接続(キープアライブ接続)を管理する構造体です。この構造体は、TCP接続のライフサイクル、リクエストとレスポンスの送受信、および接続のアイドル状態の管理を担当します。bufio.Reader.Peek(n int) ([]byte, error)
:bufio
パッケージのReader
型が提供するメソッドです。これは、入力ストリームから最大n
バイトを読み込まずに(つまり、内部バッファから)「覗き見」します。Peek
は、実際に読み込まれたバイトのスライスと、エラーを返します。エラーがio.EOF
の場合、ストリームの終端に達したことを意味します。このコミットでは、Peek(1)
を使用して、接続が閉じられたかどうかを判断するために1バイトを覗き見しています。os.EINVAL
:syscall
パッケージで定義されているエラーコードの一つで、"invalid argument"(無効な引数)を意味します。システムコールに渡された引数が無効である場合に返されることがあります。Go言語の進化の過程で、特定のコンテキストでのos.EINVAL
の扱いがより厳密になり、汎用的なエラーハンドリングから除外されることがありました。これが、このコミットで警告が「スパム的」になった原因の一つです。- Unsolicited Response (予期せぬレスポンス): HTTPの文脈では、クライアントがリクエストを送信していないにもかかわらず、サーバーからレスポンスが送られてくる状況を指します。これは通常、プロトコル違反や、接続がアイドル状態であるにもかかわらずサーバーがデータを送信してきた場合に発生します。
net/http
のTransport
は、このような予期せぬレスポンスを検出した場合に警告をログに出力します。 remoteSideClosed
関数: 以前のtransport.go
に存在した内部ヘルパー関数で、与えられたエラーがリモート側からの接続終了を示すものかどうかを判断するために使用されていました。この関数は、特にWindows環境での特定のエラーコード(syscall.Errno(10058)
)をチェックしていました。
技術的詳細
このコミットの技術的な核心は、net/http
パッケージのTransport
がアイドル状態のHTTP接続で予期せぬデータを受信した際の警告ロジックの変更にあります。
変更前は、persistConn
のreadLoop
内で、pc.br.Peek(1)
(バッファリングされたリーダーから1バイトを覗き見する)の呼び出し後にエラーが発生した場合、そのエラーがremoteSideClosed(err)
によってリモート側からの接続終了を示すものかどうかを判断していました。もしそうであれば、接続を閉じ、readLoop
を終了していました。
// 変更前のコード (抜粋)
if err != nil {
if remoteSideClosed(err) && !pc.expectingResponse() {
// Remote side closed on us. (We probably hit their
// max idle timeout)
pc.close()
return
}
}
if !pc.expectingResponse() {
log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v",
string(pb), err)
pc.close()
return
}
このremoteSideClosed
関数は、特にWindows環境でsyscall.Errno(10058)
(WSAECONNRESET
)のようなOS固有のエラーをチェックしていました。しかし、この方法は前述の通り、脆弱で移植性が低く、Goの内部変更(特にos.EINVAL
の扱い)によって、不要な警告が多発する問題を引き起こしていました。
このコミットでは、この脆弱なエラーチェックのロジックが完全に削除されました。代わりに、Peek(1)
の戻り値であるバイトスライスpb
の長さを直接チェックするようになりました。
// 変更後のコード (抜粋)
if !pc.expectingResponse() {
if len(pb) > 0 { // ここが変更点
log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v",
string(pb), err)
}
pc.close()
return
}
新しいロジックでは、pc.expectingResponse()
がfalse
(つまり、クライアントが現在レスポンスを期待していないアイドル状態)である場合に、len(pb) > 0
という条件が追加されました。これは、Peek(1)
が実際に1バイト以上のデータを読み込めた(つまり、バッファにデータが存在した)場合にのみ、予期せぬレスポンスの警告をログに出力するという意味です。
- なぜ
len(pb) > 0
が機能するのか?Peek(1)
は、ストリームの終端に達した場合や、読み取るべきデータがない場合には、空のスライス(len(pb) == 0
)を返します。- 以前はエラーオブジェクトを解析して接続終了を判断していましたが、新しいアプローチでは、
Peek
が実際にデータを返したかどうか(len(pb) > 0
)を直接確認することで、アイドル接続で予期せぬデータが到着したことをより堅牢に検出します。 - これにより、OS固有のエラーコードに依存することなく、Goの
io.Reader
インターフェースのセマンティクスに沿った、より汎用的で移植性の高い方法で接続状態を判断できるようになりました。
さらに、この変更に伴い、Windows固有のtransport_windows.go
ファイルが削除されました。このファイルは、remoteSideClosedFunc
という関数を定義しており、Windows特有のエラーハンドリングを提供していました。このコミットでremoteSideClosed
ロジックが不要になったため、このファイルも削除され、コードベースの簡素化とクロスプラットフォーム対応の強化が図られました。
Makefile
の変更は、このtransport_windows.go
ファイルの削除を反映したもので、GOFILES_windows
変数からtransport_windows.go
が削除され、それに関連する条件付きコンパイルのロジックも不要になったため削除されています。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の3つのファイルにわたります。
src/pkg/net/http/Makefile
src/pkg/net/http/transport.go
src/pkg/net/http/transport_windows.go
(削除)
diff --git a/src/pkg/net/http/Makefile b/src/pkg/net/http/Makefile
index 4bf33a629d..807bc32447 100644
--- a/src/pkg/net/http/Makefile
+++ b/src/pkg/net/http/Makefile
@@ -21,9 +21,4 @@ GOFILES=\
transfer.go\
transport.go\
-GOFILES_windows=\
- transport_windows.go\
-
-GOFILES+=$(GOFILES_$(GOOS))\
-
include ../../../Make.pkg
diff --git a/src/pkg/net/http/transport.go b/src/pkg/net/http/transport.go
index e622e41f0a..dc70be43f2 100644
--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -519,17 +519,11 @@ func (pc *persistConn) readLoop() {
for alive {
pb, err := pc.br.Peek(1)
- if err != nil {
- if remoteSideClosed(err) && !pc.expectingResponse() {
- // Remote side closed on us. (We probably hit their
- // max idle timeout)
- pc.close()
- return
- }
- }
if !pc.expectingResponse() {
- log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v",
- string(pb), err)
+ if len(pb) > 0 {
+ log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v",
+ string(pb), err)
+ }
pc.close()
return
}
diff --git a/src/pkg/net/http/transport_windows.go b/src/pkg/net/http/transport_windows.go
deleted file mode 100644
index c9ef2c2ab6..0000000000
--- a/src/pkg/net/http/transport_windows.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.\n-// Use of this source code is governed by a BSD-style\n-// license that can be found in the LICENSE file.\n-\n-package http\n-\n-import (\n-\t\"net\"\n-\t\"syscall\"\n-)\n-\n-func init() {\n-\tremoteSideClosedFunc = func(err error) (out bool) {\n-\t\top, ok := err.(*net.OpError)\n-\t\tif ok && op.Op == \"WSARecv\" && op.Net == \"tcp\" && op.Err == syscall.Errno(10058) {\n-\t\t\t// TODO(brainman,rsc): Fix whatever is generating this.\n-\t\t\treturn true\n-\t\t}\n-\t\treturn false\n-\t}\n-}
コアとなるコードの解説
src/pkg/net/http/transport.go
の変更
transport.go
のpersistConn
構造体のreadLoop
メソッドが変更されています。このメソッドは、持続的接続上でレスポンスを読み取るためのループです。
変更前は、pc.br.Peek(1)
(バッファから1バイトを覗き見する)の呼び出し後にエラーが発生した場合、そのエラーがremoteSideClosed(err)
関数によってリモート側からの接続終了を示すものかどうかをチェックしていました。もしそうであれば、接続を閉じ、readLoop
を終了していました。
// 変更前のコード
pb, err := pc.br.Peek(1)
if err != nil { // Peekでエラーが発生した場合
if remoteSideClosed(err) && !pc.expectingResponse() { // remoteSideClosedで接続終了と判断され、かつレスポンスを期待していない場合
pc.close() // 接続を閉じる
return // readLoopを終了
}
}
if !pc.expectingResponse() { // レスポンスを期待していないアイドル状態の場合
log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v",
string(pb), err) // 予期せぬレスポンスの警告をログに出力
pc.close()
return
}
変更後では、このif err != nil
ブロック全体が削除されました。これにより、remoteSideClosed
関数への依存がなくなりました。
// 変更後のコード
pb, err := pc.br.Peek(1) // Peekの呼び出しは変わらず
// 以前の if err != nil ブロックが削除された
if !pc.expectingResponse() { // レスポンスを期待していないアイドル状態の場合
if len(pb) > 0 { // ここが新しい条件: Peekが実際にデータを返した場合のみ
log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v",
string(pb), err) // 予期せぬレスポンスの警告をログに出力
}
pc.close()
return
}
新しいロジックでは、pc.expectingResponse()
がfalse
(アイドル状態)である場合に、Peek(1)
の戻り値であるpb
スライスのlen(pb) > 0
という条件が追加されました。これは、Peek
が実際に1バイト以上のデータを読み込めた場合にのみ、予期せぬレスポンスの警告をログに出力するという意味です。これにより、エラーオブジェクトの解析に依存せず、より直接的にデータ受信の有無を判断できるようになりました。
src/pkg/net/http/Makefile
の変更
Makefile
の変更は、transport_windows.go
ファイルの削除を反映したものです。
-GOFILES_windows=\
- transport_windows.go\
-
-GOFILES+=$(GOFILES_$(GOOS))\
上記の行が削除されました。これは、Windows固有のソースファイルtransport_windows.go
をGOFILES
変数に追加するための設定でした。このファイルが削除されたため、関連するMakefile
のエントリも不要となり、削除されました。これにより、ビルドプロセスが簡素化され、クロスプラットフォーム対応がよりクリーンになりました。
src/pkg/net/http/transport_windows.go
の削除
このファイルは完全に削除されました。
deleted file mode 100644
このファイルには、Windows固有のremoteSideClosedFunc
の実装が含まれていました。この関数は、net.OpError
をチェックし、特にWSARecv
操作でsyscall.Errno(10058)
(WSAECONNRESET
)エラーが発生した場合にtrue
を返すことで、リモート側からの接続リセットを検出していました。
transport.go
の変更により、remoteSideClosed
関数(およびそのWindows固有の実装)が不要になったため、このファイルは削除されました。これにより、Goのnet/http
パッケージは、OS固有のエラーハンドリングから解放され、より汎用的で移植性の高いコードベースになりました。
関連リンク
- Go CL 5453065: https://golang.org/cl/5453065
参考にした情報源リンク
- Go http Transport os.EINVAL removal: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFlwxEFSPeocErMcWoQ7UUbMuAsfSEzke612q3Quf-P5tXrOANqzHUd_8DAVwvvldKl8NED--FcKTNSrsH9DQ2WjMd98zScUC2vLptk2QE3GbRHO0RQ0g_ChkRrotVh4kLOyg==
- Go error handling philosophy: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHow_AlCEWa0zGAxFW2ZkY-Y_YBWc8GYKWBQF2fj0GWMTmRYKbC7QRHyuT-iL3_n0Ig4j5Oz3crEKMPkhO_GU6wslTab6_gX-vv4fpaZkqZhAu_HsIm6r2wG_mO1z8Clh__1WdF3uD7aAvDAV1cswGL9bqU9dV7SnM504xVUHoVGmNMWIktIvH3qvRoZVDT5ckOKxn6p7Eq1GN9XbqZoWj-Cxi__1DT0M_ZdMXi9xYqfuos6A=
- Go error handling best practices: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGVavX6LCHWfhAmMR6W1fJFFoozHVimo_M0pjSEfS9vyn8dDggeT80RlNtTvzt46nA80dKiIoI1eAzPoxq9o24TvZFLfziUVW_hw7Of1mmJMCp_zSG0wn7S1SnGUBfIntZR57Q=
os.EINVAL
in Go: https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQF_EhHfzh1bfn0S2zrR2j-0xFeEDFZOFt-9OXkcBO9_WUlPfTdnCf5AgaIzgs3TPSWQTmo8L8SJHds9XfdjAGOCcVvszy20AzrPlnKVcgkkAn2l2suzB-cDZKoNSUDe3ugxCSbBFJ-gzQ6u