[インデックス 14496] ファイルの概要
コミット
commit 127d2bf78595c6e07d59fd91ea97ca0bb516ed73
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Nov 26 13:31:02 2012 -0800
net/http: fix Transport races & deadlocks
Thanks to Dustin Sallings for exposing the most frustrating
bug ever, and for providing repro cases (which formed the
basis of the new tests in this CL), and to Dave Cheney and
Dmitry Vyukov for help debugging and fixing.
This CL depends on submited pollster CLs ffd1e075c260 (Unix)
and 14b544194509 (Windows), as well as unsubmitted 6852085.
Some operating systems (OpenBSD, NetBSD, ?) may still require
more pollster work, fixing races (Issue 4434 and
http://goo.gl/JXB6W).
Tested on linux-amd64 and darwin-amd64, both with GOMAXPROCS 1
and 4 (all combinations of which previously failed differently)
Fixes #4191
Update #4434 (related fallout from this bug)
R=dave, bradfitz, dsallings, rsc, fullung
CC=golang-dev
https://golang.org/cl/6851061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/127d2bf78595c6e07d59fd91ea97ca0bb516ed73
元コミット内容
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおける Transport
の競合状態(race conditions)とデッドロック(deadlocks)を修正することを目的としています。特に、HTTPクライアントがアイドル状態のコネクションを再利用する際の信頼性を向上させ、リソースリークやハングアップを防ぎます。
主な変更点は以下の通りです。
net/http/transport.go
:bodyEOFSignal
構造体の修正と、persistConn
のreadLoop
におけるコネクションの再利用ロジックの改善。sync/atomic
の使用を削除し、sync.Mutex
を用いたより堅牢な同期メカニズムを導入しています。bodyEOFSignal
のfn
コールバックがエラー情報を受け取るように変更され、コネクションの健全性判断に利用されます。src/pkg/net/http/server.go
: デバッグ目的のloggingConn
の追加と、Server
のnewConn
メソッドでのloggingConn
の利用。これは、サーバーサイドのコネクションの挙動を詳細にログ出力するためのもので、問題の診断に役立ちます。src/pkg/net/http/export_test.go
:NewLoggingConn
関数をエクスポートし、テストからloggingConn
を利用できるようにしています。src/pkg/net/http/transport_test.go
:TestIssue4191_InfiniteGetTimeout
とTestIssue4191_InfiniteGetToPutTimeout
という新しいテストケースが追加されています。これらは、無限にデータを送信するサーバーに対するGETリクエストや、GETで取得したボディをPUTリクエストのボディとして再利用するシナリオで発生するタイムアウトの問題を再現し、修正を検証するためのものです。
この修正は、Dustin Sallings氏が発見し再現ケースを提供したバグを解決し、Dave Cheney氏とDmitry Vyukov氏がデバッグと修正に協力しました。また、この変更は、UnixおよびWindows向けのポーリング関連の先行コミット(ffd1e075c260, 14b544194509)と、未提出のコミット(6852085)に依存しています。
変更の背景
このコミットの背景には、Go言語の net/http
パッケージ、特にHTTPクライアントの Transport
における深刻な競合状態とデッドロックの問題がありました。これらの問題は、HTTPコネクションの再利用(Keep-Alive)や、レスポンスボディの読み込みが完了する前にコネクションが閉じられる、あるいは再利用されるといったシナリオで顕在化しました。
具体的には、以下のGitHub Issueが関連しています。
- Issue 4191:
net/http
: infinite Get timeout- このIssueは、HTTPクライアントがサーバーから無限にデータをストリーミングされるような状況で、
Transport
がタイムアウトせずにハングアップしてしまう問題が報告されています。これは、レスポンスボディの読み込みが完了しない限り、コネクションがアイドル状態にならず、結果としてリソースが解放されないために発生していました。
- このIssueは、HTTPクライアントがサーバーから無限にデータをストリーミングされるような状況で、
- Issue 4434:
net/http
: Transport race condition with bodyEOFSignal- このIssueは、
bodyEOFSignal
という内部構造体に関連する競合状態を指摘しています。bodyEOFSignal
は、HTTPレスポンスボディの読み込みが完了した際に特定のコールバック関数を実行するために使用されますが、複数のゴルーチンが同時にボディを読み込もうとしたり、コネクションを閉じようとしたりする際に、不適切な同期が原因で競合状態が発生し、パニックやデッドロックを引き起こす可能性がありました。
- このIssueは、
これらの問題は、GoアプリケーションがHTTP通信を行う際の信頼性と安定性を著しく損なうものであり、特に高負荷な環境や長時間の接続を維持するアプリケーションでは、サービス停止につながる可能性がありました。そのため、これらの根本的な原因を特定し、堅牢な同期メカニズムを導入することが急務でした。
コミットメッセージには、Dustin Sallings氏が「最も苛立たしいバグ」と表現し、再現ケースを提供したことが記されており、この問題の深刻さが伺えます。また、Dave Cheney氏とDmitry Vyukov氏といったGoコミュニティの著名な開発者がデバッグと修正に協力したことからも、その複雑さと重要性がうかがえます。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびネットワークプログラミングに関する前提知識が必要です。
-
Go言語の並行処理 (Concurrency in Go)
- ゴルーチン (Goroutines): Go言語における軽量なスレッドのようなもので、数千、数万といった単位で同時に実行できます。Goの並行処理の基本単位です。
- チャネル (Channels): ゴルーチン間で安全にデータを送受信するための通信メカニズムです。チャネルは、データの受け渡しを通じてゴルーチン間の同期も行います。
sync
パッケージ:sync.Mutex
: 相互排他ロックを提供し、共有リソースへのアクセスを一度に一つのゴルーチンに制限することで、競合状態を防ぎます。Lock()
でロックを取得し、Unlock()
でロックを解放します。sync.Once
: 特定の関数が一度だけ実行されることを保証します。初期化処理などでよく使われます。sync/atomic
パッケージ: 低レベルなアトミック操作(不可分操作)を提供します。これにより、ロックを使用せずに特定の変数の読み書きを安全に行うことができますが、複雑なロジックには向かず、誤用するとデッドロックや競合状態を引き起こす可能性があります。
-
HTTPプロトコルとGoの
net/http
パッケージ- HTTP Keep-Alive: HTTP/1.1で導入された機能で、一つのTCPコネクション上で複数のHTTPリクエスト/レスポンスをやり取りすることを可能にします。これにより、コネクションの確立・切断のオーバーヘッドを削減し、パフォーマンスを向上させます。
http.Client
: HTTPリクエストを送信し、HTTPレスポンスを受信するためのクライアントです。http.Transport
:http.Client
の内部で使用され、実際のHTTPリクエストの送信(コネクションの確立、リクエストの書き込み、レスポンスの読み込みなど)を担当します。コネクションプーリング(Keep-Aliveコネクションの管理)も行います。http.Server
: HTTPリクエストを受け付け、HTTPレスポンスを返すためのサーバーです。io.Reader
/io.Writer
/io.ReadCloser
: Go言語におけるI/O操作のインターフェースです。HTTPレスポンスボディはio.ReadCloser
として提供され、読み込みとクローズが可能です。io.Copy
:io.Reader
からio.Writer
へデータをコピーするユーティリティ関数です。bufio.Reader
/bufio.Writer
: バッファリングされたI/Oを提供し、効率的な読み書きを可能にします。
-
競合状態 (Race Conditions) とデッドロック (Deadlocks)
- 競合状態: 複数のゴルーチンが共有リソースに同時にアクセスし、そのアクセス順序によってプログラムの最終結果が非決定的に変わってしまう状態です。予期せぬ動作やデータ破損の原因となります。
- デッドロック: 複数のゴルーチンが互いに相手が保持しているリソースの解放を待ち続け、結果としてどのゴルーチンも処理を進められなくなる状態です。プログラムが完全に停止してしまいます。
-
ポーリング (Polling) とI/O多重化 (I/O Multiplexing)
- GoのネットワークI/Oは、内部的にOSのポーリングメカニズム(Linuxのepoll、macOS/FreeBSDのkqueue、WindowsのIOCPなど)を利用して効率的に処理されます。これらのメカニズムは、多数のI/O操作を少数のスレッドで効率的に管理するために重要です。コミットメッセージで言及されている「pollster CLs」は、これらのOS固有のポーリング実装に関する修正を指しています。
これらの概念を理解することで、net/http
パッケージがどのように並行処理を扱い、なぜ競合状態やデッドロックが発生しうるのか、そしてこのコミットがどのようにそれらを解決しようとしているのかを深く把握できます。
技術的詳細
このコミットの技術的詳細は、主に net/http
パッケージにおけるコネクション管理とレスポンスボディの処理における並行処理の課題とその解決策に焦点を当てています。
1. bodyEOFSignal
の改善
bodyEOFSignal
は、HTTPレスポンスボディの読み込みが完了(EOFに到達)した際、またはボディがクローズされた際に、特定のコールバック関数 fn
を実行するためのラッパーです。以前の実装では、sync/atomic
を使用して isClosed
フラグを管理し、sync.Once
を使って fn
が一度だけ実行されることを保証していました。
変更点:
isClosed
フラグとsync.Once
の代わりに、sync.Mutex
と単純なclosed
ブール値、そしてrerr
(sticky Read error) を導入しました。Read
メソッドとClose
メソッドの両方で、es.mu.Lock()
とes.mu.Unlock()
を使用して共有状態(closed
,rerr
,fn
)へのアクセスを保護しています。fn
コールバックがerror
型の引数を受け取るように変更されました (func(error)
)。これにより、ボディの読み込みがEOFで終了したのか、それともエラーで終了したのかをコールバック内で判断できるようになります。EOFの場合はnil
が渡されます。condfn
メソッドは、fn
が実行された後にes.fn = nil
と設定することで、二重実行を防ぎます。これはsync.Once
と同様の効果を持ちますが、より柔軟なエラーハンドリングを可能にします。
目的:
この変更により、bodyEOFSignal
の内部状態管理がより堅牢になり、複数のゴルーチンが同時にボディを読み込んだりクローズしたりする際の競合状態が解消されます。特に、Read
がエラーを返した場合でも fn
が適切に呼び出され、コネクションの健全性判断に役立つ情報が提供されるようになります。
2. persistConn.readLoop
の修正
persistConn
は、HTTPクライアントがサーバーとの間で確立した永続的な(Keep-Alive)コネクションを表します。readLoop
は、このコネクション上でサーバーからのレスポンスを継続的に読み取るゴルーチンです。
変更点:
resp.Body.(*bodyEOFSignal).fn
のコールバック関数がfunc(err error)
シグネチャに合わせるように変更されました。- コールバック内で
alive1 := alive
とif err != nil { alive1 = false }
を追加し、ボディの読み込みがエラーで終了した場合にalive
フラグをfalse
に設定するようにしました。これにより、エラーが発生したコネクションはアイドルプールに再利用されなくなります。 waitForBodyRead <- true
がwaitForBodyRead <- alive1
に変更されました。これは、ボディの読み込みが完了した際に、コネクションがまだ再利用可能であるかどうかの情報(alive1
)をreadLoop
に伝えるためです。alive = <-waitForBodyRead
の行が追加され、readLoop
がボディの読み込み完了を待つだけでなく、その結果としてコネクションが再利用可能であるかどうかの情報を受け取るようになりました。
目的:
この修正は、特にIssue 4191で報告された「無限Getタイムアウト」の問題に対処します。以前は、レスポンスボディの読み込みが完了しない限り、readLoop
はコネクションがアイドル状態になったと判断せず、結果としてコネクションが閉じられずにリソースリークやハングアップが発生していました。新しいロジックでは、ボディの読み込みがエラーで終了した場合(例: タイムアウト)、コネクションが「死んでいる」と判断し、アイドルプールに再利用しないようにします。これにより、問題のあるコネクションが再利用されることを防ぎ、リソースが適切に解放されるようになります。
3. デバッグ用 loggingConn
の追加
src/pkg/net/http/server.go
に loggingConn
という新しい型が追加されました。これは net.Conn
インターフェースをラップし、Read
, Write
, Close
メソッドが呼び出されるたびに詳細なログを出力します。
目的:
この loggingConn
は、net/http
パッケージ内部のコネクションの挙動を詳細に追跡するためのデバッグツールとして導入されました。特に、競合状態やデッドロックのような複雑な並行処理の問題を診断する際に、コネクションのライフサイクルやデータフローを可視化するのに非常に役立ちます。debugServerConnections
フラグを true
に設定することで、サーバー側のすべてのコネクションにこのロギングが適用されます。テストコードでも NewLoggingConn
を使用してクライアント側のコネクションのデバッグに利用されています。
4. 新しいテストケース
transport_test.go
に追加された TestIssue4191_InfiniteGetTimeout
と TestIssue4191_InfiniteGetToPutTimeout
は、この修正の有効性を検証するための重要なテストです。これらは、サーバーが無限にデータを送信するシナリオをシミュレートし、クライアントがタイムアウトすることなく適切にコネクションを処理できることを確認します。
目的:
これらのテストは、以前のバグが再現しないことを保証し、将来のリグレッションを防ぐためのものです。特に、io.Copy(ioutil.Discard, sres.Body)
がエラーを返すことを期待しており、これはタイムアウトによってボディの読み込みが中断されることを意味します。
全体として、このコミットは net/http
パッケージのコネクション管理における並行処理の堅牢性を大幅に向上させ、HTTPクライアントの信頼性を高めるための重要な修正です。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下の3つのファイルに集中しています。
-
src/pkg/net/http/transport.go
:bodyEOFSignal
構造体の定義と、そのRead
,Close
,condfn
メソッドの実装が大幅に変更されています。isClosed
(atomic bool) とsync.Once
の使用が削除され、sync.Mutex
とclosed
(bool),rerr
(error) が導入されました。fn
コールバックのシグネチャがfunc()
からfunc(error)
に変更されました。
persistConn.readLoop
メソッド内のresp.Body.(*bodyEOFSignal).fn
のコールバックロジックが修正され、エラーハンドリングとalive
フラグの更新が改善されました。waitForBodyRead
チャネルへの送信値がtrue
からalive1
(コネクションの健全性を示すブール値) に変更されました。readLoop
がwaitForBodyRead
からalive
の値を受け取るようになりました。
-
src/pkg/net/http/server.go
:loggingConn
構造体と、そのnewLoggingConn
,Write
,Read
,Close
メソッドが追加されました。Server.newConn
メソッド内でdebugServerConnections
フラグがtrue
の場合にnewLoggingConn
を使用してコネクションをラップするロジックが追加されました。
-
src/pkg/net/http/transport_test.go
:TestIssue4191_InfiniteGetTimeout
とTestIssue4191_InfiniteGetToPutTimeout
という2つの新しいテスト関数が追加されました。これらは、net/http
クライアントのタイムアウトとコネクション処理に関するバグを再現し、修正を検証するためのものです。
これらの変更は、HTTPコネクションのライフサイクル管理、特にレスポンスボディの読み込みとコネクションの再利用における競合状態とデッドロックの問題を直接的に解決するためのものです。
コアとなるコードの解説
src/pkg/net/http/transport.go
の変更
bodyEOFSignal
の変更
type bodyEOFSignal struct {
body io.ReadCloser
mu sync.Mutex // guards closed, rerr and fn
closed bool // whether Close has been called
rerr error // sticky Read error
fn func(error) // error will be nil on Read io.EOF
}
func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
es.mu.Lock()
closed, rerr := es.closed, es.rerr
es.mu.Unlock()
if closed {
return 0, errors.New("http: read on closed response body")
}
if rerr != nil {
return 0, rerr
}
n, err = es.body.Read(p)
if err != nil {
es.mu.Lock()
defer es.mu.Unlock()
if es.rerr == nil { // 最初のエラーのみを記録
es.rerr = err
}
es.condfn(err) // エラー発生時にもコールバックを呼び出す
}
return
}
func (es *bodyEOFSignal) Close() error {
es.mu.Lock()
defer es.mu.Unlock()
if es.closed {
return nil
}
es.closed = true
err := es.body.Close()
es.condfn(err) // クローズ時にもコールバックを呼び出す
return err
}
// caller must hold es.mu.
func (es *bodyEOFSignal) condfn(err error) {
if es.fn == nil {
return
}
if err == io.EOF { // EOFはエラーではないと見なす
err = nil
}
es.fn(err) // コールバックを実行
es.fn = nil // コールバックは一度だけ実行される
}
-
bodyEOFSignal
構造体:sync.Mutex mu
: 以前のsync/atomic
とsync.Once
の組み合わせに代わり、mu
がclosed
,rerr
,fn
といった共有状態へのアクセスを排他的に保護します。これにより、複数のゴルーチンからの同時アクセスによる競合状態を防ぎ、より安全な同期を実現します。closed bool
:Close
メソッドが呼び出されたかどうかを示すフラグです。rerr error
:Read
メソッドで発生した最初のエラーを保持します。これにより、後続のRead
呼び出しで同じエラーを返すことができます。fn func(error)
: コールバック関数がエラー情報を受け取れるようになりました。io.EOF
の場合はnil
が渡されます。
-
Read
メソッド:es.mu.Lock()
とes.mu.Unlock()
で共有状態へのアクセスを保護します。- 既にクローズされている場合や、以前にエラーが発生している場合は、すぐにエラーを返します。
- 実際の
es.body.Read(p)
の呼び出し後、エラーが発生した場合にes.rerr
にエラーを記録し、es.condfn(err)
を呼び出します。これにより、ボディの読み込み中にエラーが発生した場合でも、fn
コールバックが適切に実行され、コネクションの健全性に関する情報が提供されます。
-
Close
メソッド:es.mu.Lock()
とes.mu.Unlock()
で共有状態へのアクセスを保護します。es.closed = true
と設定し、es.body.Close()
を呼び出した後、es.condfn(err)
を呼び出します。これにより、ボディがクローズされた際にもfn
コールバックが実行されます。
-
condfn
メソッド:es.mu
がロックされている状態で呼び出されることを前提とします。es.fn
がnil
でない場合のみ実行されます。err == io.EOF
の場合、err
をnil
に設定します。これは、io.EOF
が正常な終了を示すため、コールバックにエラーとして渡すべきではないという意図です。es.fn(err)
を呼び出し、その後es.fn = nil
と設定することで、コールバックが一度だけ実行されることを保証します。
persistConn.readLoop
の変更
// ... (前略) ...
if hasBody {
lastbody = resp.Body
waitForBodyRead = make(chan bool, 1)
resp.Body.(*bodyEOFSignal).fn = func(err error) { // fn が error を受け取るように変更
alive1 := alive // readLoop の alive 変数の現在の値をキャプチャ
if err != nil { // ボディの読み込みがエラーで終了した場合
alive1 = false // コネクションは再利用不可
}
if alive1 && !pc.t.putIdleConn(pc) { // コネクションが健全で、アイドルプールに入れられなかった場合
alive1 = false // やはり再利用不可
}
if !alive1 || pc.isBroken() { // コネクションが再利用不可または壊れている場合
pc.close() // コネクションを閉じる
}
waitForBodyRead <- alive1 // コネクションの健全性情報を readLoop に送信
}
}
// ... (中略) ...
// Wait for the just-returned response body to be fully consumed
// before we race and peek on the underlying bufio reader.
if waitForBodyRead != nil {
alive = <-waitForBodyRead // コネクションの健全性情報を受け取る
}
// ... (後略) ...
resp.Body.(*bodyEOFSignal).fn = func(err error)
:bodyEOFSignal
の変更に合わせて、コールバック関数がerror
引数を受け取るようになりました。alive1 := alive
:readLoop
のスコープにあるalive
変数の現在の値をalive1
にコピーします。これは、コールバックが実行される時点でのコネクションの「生きてる」状態を判断するためです。if err != nil { alive1 = false }
: ボディの読み込みがエラー(例: タイムアウト)で終了した場合、そのコネクションは再利用すべきではないため、alive1
をfalse
に設定します。if alive1 && !pc.t.putIdleConn(pc) { alive1 = false }
: コネクションがまだ「生きていて」、かつアイドルプールに正常に入れられなかった場合(例: プールが満杯)、そのコネクションは再利用できないと判断し、alive1
をfalse
に設定します。if !alive1 || pc.isBroken() { pc.close() }
: 上記の条件でalive1
がfalse
になった場合、またはコネクションが壊れていると判断された場合、pc.close()
を呼び出してコネクションを閉じます。これにより、問題のあるコネクションがアイドルプールに残ることを防ぎ、リソースリークやハングアップを回避します。waitForBodyRead <- alive1
:bodyEOFSignal
のコールバックが完了した際に、コネクションがまだ再利用可能であるかどうかの最終的な情報(alive1
)をreadLoop
に送信します。alive = <-waitForBodyRead
:readLoop
は、レスポンスボディの読み込みが完了するのを待つだけでなく、その結果としてコネクションが再利用可能であるかどうかの情報を受け取ります。このalive
の値に基づいて、次のリクエストでこのコネクションを再利用するか、それとも閉じるかを決定します。
これらの変更により、net/http
クライアントは、レスポンスボディの読み込み中に発生するエラーや、コネクションの再利用に関する問題をより適切に処理できるようになり、全体的な堅牢性と信頼性が向上します。
src/pkg/net/http/server.go
の変更
loggingConn
の追加
// loggingConn is used for debugging.
type loggingConn struct {
name string
net.Conn
}
var (
uniqNameMu sync.Mutex
uniqNameNext = make(map[string]int)
)
func newLoggingConn(baseName string, c net.Conn) net.Conn {
uniqNameMu.Lock()
defer uniqNameMu.Unlock()
uniqNameNext[baseName]++
return &loggingConn{
name: fmt.Sprintf("%s-%d", baseName, uniqNameNext[baseName]),
Conn: c,
}
}
func (c *loggingConn) Write(p []byte) (n int, err error) {
log.Printf("%s.Write(%d) = ....", c.name, len(p))
n, err = c.Conn.Write(p)
log.Printf("%s.Write(%d) = %d, %v", c.name, len(p), n, err)
return
}
func (c *loggingConn) Read(p []byte) (n int, err error) {
log.Printf("%s.Read(%d) = ....", c.name, len(p))
n, err = c.Conn.Read(p)
log.Printf("%s.Read(%d) = %d, %v", c.name, len(p), n, err)
return
}
func (c *loggingConn) Close() (err error) {
log.Printf("%s.Close() = ...", c.name)
err = c.Conn.Close()
log.Printf("%s.Close() = %v", c.name, err)
return
}
loggingConn
はnet.Conn
を埋め込み、Read
,Write
,Close
メソッドをオーバーライドしています。- これらのメソッドは、実際のI/O操作の前後でログメッセージを出力します。これにより、コネクション上でのデータの送受信やクローズのタイミングを詳細に追跡できます。
newLoggingConn
は、一意の名前を生成してloggingConn
インスタンスを作成します。
Server.newConn
での loggingConn
の利用
// ... (前略) ...
const debugServerConnections = false // デバッグフラグ
// Create new connection from rwc.
func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) {
c = new(conn)
c.remoteAddr = rwc.RemoteAddr().String()
c.server = srv
c.rwc = rwc
if debugServerConnections { // フラグが true の場合のみ有効
c.rwc = newLoggingConn("server", c.rwc)
}
c.body = make([]byte, sniffLen)
c.lr = io.LimitReader(c.rwc, noLimit).(*io.LimitedReader) // c.rwc を使用
br := bufio.NewReader(c.lr)
bw := bufio.NewWriter(c.rwc) // c.rwc を使用
c.buf = bufio.NewReadWriter(br, bw)
return c, nil
}
// ... (後略) ...
debugServerConnections
という定数フラグが導入されました。このフラグがtrue
に設定されている場合、Server.newConn
が新しいコネクションを作成する際に、そのnet.Conn
オブジェクトをloggingConn
でラップします。- これにより、サーバーサイドのHTTPコネクションの挙動を詳細にログ出力できるようになり、デバッグや問題の診断に役立ちます。
これらの変更は、Goの net/http
パッケージの堅牢性を高め、特に並行処理における潜在的な問題を解決するための重要なステップです。
関連リンク
- Go Issue 4191:
net/http
: infinite Get timeout - https://github.com/golang/go/issues/4191 - Go Issue 4434:
net/http
: Transport race condition with bodyEOFSignal - https://github.com/golang/go/issues/4434 - Go CL 6851061:
net/http
: fix Transport races & deadlocks - https://golang.org/cl/6851061 (Gerrit Code Review)
参考にした情報源リンク
- Go言語の公式ドキュメント (
net/http
パッケージ): https://pkg.go.dev/net/http - Go言語の並行処理に関する公式ドキュメントやチュートリアル
- Go言語の
sync
パッケージに関するドキュメント: https://pkg.go.dev/sync - Go言語の
sync/atomic
パッケージに関するドキュメント: https://pkg.go.dev/sync/atomic - HTTP/1.1 Persistent Connections (Keep-Alive) に関するRFC (RFC 2616, Section 8.1.2): https://www.rfc-editor.org/rfc/rfc2616#section-8.1.2
- 競合状態とデッドロックに関する一般的なプログラミングの概念
- ポーリングとI/O多重化に関するオペレーティングシステムの概念# [インデックス 14496] ファイルの概要
コミット
commit 127d2bf78595c6e07d59fd91ea97ca0bb516ed73
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Mon Nov 26 13:31:02 2012 -0800
net/http: fix Transport races & deadlocks
Thanks to Dustin Sallings for exposing the most frustrating
bug ever, and for providing repro cases (which formed the
basis of the new tests in this CL), and to Dave Cheney and
Dmitry Vyukov for help debugging and fixing.
This CL depends on submited pollster CLs ffd1e075c260 (Unix)
and 14b544194509 (Windows), as well as unsubmitted 6852085.
Some operating systems (OpenBSD, NetBSD, ?) may still require
more pollster work, fixing races (Issue 4434 and
http://goo.gl/JXB6W).
Tested on linux-amd64 and darwin-amd64, both with GOMAXPROCS 1
and 4 (all combinations of which previously failed differently)
Fixes #4191
Update #4434 (related fallout from this bug)
R=dave, bradfitz, dsallings, rsc, fullung
CC=golang-dev
https://golang.org/cl/6851061
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/127d2bf78595c6e07d59fd91ea97ca0bb516ed73
元コミット内容
このコミットは、Go言語の標準ライブラリである net/http
パッケージにおける Transport
の競合状態(race conditions)とデッドロック(deadlocks)を修正することを目的としています。特に、HTTPクライアントがアイドル状態のコネクションを再利用する際の信頼性を向上させ、リソースリークやハングアップを防ぎます。
主な変更点は以下の通りです。
net/http/transport.go
:bodyEOFSignal
構造体の修正と、persistConn
のreadLoop
におけるコネクションの再利用ロジックの改善。sync/atomic
の使用を削除し、sync.Mutex
を用いたより堅牢な同期メカニズムを導入しています。bodyEOFSignal
のfn
コールバックがエラー情報を受け取るように変更され、コネクションの健全性判断に利用されます。src/pkg/net/http/server.go
: デバッグ目的のloggingConn
の追加と、Server
のnewConn
メソッドでのloggingConn
の利用。これは、サーバーサイドのコネクションの挙動を詳細にログ出力するためのもので、問題の診断に役立ちます。src/pkg/net/http/export_test.go
:NewLoggingConn
関数をエクスポートし、テストからloggingConn
を利用できるようにしています。src/pkg/net/http/transport_test.go
:TestIssue4191_InfiniteGetTimeout
とTestIssue4191_InfiniteGetToPutTimeout
という新しいテストケースが追加されています。これらは、無限にデータを送信するサーバーに対するGETリクエストや、GETで取得したボディをPUTリクエストのボディとして再利用するシナリオで発生するタイムアウトの問題を再現し、修正を検証するためのものです。
この修正は、Dustin Sallings氏が発見し再現ケースを提供したバグを解決し、Dave Cheney氏とDmitry Vyukov氏がデバッグと修正に協力しました。また、この変更は、UnixおよびWindows向けのポーリング関連の先行コミット(ffd1e075c260, 14b544194509)と、未提出のコミット(6852085)に依存しています。
変更の背景
このコミットの背景には、Go言語の net/http
パッケージ、特にHTTPクライアントの Transport
における深刻な競合状態とデッドロックの問題がありました。これらの問題は、HTTPコネクションの再利用(Keep-Alive)や、レスポンスボディの読み込みが完了する前にコネクションが閉じられる、あるいは再利用されるといったシナリオで顕在化しました。
具体的には、以下のGitHub Issueが関連しています。
- Issue 4191:
net/http
: infinite Get timeout- このIssueは、HTTPクライアントがサーバーから無限にデータをストリーミングされるような状況で、
Transport
がタイムアウトせずにハングアップしてしまう問題が報告されています。これは、レスポンスボディの読み込みが完了しない限り、コネクションがアイドル状態にならず、結果としてリソースが解放されないために発生していました。
- このIssueは、HTTPクライアントがサーバーから無限にデータをストリーミングされるような状況で、
- Issue 4434:
net/http
: Transport race condition with bodyEOFSignal- このIssueは、
bodyEOFSignal
という内部構造体に関連する競合状態を指摘しています。bodyEOFSignal
は、HTTPレスポンスボディの読み込みが完了した際に特定のコールバック関数を実行するために使用されますが、複数のゴルーチンが同時にボディを読み込もうとしたり、コネクションを閉じようとしたりする際に、不適切な同期が原因で競合状態が発生し、パニックやデッドロックを引き起こす可能性がありました。
- このIssueは、
これらの問題は、GoアプリケーションがHTTP通信を行う際の信頼性と安定性を著しく損なうものであり、特に高負荷な環境や長時間の接続を維持するアプリケーションでは、サービス停止につながる可能性がありました。そのため、これらの根本的な原因を特定し、堅牢な同期メカニズムを導入することが急務でした。
コミットメッセージには、Dustin Sallings氏が「最も苛立たしいバグ」と表現し、再現ケースを提供したことが記されており、この問題の深刻さが伺えます。また、Dave Cheney氏とDmitry Vyukov氏といったGoコミュニティの著名な開発者がデバッグと修正に協力したことからも、その複雑さと重要性がうかがえます。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびネットワークプログラミングに関する前提知識が必要です。
-
Go言語の並行処理 (Concurrency in Go)
- ゴルーチン (Goroutines): Go言語における軽量なスレッドのようなもので、数千、数万といった単位で同時に実行できます。Goの並行処理の基本単位です。
- チャネル (Channels): ゴルーチン間で安全にデータを送受信するための通信メカニズムです。チャネルは、データの受け渡しを通じてゴルーチン間の同期も行います。
sync
パッケージ:sync.Mutex
: 相互排他ロックを提供し、共有リソースへのアクセスを一度に一つのゴルーチンに制限することで、競合状態を防ぎます。Lock()
でロックを取得し、Unlock()
でロックを解放します。sync.Once
: 特定の関数が一度だけ実行されることを保証します。初期化処理などでよく使われます。sync/atomic
パッケージ: 低レベルなアトミック操作(不可分操作)を提供します。これにより、ロックを使用せずに特定の変数の読み書きを安全に行うことができますが、複雑なロジックには向かず、誤用するとデッドロックや競合状態を引き起こす可能性があります。
-
HTTPプロトコルとGoの
net/http
パッケージ- HTTP Keep-Alive: HTTP/1.1で導入された機能で、一つのTCPコネクション上で複数のHTTPリクエスト/レスポンスをやり取りすることを可能にします。これにより、コネクションの確立・切断のオーバーヘッドを削減し、パフォーマンスを向上させます。
http.Client
: HTTPリクエストを送信し、HTTPレスポンスを受信するためのクライアントです。http.Transport
:http.Client
の内部で使用され、実際のHTTPリクエストの送信(コネクションの確立、リクエストの書き込み、レスポンスの読み込みなど)を担当します。コネクションプーリング(Keep-Aliveコネクションの管理)も行います。http.Server
: HTTPリクエストを受け付け、HTTPレスポンスを返すためのサーバーです。io.Reader
/io.Writer
/io.ReadCloser
: Go言語におけるI/O操作のインターフェースです。HTTPレスポンスボディはio.ReadCloser
として提供され、読み込みとクローズが可能です。io.Copy
:io.Reader
からio.Writer
へデータをコピーするユーティリティ関数です。bufio.Reader
/bufio.Writer
: バッファリングされたI/Oを提供し、効率的な読み書きを可能にします。
-
競合状態 (Race Conditions) とデッドロック (Deadlocks)
- 競合状態: 複数のゴルーチンが共有リソースに同時にアクセスし、そのアクセス順序によってプログラムの最終結果が非決定的に変わってしまう状態です。予期せぬ動作やデータ破損の原因となります。
- デッドロック: 複数のゴルーチンが互いに相手が保持しているリソースの解放を待ち続け、結果としてどのゴルーチンも処理を進められなくなる状態です。プログラムが完全に停止してしまいます。
-
ポーリング (Polling) とI/O多重化 (I/O Multiplexing)
- GoのネットワークI/Oは、内部的にOSのポーリングメカニズム(Linuxのepoll、macOS/FreeBSDのkqueue、WindowsのIOCPなど)を利用して効率的に処理されます。これらのメカニズムは、多数のI/O操作を少数のスレッドで効率的に管理するために重要です。コミットメッセージで言及されている「pollster CLs」は、これらのOS固有のポーリング実装に関する修正を指しています。
これらの概念を理解することで、net/http
パッケージがどのように並行処理を扱い、なぜ競合状態やデッドロックが発生しうるのか、そしてこのコミットがどのようにそれらを解決しようとしているのかを深く把握できます。
技術的詳細
このコミットの技術的詳細は、主に net/http
パッケージにおけるコネクション管理とレスポンスボディの処理における並行処理の課題とその解決策に焦点を当てています。
1. bodyEOFSignal
の改善
bodyEOFSignal
は、HTTPレスポンスボディの読み込みが完了(EOFに到達)した際、またはボディがクローズされた際に、特定のコールバック関数 fn
を実行するためのラッパーです。以前の実装では、sync/atomic
を使用して isClosed
フラグを管理し、sync.Once
を使って fn
が一度だけ実行されることを保証していました。
変更点:
isClosed
フラグとsync.Once
の代わりに、sync.Mutex
と単純なclosed
ブール値、そしてrerr
(sticky Read error) を導入しました。Read
メソッドとClose
メソッドの両方で、es.mu.Lock()
とes.mu.Unlock()
を使用して共有状態(closed
,rerr
,fn
)へのアクセスを保護しています。fn
コールバックがerror
型の引数を受け取るように変更されました (func(error)
)。これにより、ボディの読み込みがEOFで終了したのか、それともエラーで終了したのかをコールバック内で判断できるようになります。EOFの場合はnil
が渡されます。condfn
メソッドは、fn
が実行された後にes.fn = nil
と設定することで、二重実行を防ぎます。これはsync.Once
と同様の効果を持ちますが、より柔軟なエラーハンドリングを可能にします。
目的:
この変更により、bodyEOFSignal
の内部状態管理がより堅牢になり、複数のゴルーチンが同時にボディを読み込んだりクローズしたりする際の競合状態が解消されます。特に、Read
がエラーを返した場合でも fn
が適切に呼び出され、コネクションの健全性判断に役立つ情報が提供されるようになります。
2. persistConn.readLoop
の修正
persistConn
は、HTTPクライアントがサーバーとの間で確立した永続的な(Keep-Alive)コネクションを表します。readLoop
は、このコネクション上でサーバーからのレスポンスを継続的に読み取るゴルーチンです。
変更点:
resp.Body.(*bodyEOFSignal).fn
のコールバック関数がfunc(err error)
シグネチャに合わせるように変更されました。- コールバック内で
alive1 := alive
とif err != nil { alive1 = false }
を追加し、ボディの読み込みがエラーで終了した場合にalive
フラグをfalse
に設定するようにしました。これにより、エラーが発生したコネクションはアイドルプールに再利用されなくなります。 waitForBodyRead <- true
がwaitForBodyRead <- alive1
に変更されました。これは、ボディの読み込みが完了した際に、コネクションがまだ再利用可能であるかどうかの情報(alive1
)をreadLoop
に伝えるためです。alive = <-waitForBodyRead
の行が追加され、readLoop
がボディの読み込み完了を待つだけでなく、その結果としてコネクションが再利用可能であるかどうかの情報を受け取るようになりました。
目的:
この修正は、特にIssue 4191で報告された「無限Getタイムアウト」の問題に対処します。以前は、レスポンスボディの読み込みが完了しない限り、readLoop
はコネクションがアイドル状態になったと判断せず、結果としてコネクションが閉じられずにリソースリークやハングアップが発生していました。新しいロジックでは、ボディの読み込みがエラーで終了した場合(例: タイムアウト)、コネクションが「死んでいる」と判断し、アイドルプールに再利用しないようにします。これにより、問題のあるコネクションが再利用されることを防ぎ、リソースが適切に解放されるようになります。
3. デバッグ用 loggingConn
の追加
src/pkg/net/http/server.go
に loggingConn
という新しい型が追加されました。これは net.Conn
インターフェースをラップし、Read
, Write
, Close
メソッドが呼び出されるたびに詳細なログを出力します。
目的:
この loggingConn
は、net/http
パッケージ内部のコネクションの挙動を詳細に追跡するためのデバッグツールとして導入されました。特に、競合状態やデッドロックのような複雑な並行処理の問題を診断する際に、コネクションのライフサイクルやデータフローを可視化するのに非常に役立ちます。debugServerConnections
フラグを true
に設定することで、サーバー側のすべてのコネクションにこのロギングが適用されます。テストコードでも NewLoggingConn
を使用してクライアント側のコネクションのデバッグに利用されています。
4. 新しいテストケース
transport_test.go
に追加された TestIssue4191_InfiniteGetTimeout
と TestIssue4191_InfiniteGetToPutTimeout
は、この修正の有効性を検証するための重要なテストです。これらは、サーバーが無限にデータを送信するシナリオをシミュレートし、クライアントがタイムアウトすることなく適切にコネクションを処理できることを確認します。
目的:
これらのテストは、以前のバグが再現しないことを保証し、将来のリグレッションを防ぐためのものです。特に、io.Copy(ioutil.Discard, sres.Body)
がエラーを返すことを期待しており、これはタイムアウトによってボディの読み込みが中断されることを意味します。
全体として、このコミットは net/http
パッケージのコネクション管理における並行処理の堅牢性を大幅に向上させ、HTTPクライアントの信頼性を高めるための重要な修正です。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主に以下の3つのファイルに集中しています。
-
src/pkg/net/http/transport.go
:bodyEOFSignal
構造体の定義と、そのRead
,Close
,condfn
メソッドの実装が大幅に変更されています。isClosed
(atomic bool) とsync.Once
の使用が削除され、sync.Mutex
とclosed
(bool),rerr
(error) が導入されました。fn
コールバックのシグネチャがfunc()
からfunc(error)
に変更されました。
persistConn.readLoop
メソッド内のresp.Body.(*bodyEOFSignal).fn
のコールバックロジックが修正され、エラーハンドリングとalive
フラグの更新が改善されました。waitForBodyRead
チャネルへの送信値がtrue
からalive1
(コネクションの健全性を示すブール値) に変更されました。readLoop
がwaitForBodyRead
からalive
の値を受け取るようになりました。
-
src/pkg/net/http/server.go
:loggingConn
構造体と、そのnewLoggingConn
,Write
,Read
,Close
メソッドが追加されました。Server.newConn
メソッド内でdebugServerConnections
フラグがtrue
の場合にnewLoggingConn
を使用してコネクションをラップするロジックが追加されました。
-
src/pkg/net/http/transport_test.go
:TestIssue4191_InfiniteGetTimeout
とTestIssue4191_InfiniteGetToPutTimeout
という2つの新しいテスト関数が追加されました。これらは、net/http
クライアントのタイムアウトとコネクション処理に関するバグを再現し、修正を検証するためのものです。
これらの変更は、HTTPコネクションのライフサイクル管理、特にレスポンスボディの読み込みとコネクションの再利用における競合状態とデッドロックの問題を直接的に解決するためのものです。
コアとなるコードの解説
src/pkg/net/http/transport.go
の変更
bodyEOFSignal
の変更
type bodyEOFSignal struct {
body io.ReadCloser
mu sync.Mutex // guards closed, rerr and fn
closed bool // whether Close has been called
rerr error // sticky Read error
fn func(error) // error will be nil on Read io.EOF
}
func (es *bodyEOFSignal) Read(p []byte) (n int, err error) {
es.mu.Lock()
closed, rerr := es.closed, es.rerr
es.mu.Unlock()
if closed {
return 0, errors.New("http: read on closed response body")
}
if rerr != nil {
return 0, rerr
}
n, err = es.body.Read(p)
if err != nil {
es.mu.Lock()
defer es.mu.Unlock()
if es.rerr == nil { // 最初のエラーのみを記録
es.rerr = err
}
es.condfn(err) // エラー発生時にもコールバックを呼び出す
}
return
}
func (es *bodyEOFSignal) Close() error {
es.mu.Lock()
defer es.mu.Unlock()
if es.closed {
return nil
}
es.closed = true
err := es.body.Close()
es.condfn(err) // クローズ時にもコールバックを呼び出す
return err
}
// caller must hold es.mu.
func (es *bodyEOFSignal) condfn(err error) {
if es.fn == nil {
return
}
if err == io.EOF { // EOFはエラーではないと見なす
err = nil
}
es.fn(err) // コールバックを実行
es.fn = nil // コールバックは一度だけ実行される
}
-
bodyEOFSignal
構造体:sync.Mutex mu
: 以前のsync/atomic
とsync.Once
の組み合わせに代わり、mu
がclosed
,rerr
,fn
といった共有状態へのアクセスを排他的に保護します。これにより、複数のゴルーチンからの同時アクセスによる競合状態を防ぎ、より安全な同期を実現します。closed bool
:Close
メソッドが呼び出されたかどうかを示すフラグです。rerr error
:Read
メソッドで発生した最初のエラーを保持します。これにより、後続のRead
呼び出しで同じエラーを返すことができます。fn func(error)
: コールバック関数がエラー情報を受け取れるようになりました。io.EOF
の場合はnil
が渡されます。
-
Read
メソッド:es.mu.Lock()
とes.mu.Unlock()
で共有状態へのアクセスを保護します。- 既にクローズされている場合や、以前にエラーが発生している場合は、すぐにエラーを返します。
- 実際の
es.body.Read(p)
の呼び出し後、エラーが発生した場合にes.rerr
にエラーを記録し、es.condfn(err)
を呼び出します。これにより、ボディの読み込み中にエラーが発生した場合でも、fn
コールバックが適切に実行され、コネクションの健全性に関する情報が提供されます。
-
Close
メソッド:es.mu.Lock()
とes.mu.Unlock()
で共有状態へのアクセスを保護します。es.closed = true
と設定し、es.body.Close()
を呼び出した後、es.condfn(err)
を呼び出します。これにより、ボディがクローズされた際にもfn
コールバックが実行されます。
-
condfn
メソッド:es.mu
がロックされている状態で呼び出されることを前提とします。es.fn
がnil
でない場合のみ実行されます。err == io.EOF
の場合、err
をnil
に設定します。これは、io.EOF
が正常な終了を示すため、コールバックにエラーとして渡すべきではないという意図です。es.fn(err)
を呼び出し、その後es.fn = nil
と設定することで、コールバックが一度だけ実行されることを保証します。
persistConn.readLoop
の変更
// ... (前略) ...
if hasBody {
lastbody = resp.Body
waitForBodyRead = make(chan bool, 1)
resp.Body.(*bodyEOFSignal).fn = func(err error) { // fn が error を受け取るように変更
alive1 := alive // readLoop の alive 変数の現在の値をキャプチャ
if err != nil { // ボディの読み込みがエラーで終了した場合
alive1 = false // コネクションは再利用不可
}
if alive1 && !pc.t.putIdleConn(pc) { // コネクションが健全で、アイドルプールに入れられなかった場合
alive1 = false // やはり再利用不可
}
if !alive1 || pc.isBroken() { // コネクションが再利用不可または壊れている場合
pc.close() // コネクションを閉じる
}
waitForBodyRead <- alive1 // コネクションの健全性情報を readLoop に送信
}
}
// ... (中略) ...
// Wait for the just-returned response body to be fully consumed
// before we race and peek on the underlying bufio reader.
if waitForBodyRead != nil {
alive = <-waitForBodyRead // コネクションの健全性情報を受け取る
}
// ... (後略) ...
resp.Body.(*bodyEOFSignal).fn = func(err error)
:bodyEOFSignal
の変更に合わせて、コールバック関数がerror
引数を受け取るようになりました。alive1 := alive
:readLoop
のスコープにあるalive
変数の現在の値をalive1
にコピーします。これは、コールバックが実行される時点でのコネクションの「生きてる」状態を判断するためです。if err != nil { alive1 = false }
: ボディの読み込みがエラー(例: タイムアウト)で終了した場合、そのコネクションは再利用すべきではないため、alive1
をfalse
に設定します。if alive1 && !pc.t.putIdleConn(pc) { alive1 = false }
: コネクションがまだ「生きていて」、かつアイドルプールに正常に入れられなかった場合(例: プールが満杯)、そのコネクションは再利用できないと判断し、alive1
をfalse
に設定します。if !alive1 || pc.isBroken() { pc.close() }
: 上記の条件でalive1
がfalse
になった場合、またはコネクションが壊れていると判断された場合、pc.close()
を呼び出してコネクションを閉じます。これにより、問題のあるコネクションがアイドルプールに残ることを防ぎ、リソースリークやハングアップを回避します。waitForBodyRead <- alive1
:bodyEOFSignal
のコールバックが完了した際に、コネクションがまだ再利用可能であるかどうかの最終的な情報(alive1
)をreadLoop
に送信します。alive = <-waitForBodyRead
:readLoop
は、レスポンスボディの読み込みが完了するのを待つだけでなく、その結果としてコネクションが再利用可能であるかどうかの情報を受け取ります。このalive
の値に基づいて、次のリクエストでこのコネクションを再利用するか、それとも閉じるかを決定します。
これらの変更により、net/http
クライアントは、レスポンスボディの読み込み中に発生するエラーや、コネクションの再利用に関する問題をより適切に処理できるようになり、全体的な堅牢性と信頼性が向上します。
src/pkg/net/http/server.go
の変更
loggingConn
の追加
// loggingConn is used for debugging.
type loggingConn struct {
name string
net.Conn
}
var (
uniqNameMu sync.Mutex
uniqNameNext = make(map[string]int)
)
func newLoggingConn(baseName string, c net.Conn) net.Conn {
uniqNameMu.Lock()
defer uniqNameMu.Unlock()
uniqNameNext[baseName]++
return &loggingConn{
name: fmt.Sprintf("%s-%d", baseName, uniqNameNext[baseName]),
Conn: c,
}
}
func (c *loggingConn) Write(p []byte) (n int, err error) {
log.Printf("%s.Write(%d) = ....", c.name, len(p))
n, err = c.Conn.Write(p)
log.Printf("%s.Write(%d) = %d, %v", c.name, len(p), n, err)
return
}
func (c *loggingConn) Read(p []byte) (n int, err error) {
log.Printf("%s.Read(%d) = ....", c.name, len(p))
n, err = c.Conn.Read(p)
log.Printf("%s.Read(%d) = %d, %v", c.name, len(p), n, err)
return
}
func (c *loggingConn) Close() (err error) {
log.Printf("%s.Close() = ...", c.name)
err = c.Conn.Close()
log.Printf("%s.Close() = %v", c.name, err)
return
}
loggingConn
はnet.Conn
を埋め込み、Read
,Write
,Close
メソッドをオーバーライドしています。- これらのメソッドは、実際のI/O操作の前後でログメッセージを出力します。これにより、コネクション上でのデータの送受信やクローズのタイミングを詳細に追跡できます。
newLoggingConn
は、一意の名前を生成してloggingConn
インスタンスを作成します。
Server.newConn
での loggingConn
の利用
// ... (前略) ...
const debugServerConnections = false // デバッグフラグ
// Create new connection from rwc.
func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) {
c = new(conn)
c.remoteAddr = rwc.RemoteAddr().String()
c.server = srv
c.rwc = rwc
if debugServerConnections { // フラグが true の場合のみ有効
c.rwc = newLoggingConn("server", c.rwc)
}
c.body = make([]byte, sniffLen)
c.lr = io.LimitReader(c.rwc, noLimit).(*io.LimitedReader) // c.rwc を使用
br := bufio.NewReader(c.lr)
bw := bufio.NewWriter(c.rwc) // c.rwc を使用
c.buf = bufio.NewReadWriter(br, bw)
return c, nil
}
// ... (後略) ...
debugServerConnections
という定数フラグが導入されました。このフラグがtrue
に設定されている場合、Server.newConn
が新しいコネクションを作成する際に、そのnet.Conn
オブジェクトをloggingConn
でラップします。- これにより、サーバーサイドのHTTPコネクションの挙動を詳細にログ出力できるようになり、デバッグや問題の診断に役立ちます。
これらの変更は、Goの net/http
パッケージの堅牢性を高め、特に並行処理における潜在的な問題を解決するための重要なステップです。
関連リンク
- Go Issue 4191:
net/http
: infinite Get timeout - https://github.com/golang/go/issues/4191 - Go Issue 4434:
net/http
: Transport race condition with bodyEOFSignal - https://github.com/golang/go/issues/4434 - Go CL 6851061:
net/http
: fix Transport races & deadlocks - https://golang.org/cl/6851061 (Gerrit Code Review)
参考にした情報源リンク
- Go言語の公式ドキュメント (
net/http
パッケージ): https://pkg.go.dev/net/http - Go言語の並行処理に関する公式ドキュメントやチュートリアル
- Go言語の
sync
パッケージに関するドキュメント: https://pkg.go.dev/sync - Go言語の
sync/atomic
パッケージに関するドキュメント: https://pkg.go.dev/sync/atomic - HTTP/1.1 Persistent Connections (Keep-Alive) に関するRFC (RFC 2616, Section 8.1.2): https://www.rfc-editor.org/rfc/rfc2616#section-8.1.2
- 競合状態とデッドロックに関する一般的なプログラミングの概念
- ポーリングとI/O多重化に関するオペレーティングシステムの概念