[インデックス 12280] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http/httptest
パッケージにおける httptest.Server
の Close
メソッドの挙動を改善するものです。具体的には、Server.Close
が呼び出された際に、そのサーバーで処理中の未完了のリクエストがすべて終了するまで待機するように変更されます。これにより、テスト環境などにおいて、サーバーがシャットダウンされる前にリクエストが適切に処理されることが保証され、競合状態やテストの不安定性を解消する目的があります。
コミット
commit 8f0bfc5a29ab942af5b8dd1caf143383a90c2170
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Feb 29 12:18:26 2012 -0800
net/http/httptest: make Server.Close wait for outstanding requests to finish
Might fix issue 3050
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5708066
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8f0bfc5a29ab942af5b8dd1caf143383a90c2170
元コミット内容
net/http/httptest: make Server.Close wait for outstanding requests to finish
Might fix issue 3050
変更の背景
この変更の背景には、net/http/httptest
パッケージの Server
が、テストシナリオにおいて予期せぬ挙動を示す可能性があったという問題があります。以前の Server.Close
メソッドは、リスナーを閉じるだけで、その時点で処理中のHTTPリクエストが完了するのを待機しませんでした。
これにより、以下のような問題が発生する可能性がありました。
- 競合状態 (Race Condition): テストコードが
httptest.Server
を起動し、リクエストを送信した後、すぐにServer.Close()
を呼び出すようなシナリオで、リクエストの処理が完了する前にサーバーがシャットダウンされてしまう可能性がありました。これにより、リクエストが途中で中断されたり、テストが不安定になったりする原因となります。 - リソースリークの可能性: 未完了のリクエストに関連するリソースが適切に解放されないままサーバーが終了してしまうことで、リソースリークやデッドロックのような問題を引き起こす可能性がありました。
- テストの信頼性の低下: テストが非決定的な結果を返す(パスしたり失敗したりする)原因となり、テストの信頼性を損ねていました。
コミットメッセージにある "Might fix issue 3050" については、Go言語の公式リポジトリにおける特定の公開されたIssue 3050は確認できませんでした。これは、内部的なバグトラッキングシステムにおける参照、あるいは当時まだ公開されていなかった問題への言及である可能性があります。しかし、このコミットの目的は、上記のような Server.Close
の非同期的なシャットダウン挙動に起因する問題を解決することにあります。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念と標準ライブラリの知識が必要です。
net/http
パッケージ: Go言語でHTTPクライアントおよびサーバーを実装するための基本的な機能を提供するパッケージです。http.Server
: HTTPサーバーの構成(アドレス、ハンドラ、タイムアウトなど)を定義し、リクエストを処理するための構造体です。http.Handler
インターフェース: HTTPリクエストを処理するためのインターフェースで、ServeHTTP(ResponseWriter, *Request)
メソッドを実装する必要があります。http.ResponseWriter
: HTTPレスポンスを構築するためにハンドラが使用するインターフェースです。http.Request
: クライアントからのHTTPリクエストを表す構造体です。
net/http/httptest
パッケージ: HTTPサーバーのテストを容易にするためのユーティリティを提供するパッケージです。httptest.Server
: テスト目的でHTTPサーバーを簡単に起動・停止できる構造体です。通常、ランダムなポートでリッスンし、テスト中にHTTPリクエストを処理します。Server.Close()
:httptest.Server
をシャットダウンするためのメソッドです。このコミット以前は、未完了のリクエストを待機せずに即座にシャットダウンしていました。
sync
パッケージ: Go言語における並行処理の同期プリミティブを提供するパッケージです。sync.WaitGroup
: 複数のゴルーチンが完了するまで待機するために使用される同期プリミティブです。Add(delta int)
: カウンタにdelta
を加算します。Done()
: カウンタを1減らします。通常はdefer
ステートメントで使用されます。Wait()
: カウンタがゼロになるまでブロックします。
技術的詳細
このコミットは、httptest.Server
の Close
メソッドが未完了のリクエストを待機するようにするために、sync.WaitGroup
を導入しています。
変更の主要なポイントは以下の通りです。
-
sync.WaitGroup
の追加:httptest.Server
構造体にwg sync.WaitGroup
フィールドが追加されました。このwg
は、サーバー上で処理中のHTTPリクエストの数をカウントするために使用されます。type Server struct { // ... wg sync.WaitGroup }
-
ハンドラのラップ (
wrapHandler
メソッドとwaitGroupHandler
構造体):httptest.Server
がリクエストを処理する際に、sync.WaitGroup
のカウンタを増減させるための新しいハンドラが導入されました。waitGroupHandler
構造体は、元のhttp.Handler
をラップし、httptest.Server
への参照 (s
) を保持します。waitGroupHandler
のServeHTTP
メソッドが呼び出されるたびに、h.s.wg.Add(1)
でWaitGroup
のカウンタをインクリメントします。- リクエストの処理が完了するか、パニックが発生した場合でも確実にカウンタをデクリメントするために、
defer h.s.wg.Done()
が使用されます。 - 実際のHTTPリクエスト処理は、ラップされた元のハンドラ
h.h.ServeHTTP(w, r)
に委譲されます。
type waitGroupHandler struct { s *Server h http.Handler // non-nil } func (h *waitGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.s.wg.Add(1) defer h.s.wg.Done() // a defer, in case ServeHTTP below panics h.h.ServeHTTP(w, r) }
-
ハンドラの適用 (
Start
およびStartTLS
メソッド):httptest.Server
のStart
およびStartTLS
メソッド内で、サーバーのConfig.Handler
が新しいwaitGroupHandler
でラップされるようになりました。これにより、すべてのHTTPリクエストがsync.WaitGroup
によって追跡されるようになります。func (s *Server) Start() { // ... s.wrapHandler() // New line go s.Config.Serve(s.Listener) // ... } func (s *Server) StartTLS() { // ... s.wrapHandler() // New line go s.Config.Serve(s.Listener) } func (s *Server) wrapHandler() { h := s.Config.Handler if h == nil { h = http.DefaultServeMux } s.Config.Handler = &waitGroupHandler{ s: s, h: h, } }
-
Server.Close
の待機:Server.Close
メソッドの最後にs.wg.Wait()
が追加されました。これにより、Close
が呼び出されると、sync.WaitGroup
のカウンタがゼロになる(つまり、すべての未完了のリクエストが処理を終える)まで、このメソッドはブロックされるようになります。func (s *Server) Close() { s.Listener.Close() s.wg.Wait() // New line }
この変更により、httptest.Server
はより堅牢になり、テストの信頼性が向上します。特に、テストの終了時にサーバーが適切にクリーンアップされることが保証されるため、テストスイート全体の安定性に寄与します。
コアとなるコードの変更箇所
src/pkg/net/http/httptest/server.go
Server
構造体にwg sync.WaitGroup
フィールドが追加されました。wrapHandler()
メソッドが追加され、Server.Config.Handler
をwaitGroupHandler
でラップするようになりました。Start()
およびStartTLS()
メソッド内でs.wrapHandler()
が呼び出されるようになりました。waitGroupHandler
構造体が追加されました。waitGroupHandler
のServeHTTP
メソッドが実装され、sync.WaitGroup
のAdd(1)
とDone()
を呼び出すようになりました。Server.Close()
メソッドの最後にs.wg.Wait()
が追加されました。
src/pkg/net/http/sniff_test.go
TestSniffWriteSize
テスト関数内で、Get
関数から返されたレスポンスボディを閉じるためにres.Body.Close()
が追加されました。これは、リソースリークを防ぐための一般的なベストプラクティスであり、Server.Close
の変更とは直接関係ありませんが、テストのクリーンアップを改善します。
コアとなるコードの解説
src/pkg/net/http/httptest/server.go
// Server 構造体への wg sync.WaitGroup の追加
type Server struct {
// ...
wg sync.WaitGroup
}
// wrapHandler メソッドの追加
// Server のハンドラを waitGroupHandler でラップし、リクエストの追跡を可能にする
func (s *Server) wrapHandler() {
h := s.Config.Handler
if h == nil {
h = http.DefaultServeMux // ハンドラが設定されていない場合はデフォルトのServeMuxを使用
}
s.Config.Handler = &waitGroupHandler{ // 新しいハンドラでラップ
s: s,
h: h,
}
}
// Start メソッドでの wrapHandler の呼び出し
func (s *Server) Start() {
// ...
s.URL = "http://" + s.Listener.Addr().String()
s.wrapHandler() // ここでハンドラをラップ
go s.Config.Serve(s.Listener) // ラップされたハンドラでサーバーを起動
// ...
}
// StartTLS メソッドでの wrapHandler の呼び出し
func (s *Server) StartTLS() {
// ...
s.URL = "https://" + s.Listener.Addr().String()
s.wrapHandler() // ここでハンドラをラップ
go s.Config.Serve(s.Listener) // ラップされたハンドラでサーバーを起動
}
// waitGroupHandler 構造体の定義
// HTTPリクエストの数をカウントし、Server.Close が待機できるようにする
type waitGroupHandler struct {
s *Server // 関連する httptest.Server へのポインタ
h http.Handler // ラップする元のハンドラ
}
// waitGroupHandler の ServeHTTP メソッドの実装
// 各リクエストの開始時に wg.Add(1) を呼び出し、終了時に wg.Done() を呼び出す
func (h *waitGroupHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.s.wg.Add(1) // リクエスト開始時にカウンタをインクリメント
defer h.s.wg.Done() // リクエスト終了時(パニック時も含む)にカウンタをデクリメント
h.h.ServeHTTP(w, r) // 元のハンドラにリクエスト処理を委譲
}
// Server.Close メソッドの変更
// リスナーを閉じた後、未完了のリクエストがすべて終了するまで待機する
func (s *Server) Close() {
s.Listener.Close() // リスナーを閉じる
s.wg.Wait() // すべてのリクエストが完了するまで待機
}
src/pkg/net/http/sniff_test.go
func TestSniffWriteSize(t *testing.T) {
// ...
defer ts.Close()
for _, size := range []int{0, 1, 200, 600, 999, 1000, 1023, 1024, 512 << 10, 1 << 20} {
res, err := Get(fmt.Sprintf("%s/?size=%d", ts.URL, size))
if err != nil {
t.Fatalf("size %d: %v", size, err)
}
res.Body.Close() // レスポンスボディを閉じることでリソースを解放
}
}
この res.Body.Close()
の追加は、HTTPクライアントがレスポンスボディを読み終えた後に明示的に閉じるという一般的なベストプラクティスに従ったものです。これにより、TCP接続が適切に再利用されたり、リソースがリークしたりするのを防ぎます。これは httptest.Server.Close
の変更とは直接的な依存関係はありませんが、テストの健全性を高めるための関連する改善です。
関連リンク
- Go CL 5708066: https://golang.org/cl/5708066
参考にした情報源リンク
- Go言語
net/http
パッケージドキュメント: https://pkg.go.dev/net/http - Go言語
net/http/httptest
パッケージドキュメント: https://pkg.go.dev/net/http/httptest - Go言語
sync
パッケージドキュメント: https://pkg.go.dev/sync - Go issue 3050に関するWeb検索結果 (直接的な関連は確認できず、内部的な参照の可能性):
- Wailsapp/Wails GitHub Issue #3050: https://github.com/wailsapp/wails/issues/3050
- Red Hat OpenShift Dev Spaces (CRW-3050): https://access.redhat.com/documentation/en-us/red_hat_openshift_dev_spaces/3.1/html/release_notes/known-issues
- Go Runtime Source Code (runtime/proc.go line 3050): https://go.dev/src/runtime/proc.go?m=text#L3050
- Stack Overflow - Docker Networking (dial tcp 172.18.0.6:3050): https://stackoverflow.com/questions/64700000/dial-tcp-172-18-0-63050-connect-connection-refused-in-go-application-in-docker