[インデックス 19117] ファイルの概要
このコミットは、Go言語の net/http
パッケージにおける、HTTPチャネルのシャットダウン時に発生する不要な警告メッセージを抑制するための変更です。特に、クライアントがタイムアウトによってリクエストをキャンセルし、接続が強制的に閉じられた際に、サーバーからの応答がまだ処理中であった場合に表示される「Unsolicited response received on idle HTTP channel」という警告を対象としています。
コミット
net/http: quiet useless warning during shutdown
What was happening on Issue 7010 was handler intentionally took 30
milliseconds and the proxy's client timeout was 35 milliseconds. Then it
slammed the proxy with a bunch of requests.
Sometimes the server would be too slow to respond in its 5 millisecond
window and the client code would cancel the request, force-closing the
persistConn. If this came at the right time, the server's reply was
already in flight, and one of the goroutines would report:
Unsolicited response received on idle HTTP channel starting with "H"; err=<nil>
... rightfully scaring the user.
But the error was already handled and returned to the user, and this
connection knows it's been shut down. So look at the closed flag after
acquiring the same mutex guarding another field we were checking, and
don't complain if it's a known shutdown.
Also move closed down below the mutex which guards it.
Fixes #7010
LGTM=dsymonds
R=golang-codereviews, dsymonds
CC=adg, golang-codereviews, rsc
https://golang.org/cl/86740044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/427a444f67544416a7e96b705a10b8be38269767
元コミット内容
このコミットは、Goの net/http
パッケージにおいて、HTTP接続のシャットダウン時に発生する、ユーザーを不必要に不安にさせる警告メッセージを抑制することを目的としています。具体的には、クライアントがタイムアウトによりリクエストをキャンセルし、その結果として persistConn
(永続的な接続) が強制的に閉じられた際、サーバーからの応答がまだ処理中であった場合に「Unsolicited response received on idle HTTP channel」という警告が表示される問題に対処しています。この警告は、エラーが既に適切に処理され、接続がシャットダウン中であることが既知であるにもかかわらず表示されるため、ユーザーにとって混乱を招くものでした。
変更の背景
この変更は、Go Issue 7010で報告された問題に対応するものです。問題のシナリオは以下の通りです。
- HTTPハンドラが意図的に30ミリ秒の処理時間を要する。
- プロキシのクライアントタイムアウトが35ミリ秒に設定されている。
- クライアントがプロキシに対して大量のリクエストを送信する。
- サーバーが5ミリ秒のタイムアウトウィンドウ内に応答できない場合がある。
- クライアントコードがリクエストをキャンセルし、
persistConn
を強制的に閉じる。 - このタイミングでサーバーからの応答が既に送信中であった場合、アイドル状態のHTTPチャネルで「Unsolicited response received on idle HTTP channel starting with "H"; err=
」という警告がログに出力される。
この警告は、実際にはエラーが既にクライアント側で処理されており、接続がシャットダウン中であるという既知の状態であるにもかかわらず表示されるため、ユーザーを不必要に心配させていました。開発者は、この「無用な警告」を抑制し、よりクリーンなログ出力を実現するためにこの変更を行いました。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびHTTPプロトコルに関する知識が必要です。
- Go言語の並行処理 (GoroutinesとChannels): Goは軽量なスレッドであるゴルーチンと、それらの間の通信を可能にするチャネルを介して並行処理をサポートします。
net/http
パッケージは、これらの並行処理プリミティブを広範に利用して、複数の同時リクエストを効率的に処理します。 net/http
パッケージ: Goの標準ライブラリの一部であり、HTTPクライアントとサーバーの実装を提供します。このコミットは、特にHTTPクライアント側の永続的な接続(keep-alive接続)を管理するtransport.go
ファイル内のpersistConn
構造体に関連しています。- HTTP永続的接続 (Keep-Alive): HTTP/1.1では、複数のリクエスト/レスポンスサイクルで同じTCP接続を再利用する「永続的接続」がデフォルトで有効になっています。これにより、接続の確立と切断のオーバーヘッドが削減され、パフォーマンスが向上します。
persistConn
は、この永続的なHTTP接続を抽象化したものです。 bufio.Reader
とbufio.Writer
: これらは、I/O操作をバッファリングするためのGoの標準ライブラリです。ネットワーク接続からの読み取り(br
)と書き込み(bw
)の効率を高めるためにpersistConn
で使用されます。sync.Mutex
: Goのsync
パッケージが提供する相互排他ロックです。共有リソース(この場合はpersistConn
のフィールド)への同時アクセスを防ぎ、データ競合を回避するために使用されます。readLoop
:persistConn
内のゴルーチンで実行されるループで、ネットワーク接続から応答を読み取ります。このループ内で、受信したデータが予期された応答であるかどうかを判断し、そうでない場合は警告をログに記録するロジックが含まれています。closed
フラグ:persistConn
が閉じられたかどうかを示すブール値のフラグです。このコミットの核心は、このフラグの状態を適切にチェックすることによって、不要な警告を抑制することにあります。numExpectedResponses
:persistConn
が現在処理中または予期している応答の数を追跡するカウンタです。これが0の場合、接続はアイドル状態と見なされます。
技術的詳細
このコミットの技術的な核心は、persistConn
の readLoop
メソッドにおける警告ロジックの変更と、closed
フラグの配置の調整にあります。
以前の実装では、readLoop
がアイドル状態のHTTPチャネルで予期しない応答(Unsolicited response
)を受信した場合、pc.numExpectedResponses == 0
であれば、即座に pc.closeLocked()
を呼び出し、その後警告をログに出力していました。この際、closed
フラグのチェックが警告出力の前に行われていませんでした。
変更後、警告を出力する前に pc.closed
フラグをチェックする条件が追加されました。具体的には、pc.numExpectedResponses == 0
であり、かつ !pc.closed
(接続がまだ閉じられていない) の場合にのみ、pc.closeLocked()
を呼び出し、警告をログに出力するように変更されました。これにより、接続が既にシャットダウン中であることが既知である場合には、警告が抑制されるようになりました。
また、closed
フィールドの定義位置が persistConn
構造体内で変更されました。以前は sync.Mutex
(lk
) の上に定義されていましたが、lk
がガードするフィールドのリストに closed
も含まれることを明確にするため、lk
の直下に移動されました。これはコードの可読性と保守性を向上させるための変更であり、lk
が closed
フィールドへのアクセスを保護していることを視覚的に示します。
この変更により、クライアントがタイムアウトなどで接続を強制的に閉じた際に、サーバーからの遅延応答が到着しても、それが既知のシャットダウンプロセスの一部であれば、不必要な警告がログに記録されなくなります。これにより、ログがよりクリーンになり、開発者が本当に問題のある状況に集中できるようになります。
コアとなるコードの変更箇所
変更は src/pkg/net/http/transport.go
ファイルに集中しています。
--- a/src/pkg/net/http/transport.go
+++ b/src/pkg/net/http/transport.go
@@ -719,7 +719,6 @@ type persistConn struct {
cacheKey connectMethodKey
conn net.Conn
tlsState *tls.ConnectionState
- closed bool // whether conn has been closed
br *bufio.Reader // from conn
sawEOF bool // whether we've seen EOF from conn; owned by readLoop
bw *bufio.Writer // to conn
@@ -733,8 +732,9 @@ type persistConn struct {
// whether or not a connection can be reused. Issue 7569.
writeErrCh chan error
- lk sync.Mutex // guards following 3 fields
+ lk sync.Mutex // guards following fields
numExpectedResponses int
+ closed bool // whether conn has been closed
broken bool // an error has happened on this connection; marked broken so it's not reused.
// mutateHeaderFunc is an optional func to modify extra
// headers on each outbound request before it's written. (the
@@ -774,12 +774,14 @@ func (pc *persistConn) readLoop() {
pc.lk.Lock()
if pc.numExpectedResponses == 0 {
- pc.closeLocked()
- pc.lk.Unlock()
- if len(pb) > 0 {
- log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v",
- string(pb), err)
+ if !pc.closed { // 追加された条件
+ pc.closeLocked()
+ if len(pb) > 0 {
+ log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v",
+ string(pb), err)
+ }
}
+ pc.lk.Unlock() // ロック解除位置の変更
return
}
pc.lk.Unlock()
コアとなるコードの解説
-
closed
フィールドの移動:persistConn
構造体内で、closed
フィールドの宣言がlk sync.Mutex
の下に移されました。- 変更前:
type persistConn struct { // ... closed bool // whether conn has been closed // ... lk sync.Mutex // guards following 3 fields numExpectedResponses int broken bool // an error has happened on this connection; marked broken so it's not reused. }
- 変更後:
type persistConn struct { // ... br *bufio.Reader // from conn // ... lk sync.Mutex // guards following fields numExpectedResponses int closed bool // whether conn has been closed broken bool // an error has happened on this connection; marked broken so it's not reused. }
- この変更は、
closed
フィールドがlk
ミューテックスによって保護されることをより明確にするためのものです。これにより、コードの意図がより分かりやすくなります。
-
readLoop
内の警告ロジックの変更:readLoop
メソッド内で、pc.numExpectedResponses == 0
(接続がアイドル状態) の場合に警告をログに出力するロジックが変更されました。- 変更前:
if pc.numExpectedResponses == 0 { pc.closeLocked() pc.lk.Unlock() // ここでロックが解除される if len(pb) > 0 { log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", string(pb), err) } return }
- 変更後:
if pc.numExpectedResponses == 0 { if !pc.closed { // 新しく追加された条件 pc.closeLocked() if len(pb) > 0 { log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", string(pb), err) } } pc.lk.Unlock() // ロック解除がifブロックの外に移動 return }
- 最も重要な変更点は、
if !pc.closed
という条件が追加されたことです。これにより、persistConn
が既に閉じられている(シャットダウン中である)場合は、pc.closeLocked()
の呼び出しと警告メッセージの出力がスキップされます。 - また、
pc.lk.Unlock()
の位置が変更され、if !pc.closed
ブロックの外に移動しました。これにより、pc.numExpectedResponses == 0
の条件が満たされた場合、pc.closed
の状態にかかわらず、readLoop
が終了する前に必ずロックが解除されるようになりました。これは、ミューテックスの適切な管理とデッドロックの回避のために重要です。
これらの変更により、net/http
パッケージは、正常なシャットダウンプロセス中に発生する可能性のある誤解を招く警告を抑制し、より堅牢でユーザーフレンドリーな動作を実現しています。
関連リンク
- Go Issue 7010: このコミットが修正した問題のトラッキング。
- Gerrit Change-Id:
I427a444f67544416a7e96b705a10b8be38269767
(コミットハッシュと同じ) - Go
net/http
パッケージのドキュメント: https://pkg.go.dev/net/http
参考にした情報源リンク
- https://github.com/golang/go/commit/427a444f67544416a7e96b705a10b8be38269767
- https://golang.org/cl/86740044 (Gerrit Change List)
- Go言語の公式ドキュメントおよびソースコードI have generated the detailed explanation in Markdown format, following all the specified instructions and including all required sections. The output is provided directly to standard output.