[インデックス 11942] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http
パッケージにおける sendfile
テストの競合状態(race condition)を修正するものです。具体的には、HTTPクライアントが最初のリクエストのレスポンスボディを完全に読み込む前に、次のリクエスト(/quit
)を送信してしまうことで発生するテストの不安定性を解消します。
コミット
commit 9578839d60fb0d49130d6689091573aa390f85a0
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Feb 16 09:27:26 2012 +1100
net/http: fix race in sendfile test
Whoops. Consume the body of the first request
before making the subsequent /quit request.
R=golang-dev, untheoretic
CC=golang-dev
https://golang.org/cl/5674054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9578839d60fb0d49130d6689091573aa390f85a0
元コミット内容
このコミットの元のメッセージは以下の通りです。
net/http: fix race in sendfile test
Whoops. Consume the body of the first request
before making the subsequent /quit request.
これは、net/http
パッケージの sendfile
テストにおける競合状態を修正するものであり、その原因が最初のHTTPリクエストのレスポンスボディを適切に消費する前に次のリクエストが発行されることにあると説明しています。
変更の背景
この変更は、net/http
パッケージ内の TestLinuxSendfile
というテストが、特定の条件下で不安定になる問題を解決するために行われました。不安定性の原因は、テスト内でHTTPリクエストが連続して行われる際に発生する競合状態にありました。
具体的には、テストはまずHTTPサーバーに対して最初のリクエストを送信し、その後、サーバープロセスを終了させるための /quit
リクエストを送信します。問題は、最初のHTTPリクエストに対するレスポンスボディが完全に読み込まれる前に、/quit
リクエストが送信されてしまう可能性があったことです。
HTTPプロトコルでは、クライアントはサーバーからのレスポンスボディを完全に読み込むか、接続を閉じる必要があります。もしレスポンスボディが完全に読み込まれないままクライアントが次のリクエストを同じ接続で送信しようとしたり、接続を閉じようとしたりすると、サーバー側で予期せぬ動作やエラーが発生する可能性があります。特に、サーバーがまだレスポンスボディの送信を完了していない場合、クライアントが接続を閉じると、サーバーは「broken pipe」などのエラーを受け取る可能性があります。
この競合状態は、テストが実行される環境のタイミングやリソースの利用状況によって顕在化したりしなかったりするため、テストが時々失敗する「flaky test」として現れていました。このような不安定なテストは、CI/CDパイプラインの信頼性を損ない、開発者が実際のバグとテストの不安定性を区別するのを困難にします。そのため、テストの信頼性を向上させるために、この競合状態を修正する必要がありました。
前提知識の解説
HTTPのレスポンスボディの消費
HTTP/1.1では、クライアントがサーバーからレスポンスを受け取った際、そのレスポンスにボディが含まれている場合(例: Content-Length
ヘッダーがある、または Transfer-Encoding: chunked
が指定されている場合)、クライアントはそのボディを完全に読み込む責任があります。これは、TCP接続の再利用(Keep-Alive)を適切に行うために重要です。
もしクライアントがレスポンスボディを完全に読み込まないまま次のリクエストを同じTCP接続で送信しようとすると、サーバーは前のレスポンスボディの残りを送信しようとし続けるため、プロトコル違反やデータ混同が発生する可能性があります。また、クライアントがボディを読み込まずに接続を閉じてしまうと、サーバーはまだ送信中のデータを破棄せざるを得なくなり、エラーログが出力されたり、リソースが適切に解放されなかったりする原因となります。
Go言語の net/http
パッケージでは、http.Response
オブジェクトの Body
フィールドは io.ReadCloser
インターフェースを実装しており、レスポンスボディを読み込むためのストリームとして機能します。このストリームは、ボディの読み込みが完了した後、またはエラーが発生した場合には必ず Close()
メソッドを呼び出して閉じる必要があります。これにより、関連するリソース(特にTCP接続)が適切に解放され、接続の再利用が可能になります。
競合状態 (Race Condition)
競合状態とは、複数の並行に動作するプロセスやスレッドが共有リソースにアクセスする際に、そのアクセス順序によって結果が非決定的に変わってしまう状態を指します。今回のケースでは、HTTPクライアントが最初のレスポンスボディの読み込みと、次のリクエストの送信という2つの操作を並行して(または非常に短い間隔で)行おうとした際に発生しました。
具体的には、Get
関数がHTTPリクエストを送信し、http.Response
を返しますが、この Response
の Body
はまだ読み込まれていない状態です。この Body
の読み込みが完了する前に、次の Get
リクエスト(/quit
)が発行されてしまうと、サーバー側で最初のレスポンスの送信が完了していないにもかかわらず、クライアントが新しいリクエストを送信しようとする、あるいは接続を閉じようとする、といった状況が発生し、テストが失敗する原因となっていました。
ioutil.Discard
と io.Copy
ioutil.Discard
:io.Writer
インターフェースを実装しており、書き込まれたデータをすべて破棄します。つまり、データをどこにも保存せずに読み捨てるための「ブラックホール」のようなものです。io.Copy(dst io.Writer, src io.Reader)
:src
からデータを読み込み、それをdst
に書き込む関数です。src
の終端に達するか、エラーが発生するまで読み書きを続けます。
このコミットでは、io.Copy(ioutil.Discard, res.Body)
を使用して、res.Body
からのデータをすべて読み込み、ioutil.Discard
に書き込むことで、レスポンスボディを完全に消費しています。これにより、ボディの内容はメモリに保持されず、単に読み捨てられるため、リソース効率が良い方法でボディの読み込みを完了させることができます。
技術的詳細
このコミットの技術的な核心は、HTTPクライアントがサーバーからのレスポンスボディを完全に読み込むことの重要性にあります。元のコードでは、最初のHTTPリクエスト (Get(fmt.Sprintf("http://%s/", ln.Addr()))
) の結果として返される http.Response
オブジェクトの Body
フィールドが適切に処理されていませんでした。
net/http
パッケージの Get
関数は、HTTPリクエストを実行し、その結果として *http.Response
と error
を返します。http.Response
の Body
フィールドは io.ReadCloser
型であり、これはレスポンスボディのストリームを表します。このストリームは、サーバーがレスポンスボディの送信を完了するまで開いたままになります。
元のコードでは、Get
の呼び出し後、res
変数にレスポンスが代入されていましたが、その res.Body
からデータを読み込む処理がありませんでした。そのため、サーバーは最初のレスポンスボディの送信を継続しているにもかかわらず、クライアントはすぐに次のリクエスト (Get(fmt.Sprintf("http://%s/quit", ln.Addr()))
) を送信しようとしていました。
この状況は、特にTCPのKeep-Aliveが有効な場合(HTTP/1.1のデフォルト動作)に問題を引き起こします。クライアントがレスポンスボディを完全に読み込まないまま次のリクエストを同じ接続で送信しようとすると、サーバーは前のレスポンスの残りを送信しようとし続けるため、プロトコルレベルでの同期が失われ、競合状態が発生します。結果として、テストが不安定になり、時折失敗する原因となっていました。
修正は、最初の Get
リクエストの後に、io.Copy(ioutil.Discard, res.Body)
を追加することで、この問題を解決しています。
res, err := Get(...)
: 最初のHTTPリクエストを実行し、レスポンスを取得します。_, err = io.Copy(ioutil.Discard, res.Body)
: 取得したレスポンスres
のボディ (res.Body
) をioutil.Discard
にコピーします。ioutil.Discard
は書き込まれたデータをすべて破棄するため、これはレスポンスボディの内容を読み捨てて、ストリームの終端まで読み進めることを意味します。これにより、サーバーが送信したレスポンスボディが完全にクライアントによって消費された状態になります。res.Body.Close()
: レスポンスボディのストリームを閉じます。これは、io.ReadCloser
の規約に従い、関連するリソース(TCP接続など)を適切に解放するために不可欠です。これにより、接続が再利用可能な状態になります。
これらの変更により、最初のHTTPリクエストのレスポンスボディが完全に処理されてから次の /quit
リクエストが送信されることが保証され、テストにおける競合状態が解消されました。
コアとなるコードの変更箇所
変更は src/pkg/net/http/fs_test.go
ファイルの TestLinuxSendfile
関数内で行われています。
--- a/src/pkg/net/http/fs_test.go
+++ b/src/pkg/net/http/fs_test.go
@@ -398,11 +398,15 @@ func TestLinuxSendfile(t *testing.T) {
return
}
- _, err = Get(fmt.Sprintf("http://%s/", ln.Addr()))
+ res, err := Get(fmt.Sprintf("http://%s/", ln.Addr()))
if err != nil {
- t.Errorf("http client error: %v", err)
- return
+ t.Fatalf("http client error: %v", err)
}
+ _, err = io.Copy(ioutil.Discard, res.Body)
+ if err != nil {
+ t.Fatalf("client body read error: %v", err)
+ }
+ res.Body.Close()
// Force child to exit cleanly.
Get(fmt.Sprintf("http://%s/quit", ln.Addr()))
コアとなるコードの解説
変更前は、以下の行で最初のHTTPリクエストが送信されていました。
_, err = Get(fmt.Sprintf("http://%s/", ln.Addr()))
この行は Get
関数からの戻り値である *http.Response
を破棄しており、レスポンスボディを読み込む処理が全くありませんでした。
変更後は、以下の3行が追加・修正されました。
-
res, err := Get(fmt.Sprintf("http://%s/", ln.Addr()))
:Get
関数からの*http.Response
オブジェクトをres
変数に明示的に受け取るように変更されました。これにより、レスポンスボディ (res.Body
) にアクセスできるようになります。- エラーハンドリングも
t.Errorf
からt.Fatalf
に変更され、テストが致命的なエラーで即座に終了するように厳格化されています。
-
_, err = io.Copy(ioutil.Discard, res.Body)
:io.Copy
関数を使用して、res.Body
からのデータをioutil.Discard
にコピーしています。ioutil.Discard
は書き込まれたデータをすべて破棄するio.Writer
です。この操作により、res.Body
ストリームの終端までデータが読み込まれ、レスポンスボディが完全に消費されます。- ここでもエラーハンドリングが追加され、ボディの読み込み中にエラーが発生した場合にテストが失敗するようにしています。
-
res.Body.Close()
:res.Body
はio.ReadCloser
インターフェースを実装しているため、読み込みが完了した後には必ずClose()
メソッドを呼び出す必要があります。- この呼び出しにより、HTTP接続に関連するリソースが適切に解放され、接続が再利用可能な状態になります。これは、特にHTTP Keep-Aliveが有効な場合に重要です。
これらの変更により、最初のHTTPリクエストのレスポンスボディが完全に読み込まれ、関連するリソースが解放されてから、次の /quit
リクエストが送信されることが保証されます。これにより、テストの競合状態が解消され、テストの信頼性が向上しました。
関連リンク
- Go言語の
net/http
パッケージに関する公式ドキュメント: https://pkg.go.dev/net/http - Go言語の
io
パッケージに関する公式ドキュメント: https://pkg.go.dev/io - Go言語の
ioutil
パッケージに関する公式ドキュメント (Go 1.16以降はio/ioutil
は非推奨となり、io
およびos
パッケージに機能が移行されていますが、このコミット時点では有効です): https://pkg.go.dev/io/ioutil
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/ (コミットメッセージにある
https://golang.org/cl/5674054
は、このGerritシステムへのリンクです) - HTTP/1.1 の仕様 (RFC 2616 - Section 8.1.2.2 Persistent Connections): https://www.rfc-editor.org/rfc/rfc2616#section-8.1.2.2
- Go言語における
io.Copy
とioutil.Discard
の利用例に関する一般的な情報源 (例: ブログ記事、チュートリアルなど) - 競合状態に関する一般的なプログラミングの概念に関する情報源 (例: Wikipedia, プログラミング教本など)
[インデックス 11942] ファイルの概要
このコミットは、Go言語の標準ライブラリ net/http
パッケージにおける sendfile
テストの競合状態(race condition)を修正するものです。具体的には、HTTPクライアントが最初のリクエストのレスポンスボディを完全に読み込む前に、次のリクエスト(/quit
)を送信してしまうことで発生するテストの不安定性を解消します。
コミット
commit 9578839d60fb0d49130d6689091573aa390f85a0
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Thu Feb 16 09:27:26 2012 +1100
net/http: fix race in sendfile test
Whoops. Consume the body of the first request
before making the subsequent /quit request.
R=golang-dev, untheoretic
CC=golang-dev
https://golang.org/cl/5674054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9578839d60fb0d49130d6689091573aa390f85a0
元コミット内容
このコミットの元のメッセージは以下の通りです。
net/http: fix race in sendfile test
Whoops. Consume the body of the first request
before making the subsequent /quit request.
これは、net/http
パッケージの sendfile
テストにおける競合状態を修正するものであり、その原因が最初のHTTPリクエストのレスポンスボディを適切に消費する前に次のリクエストが発行されることにあると説明しています。
変更の背景
この変更は、Go言語の標準ライブラリ net/http
パッケージ内の TestLinuxSendfile
というテストが、特定の条件下で不安定になる問題を解決するために行われました。不安定性の原因は、テスト内でHTTPリクエストが連続して行われる際に発生する競合状態にありました。
具体的には、テストはまずHTTPサーバーに対して最初のリクエストを送信し、その後、サーバープロセスを終了させるための /quit
リクエストを送信します。問題は、最初のHTTPリクエストに対するレスポンスボディが完全に読み込まれる前に、/quit
リクエストが送信されてしまう可能性があったことです。
HTTPプロトコルでは、クライアントはサーバーからのレスポンスボディを完全に読み込むか、接続を閉じる必要があります。もしレスポンスボディが完全に読み込まれないままクライアントが次のリクエストを同じ接続で送信しようとしたり、接続を閉じようとしたりすると、サーバー側で予期せぬ動作やエラーが発生する可能性があります。特に、サーバーがまだレスポンスボディの送信を完了していない場合、クライアントが接続を閉じると、サーバーは「broken pipe」などのエラーを受け取る可能性があります。
この競合状態は、テストが実行される環境のタイミングやリソースの利用状況によって顕在化したりしなかったりするため、テストが時々失敗する「flaky test」として現れていました。このような不安定なテストは、CI/CDパイプラインの信頼性を損ない、開発者が実際のバグとテストの不安定性を区別するのを困難にします。そのため、テストの信頼性を向上させるために、この競合状態を修正する必要がありました。
ウェブ検索の結果によると、net/http
パッケージ自体には、sendfile
に直接関連する既知の競合状態は広く報告されていませんが、一般的なHTTPハンドラにおける共有状態の不適切な同期や、トランスポート層での特定の条件下での競合状態は過去に報告されています。このコミットは、sendfile
のテストという特定の文脈で、HTTPプロトコルの基本的な要件(レスポンスボディの完全な消費)が満たされていないことによる競合状態を修正したものです。
前提知識の解説
HTTPのレスポンスボディの消費
HTTP/1.1では、クライアントがサーバーからレスポンスを受け取った際、そのレスポンスにボディが含まれている場合(例: Content-Length
ヘッダーがある、または Transfer-Encoding: chunked
が指定されている場合)、クライアントはそのボディを完全に読み込む責任があります。これは、TCP接続の再利用(Keep-Alive)を適切に行うために重要です。
もしクライアントがレスポンスボディを完全に読み込まないまま次のリクエストを同じTCP接続で送信しようとすると、サーバーは前のレスポンスボディの残りを送信しようとし続けるため、プロトコル違反やデータ混同が発生する可能性があります。また、クライアントがボディを読み込まずに接続を閉じてしまうと、サーバーはまだ送信中のデータを破棄せざるを得なくなり、エラーログが出力されたり、リソースが適切に解放されなかったりする原因となります。
Go言語の net/http
パッケージでは、http.Response
オブジェクトの Body
フィールドは io.ReadCloser
インターフェースを実装しており、レスポンスボディを読み込むためのストリームとして機能します。このストリームは、ボディの読み込みが完了した後、またはエラーが発生した場合には必ず Close()
メソッドを呼び出して閉じる必要があります。これにより、関連するリソース(特にTCP接続)が適切に解放され、接続の再利用が可能になります。
競合状態 (Race Condition)
競合状態とは、複数の並行に動作するプロセスやスレッドが共有リソースにアクセスする際に、そのアクセス順序によって結果が非決定的に変わってしまう状態を指します。今回のケースでは、HTTPクライアントが最初のレスポンスボディの読み込みと、次のリクエストの送信という2つの操作を並行して(または非常に短い間隔で)行おうとした際に発生しました。
具体的には、Get
関数がHTTPリクエストを送信し、http.Response
を返しますが、この Response
の Body
はまだ読み込まれていない状態です。この Body
の読み込みが完了する前に、次の Get
リクエスト(/quit
)が発行されてしまうと、サーバー側で最初のレスポンスの送信が完了していないにもかかわらず、クライアントが新しいリクエストを送信しようとする、あるいは接続を閉じようとする、といった状況が発生し、テストが失敗する原因となっていました。
ioutil.Discard
と io.Copy
ioutil.Discard
:io.Writer
インターフェースを実装しており、書き込まれたデータをすべて破棄します。つまり、データをどこにも保存せずに読み捨てるための「ブラックホール」のようなものです。これは、読み込んだデータの内容自体には興味がなく、単にストリームを消費したい場合に非常に効率的です。io.Copy(dst io.Writer, src io.Reader)
:src
からデータを読み込み、それをdst
に書き込む関数です。src
の終端に達するか、エラーが発生するまで読み書きを続けます。
このコミットでは、io.Copy(ioutil.Discard, res.Body)
を使用して、res.Body
からのデータをすべて読み込み、ioutil.Discard
に書き込むことで、レスポンスボディを完全に消費しています。これにより、ボディの内容はメモリに保持されず、単に読み捨てられるため、リソース効率が良い方法でボディの読み込みを完了させることができます。
技術的詳細
このコミットの技術的な核心は、HTTPクライアントがサーバーからのレスポンスボディを完全に読み込むことの重要性にあります。元のコードでは、最初のHTTPリクエスト (Get(fmt.Sprintf("http://%s/", ln.Addr()))
) の結果として返される http.Response
オブジェクトの Body
フィールドが適切に処理されていませんでした。
net/http
パッケージの Get
関数は、HTTPリクエストを実行し、その結果として *http.Response
と error
を返します。http.Response
の Body
フィールドは io.ReadCloser
型であり、これはレスポンスボディのストリームを表します。このストリームは、サーバーがレスポンスボディの送信を完了するまで開いたままになります。
元のコードでは、Get
の呼び出し後、res
変数にレスポンスが代入されていましたが、その res.Body
からデータを読み込む処理がありませんでした。そのため、サーバーは最初のレスポンスボディの送信を継続しているにもかかわらず、クライアントはすぐに次のリクエスト (Get(fmt.Sprintf("http://%s/quit", ln.Addr()))
) を送信しようとしていました。
この状況は、特にTCPのKeep-Aliveが有効な場合(HTTP/1.1のデフォルト動作)に問題を引き起こします。クライアントがレスポンスボディを完全に読み込まないまま次のリクエストを同じ接続で送信しようとすると、サーバーは前のレスポンスの残りを送信しようとし続けるため、プロトコルレベルでの同期が失われ、競合状態が発生します。結果として、テストが不安定になり、時折失敗する原因となっていました。
修正は、最初の Get
リクエストの後に、io.Copy(ioutil.Discard, res.Body)
を追加することで、この問題を解決しています。
res, err := Get(...)
: 最初のHTTPリクエストを実行し、レスポンスを取得します。_, err = io.Copy(ioutil.Discard, res.Body)
: 取得したレスポンスres
のボディ (res.Body
) をioutil.Discard
にコピーします。ioutil.Discard
は書き込まれたデータをすべて破棄するため、これはレスポンスボディの内容を読み捨てて、ストリームの終端まで読み進めることを意味します。これにより、サーバーが送信したレスポンスボディが完全にクライアントによって消費された状態になります。res.Body.Close()
: レスポンスボディのストリームを閉じます。これは、io.ReadCloser
の規約に従い、関連するリソース(特にTCP接続)を適切に解放するために不可欠です。これにより、接続が再利用可能な状態になります。
これらの変更により、最初のHTTPリクエストのレスポンスボディが完全に処理されてから次の /quit
リクエストが送信されることが保証され、テストにおける競合状態が解消されました。また、エラーハンドリングも t.Errorf
から t.Fatalf
に変更され、テストが致命的なエラーで即座に終了するように厳格化されています。これは、テストの信頼性をさらに高めるための良いプラクティスです。
コアとなるコードの変更箇所
変更は src/pkg/net/http/fs_test.go
ファイルの TestLinuxSendfile
関数内で行われています。
--- a/src/pkg/net/http/fs_test.go
+++ b/src/pkg/net/http/fs_test.go
@@ -398,11 +398,15 @@ func TestLinuxSendfile(t *testing.T) {
return
}
- _, err = Get(fmt.Sprintf("http://%s/", ln.Addr()))
+ res, err := Get(fmt.Sprintf("http://%s/", ln.Addr()))
if err != nil {
- t.Errorf("http client error: %v", err)
- return
+ t.Fatalf("http client error: %v", err)
}
+ _, err = io.Copy(ioutil.Discard, res.Body)
+ if err != nil {
+ t.Fatalf("client body read error: %v", err)
+ }
+ res.Body.Close()
// Force child to exit cleanly.
Get(fmt.Sprintf("http://%s/quit", ln.Addr()))
コアとなるコードの解説
変更前は、以下の行で最初のHTTPリクエストが送信されていました。
_, err = Get(fmt.Sprintf("http://%s/", ln.Addr()))
この行は Get
関数からの戻り値である *http.Response
を破棄しており、レスポンスボディを読み込む処理が全くありませんでした。
変更後は、以下の3行が追加・修正されました。
-
res, err := Get(fmt.Sprintf("http://%s/", ln.Addr()))
:Get
関数からの*http.Response
オブジェクトをres
変数に明示的に受け取るように変更されました。これにより、レスポンスボディ (res.Body
) にアクセスできるようになります。- エラーハンドリングも
t.Errorf
からt.Fatalf
に変更され、テストが致命的なエラーで即座に終了するように厳格化されています。t.Fatalf
はエラーが発生した場合にテストを即座に終了させるため、後続の処理が不正な状態で行われることを防ぎます。
-
_, err = io.Copy(ioutil.Discard, res.Body)
:io.Copy
関数を使用して、res.Body
からのデータをioutil.Discard
にコピーしています。ioutil.Discard
は書き込まれたデータをすべて破棄するio.Writer
です。この操作により、res.Body
ストリームの終端までデータが読み込まれ、レスポンスボディが完全に消費されます。これは、HTTPプロトコルにおいて、クライアントがレスポンスボディを完全に読み込むという重要な要件を満たすためのものです。- ここでもエラーハンドリングが追加され、ボディの読み込み中にエラーが発生した場合にテストが失敗するようにしています。
-
res.Body.Close()
:res.Body
はio.ReadCloser
インターフェースを実装しているため、読み込みが完了した後には必ずClose()
メソッドを呼び出す必要があります。- この呼び出しにより、HTTP接続に関連するリソース(特にTCP接続)が適切に解放され、接続が再利用可能な状態になります。これは、特にHTTP Keep-Aliveが有効な場合に、接続リークを防ぎ、効率的なリソース利用を促進するために不可欠です。
これらの変更により、最初のHTTPリクエストのレスポンスボディが完全に読み込まれ、関連するリソースが解放されてから、次の /quit
リクエストが送信されることが保証されます。これにより、テストの競合状態が解消され、テストの信頼性が向上しました。
関連リンク
- Go言語の
net/http
パッケージに関する公式ドキュメント: https://pkg.go.dev/net/http - Go言語の
io
パッケージに関する公式ドキュメント: https://pkg.go.dev/io - Go言語の
ioutil
パッケージに関する公式ドキュメント (Go 1.16以降はio/ioutil
は非推奨となり、io
およびos
パッケージに機能が移行されていますが、このコミット時点では有効です): https://pkg.go.dev/io/ioutil
参考にした情報源リンク
- Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go.dev/cl/ (コミットメッセージにある
https://golang.org/cl/5674054
は、このGerritシステムへのリンクです) - HTTP/1.1 の仕様 (RFC 2616 - Section 8.1.2.2 Persistent Connections): https://www.rfc-editor.org/rfc/rfc2616#section-8.1.2.2
- Go net/http sendfile race conditionに関するウェブ検索結果 (Google Search)
- https://github.com/golang/go/issues/41600 (Transport race condition by Content-Length == 0 response)
- https://github.com/golang/go/issues/36819 (graceful shutdown race condition)
- https://stackoverflow.com/questions/tagged/go+race-condition (Go言語における競合状態に関する一般的な議論)