Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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 構造体の修正と、persistConnreadLoop におけるコネクションの再利用ロジックの改善。sync/atomic の使用を削除し、sync.Mutex を用いたより堅牢な同期メカニズムを導入しています。bodyEOFSignalfn コールバックがエラー情報を受け取るように変更され、コネクションの健全性判断に利用されます。
  • src/pkg/net/http/server.go: デバッグ目的の loggingConn の追加と、ServernewConn メソッドでの loggingConn の利用。これは、サーバーサイドのコネクションの挙動を詳細にログ出力するためのもので、問題の診断に役立ちます。
  • src/pkg/net/http/export_test.go: NewLoggingConn 関数をエクスポートし、テストから loggingConn を利用できるようにしています。
  • src/pkg/net/http/transport_test.go: TestIssue4191_InfiniteGetTimeoutTestIssue4191_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 4434: net/http: Transport race condition with bodyEOFSignal
    • このIssueは、bodyEOFSignal という内部構造体に関連する競合状態を指摘しています。bodyEOFSignal は、HTTPレスポンスボディの読み込みが完了した際に特定のコールバック関数を実行するために使用されますが、複数のゴルーチンが同時にボディを読み込もうとしたり、コネクションを閉じようとしたりする際に、不適切な同期が原因で競合状態が発生し、パニックやデッドロックを引き起こす可能性がありました。

これらの問題は、GoアプリケーションがHTTP通信を行う際の信頼性と安定性を著しく損なうものであり、特に高負荷な環境や長時間の接続を維持するアプリケーションでは、サービス停止につながる可能性がありました。そのため、これらの根本的な原因を特定し、堅牢な同期メカニズムを導入することが急務でした。

コミットメッセージには、Dustin Sallings氏が「最も苛立たしいバグ」と表現し、再現ケースを提供したことが記されており、この問題の深刻さが伺えます。また、Dave Cheney氏とDmitry Vyukov氏といったGoコミュニティの著名な開発者がデバッグと修正に協力したことからも、その複雑さと重要性がうかがえます。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびネットワークプログラミングに関する前提知識が必要です。

  1. Go言語の並行処理 (Concurrency in Go)

    • ゴルーチン (Goroutines): Go言語における軽量なスレッドのようなもので、数千、数万といった単位で同時に実行できます。Goの並行処理の基本単位です。
    • チャネル (Channels): ゴルーチン間で安全にデータを送受信するための通信メカニズムです。チャネルは、データの受け渡しを通じてゴルーチン間の同期も行います。
    • sync パッケージ:
      • sync.Mutex: 相互排他ロックを提供し、共有リソースへのアクセスを一度に一つのゴルーチンに制限することで、競合状態を防ぎます。Lock() でロックを取得し、Unlock() でロックを解放します。
      • sync.Once: 特定の関数が一度だけ実行されることを保証します。初期化処理などでよく使われます。
      • sync/atomic パッケージ: 低レベルなアトミック操作(不可分操作)を提供します。これにより、ロックを使用せずに特定の変数の読み書きを安全に行うことができますが、複雑なロジックには向かず、誤用するとデッドロックや競合状態を引き起こす可能性があります。
  2. 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を提供し、効率的な読み書きを可能にします。
  3. 競合状態 (Race Conditions) とデッドロック (Deadlocks)

    • 競合状態: 複数のゴルーチンが共有リソースに同時にアクセスし、そのアクセス順序によってプログラムの最終結果が非決定的に変わってしまう状態です。予期せぬ動作やデータ破損の原因となります。
    • デッドロック: 複数のゴルーチンが互いに相手が保持しているリソースの解放を待ち続け、結果としてどのゴルーチンも処理を進められなくなる状態です。プログラムが完全に停止してしまいます。
  4. ポーリング (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 := aliveif err != nil { alive1 = false } を追加し、ボディの読み込みがエラーで終了した場合に alive フラグを false に設定するようにしました。これにより、エラーが発生したコネクションはアイドルプールに再利用されなくなります。
  • waitForBodyRead <- truewaitForBodyRead <- alive1 に変更されました。これは、ボディの読み込みが完了した際に、コネクションがまだ再利用可能であるかどうかの情報(alive1)を readLoop に伝えるためです。
  • alive = <-waitForBodyRead の行が追加され、readLoop がボディの読み込み完了を待つだけでなく、その結果としてコネクションが再利用可能であるかどうかの情報を受け取るようになりました。

目的: この修正は、特にIssue 4191で報告された「無限Getタイムアウト」の問題に対処します。以前は、レスポンスボディの読み込みが完了しない限り、readLoop はコネクションがアイドル状態になったと判断せず、結果としてコネクションが閉じられずにリソースリークやハングアップが発生していました。新しいロジックでは、ボディの読み込みがエラーで終了した場合(例: タイムアウト)、コネクションが「死んでいる」と判断し、アイドルプールに再利用しないようにします。これにより、問題のあるコネクションが再利用されることを防ぎ、リソースが適切に解放されるようになります。

3. デバッグ用 loggingConn の追加

src/pkg/net/http/server.gologgingConn という新しい型が追加されました。これは net.Conn インターフェースをラップし、Read, Write, Close メソッドが呼び出されるたびに詳細なログを出力します。

目的: この loggingConn は、net/http パッケージ内部のコネクションの挙動を詳細に追跡するためのデバッグツールとして導入されました。特に、競合状態やデッドロックのような複雑な並行処理の問題を診断する際に、コネクションのライフサイクルやデータフローを可視化するのに非常に役立ちます。debugServerConnections フラグを true に設定することで、サーバー側のすべてのコネクションにこのロギングが適用されます。テストコードでも NewLoggingConn を使用してクライアント側のコネクションのデバッグに利用されています。

4. 新しいテストケース

transport_test.go に追加された TestIssue4191_InfiniteGetTimeoutTestIssue4191_InfiniteGetToPutTimeout は、この修正の有効性を検証するための重要なテストです。これらは、サーバーが無限にデータを送信するシナリオをシミュレートし、クライアントがタイムアウトすることなく適切にコネクションを処理できることを確認します。

目的: これらのテストは、以前のバグが再現しないことを保証し、将来のリグレッションを防ぐためのものです。特に、io.Copy(ioutil.Discard, sres.Body) がエラーを返すことを期待しており、これはタイムアウトによってボディの読み込みが中断されることを意味します。

全体として、このコミットは net/http パッケージのコネクション管理における並行処理の堅牢性を大幅に向上させ、HTTPクライアントの信頼性を高めるための重要な修正です。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は、主に以下の3つのファイルに集中しています。

  1. src/pkg/net/http/transport.go:

    • bodyEOFSignal 構造体の定義と、その Read, Close, condfn メソッドの実装が大幅に変更されています。
      • isClosed (atomic bool) と sync.Once の使用が削除され、sync.Mutexclosed (bool), rerr (error) が導入されました。
      • fn コールバックのシグネチャが func() から func(error) に変更されました。
    • persistConn.readLoop メソッド内の resp.Body.(*bodyEOFSignal).fn のコールバックロジックが修正され、エラーハンドリングと alive フラグの更新が改善されました。
      • waitForBodyRead チャネルへの送信値が true から alive1 (コネクションの健全性を示すブール値) に変更されました。
      • readLoopwaitForBodyRead から alive の値を受け取るようになりました。
  2. src/pkg/net/http/server.go:

    • loggingConn 構造体と、その newLoggingConn, Write, Read, Close メソッドが追加されました。
    • Server.newConn メソッド内で debugServerConnections フラグが true の場合に newLoggingConn を使用してコネクションをラップするロジックが追加されました。
  3. src/pkg/net/http/transport_test.go:

    • TestIssue4191_InfiniteGetTimeoutTestIssue4191_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/atomicsync.Once の組み合わせに代わり、muclosed, 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.fnnil でない場合のみ実行されます。
    • err == io.EOF の場合、errnil に設定します。これは、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 }: ボディの読み込みがエラー(例: タイムアウト)で終了した場合、そのコネクションは再利用すべきではないため、alive1false に設定します。
  • if alive1 && !pc.t.putIdleConn(pc) { alive1 = false }: コネクションがまだ「生きていて」、かつアイドルプールに正常に入れられなかった場合(例: プールが満杯)、そのコネクションは再利用できないと判断し、alive1false に設定します。
  • if !alive1 || pc.isBroken() { pc.close() }: 上記の条件で alive1false になった場合、またはコネクションが壊れていると判断された場合、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
}
  • loggingConnnet.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言語の公式ドキュメント (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 構造体の修正と、persistConnreadLoop におけるコネクションの再利用ロジックの改善。sync/atomic の使用を削除し、sync.Mutex を用いたより堅牢な同期メカニズムを導入しています。bodyEOFSignalfn コールバックがエラー情報を受け取るように変更され、コネクションの健全性判断に利用されます。
  • src/pkg/net/http/server.go: デバッグ目的の loggingConn の追加と、ServernewConn メソッドでの loggingConn の利用。これは、サーバーサイドのコネクションの挙動を詳細にログ出力するためのもので、問題の診断に役立ちます。
  • src/pkg/net/http/export_test.go: NewLoggingConn 関数をエクスポートし、テストから loggingConn を利用できるようにしています。
  • src/pkg/net/http/transport_test.go: TestIssue4191_InfiniteGetTimeoutTestIssue4191_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 4434: net/http: Transport race condition with bodyEOFSignal
    • このIssueは、bodyEOFSignal という内部構造体に関連する競合状態を指摘しています。bodyEOFSignal は、HTTPレスポンスボディの読み込みが完了した際に特定のコールバック関数を実行するために使用されますが、複数のゴルーチンが同時にボディを読み込もうとしたり、コネクションを閉じようとしたりする際に、不適切な同期が原因で競合状態が発生し、パニックやデッドロックを引き起こす可能性がありました。

これらの問題は、GoアプリケーションがHTTP通信を行う際の信頼性と安定性を著しく損なうものであり、特に高負荷な環境や長時間の接続を維持するアプリケーションでは、サービス停止につながる可能性がありました。そのため、これらの根本的な原因を特定し、堅牢な同期メカニズムを導入することが急務でした。

コミットメッセージには、Dustin Sallings氏が「最も苛立たしいバグ」と表現し、再現ケースを提供したことが記されており、この問題の深刻さが伺えます。また、Dave Cheney氏とDmitry Vyukov氏といったGoコミュニティの著名な開発者がデバッグと修正に協力したことからも、その複雑さと重要性がうかがえます。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびネットワークプログラミングに関する前提知識が必要です。

  1. Go言語の並行処理 (Concurrency in Go)

    • ゴルーチン (Goroutines): Go言語における軽量なスレッドのようなもので、数千、数万といった単位で同時に実行できます。Goの並行処理の基本単位です。
    • チャネル (Channels): ゴルーチン間で安全にデータを送受信するための通信メカニズムです。チャネルは、データの受け渡しを通じてゴルーチン間の同期も行います。
    • sync パッケージ:
      • sync.Mutex: 相互排他ロックを提供し、共有リソースへのアクセスを一度に一つのゴルーチンに制限することで、競合状態を防ぎます。Lock() でロックを取得し、Unlock() でロックを解放します。
      • sync.Once: 特定の関数が一度だけ実行されることを保証します。初期化処理などでよく使われます。
      • sync/atomic パッケージ: 低レベルなアトミック操作(不可分操作)を提供します。これにより、ロックを使用せずに特定の変数の読み書きを安全に行うことができますが、複雑なロジックには向かず、誤用するとデッドロックや競合状態を引き起こす可能性があります。
  2. 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を提供し、効率的な読み書きを可能にします。
  3. 競合状態 (Race Conditions) とデッドロック (Deadlocks)

    • 競合状態: 複数のゴルーチンが共有リソースに同時にアクセスし、そのアクセス順序によってプログラムの最終結果が非決定的に変わってしまう状態です。予期せぬ動作やデータ破損の原因となります。
    • デッドロック: 複数のゴルーチンが互いに相手が保持しているリソースの解放を待ち続け、結果としてどのゴルーチンも処理を進められなくなる状態です。プログラムが完全に停止してしまいます。
  4. ポーリング (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 := aliveif err != nil { alive1 = false } を追加し、ボディの読み込みがエラーで終了した場合に alive フラグを false に設定するようにしました。これにより、エラーが発生したコネクションはアイドルプールに再利用されなくなります。
  • waitForBodyRead <- truewaitForBodyRead <- alive1 に変更されました。これは、ボディの読み込みが完了した際に、コネクションがまだ再利用可能であるかどうかの情報(alive1)を readLoop に伝えるためです。
  • alive = <-waitForBodyRead の行が追加され、readLoop がボディの読み込み完了を待つだけでなく、その結果としてコネクションが再利用可能であるかどうかの情報を受け取るようになりました。

目的: この修正は、特にIssue 4191で報告された「無限Getタイムアウト」の問題に対処します。以前は、レスポンスボディの読み込みが完了しない限り、readLoop はコネクションがアイドル状態になったと判断せず、結果としてコネクションが閉じられずにリソースリークやハングアップが発生していました。新しいロジックでは、ボディの読み込みがエラーで終了した場合(例: タイムアウト)、コネクションが「死んでいる」と判断し、アイドルプールに再利用しないようにします。これにより、問題のあるコネクションが再利用されることを防ぎ、リソースが適切に解放されるようになります。

3. デバッグ用 loggingConn の追加

src/pkg/net/http/server.gologgingConn という新しい型が追加されました。これは net.Conn インターフェースをラップし、Read, Write, Close メソッドが呼び出されるたびに詳細なログを出力します。

目的: この loggingConn は、net/http パッケージ内部のコネクションの挙動を詳細に追跡するためのデバッグツールとして導入されました。特に、競合状態やデッドロックのような複雑な並行処理の問題を診断する際に、コネクションのライフサイクルやデータフローを可視化するのに非常に役立ちます。debugServerConnections フラグを true に設定することで、サーバー側のすべてのコネクションにこのロギングが適用されます。テストコードでも NewLoggingConn を使用してクライアント側のコネクションのデバッグに利用されています。

4. 新しいテストケース

transport_test.go に追加された TestIssue4191_InfiniteGetTimeoutTestIssue4191_InfiniteGetToPutTimeout は、この修正の有効性を検証するための重要なテストです。これらは、サーバーが無限にデータを送信するシナリオをシミュレートし、クライアントがタイムアウトすることなく適切にコネクションを処理できることを確認します。

目的: これらのテストは、以前のバグが再現しないことを保証し、将来のリグレッションを防ぐためのものです。特に、io.Copy(ioutil.Discard, sres.Body) がエラーを返すことを期待しており、これはタイムアウトによってボディの読み込みが中断されることを意味します。

全体として、このコミットは net/http パッケージのコネクション管理における並行処理の堅牢性を大幅に向上させ、HTTPクライアントの信頼性を高めるための重要な修正です。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は、主に以下の3つのファイルに集中しています。

  1. src/pkg/net/http/transport.go:

    • bodyEOFSignal 構造体の定義と、その Read, Close, condfn メソッドの実装が大幅に変更されています。
      • isClosed (atomic bool) と sync.Once の使用が削除され、sync.Mutexclosed (bool), rerr (error) が導入されました。
      • fn コールバックのシグネチャが func() から func(error) に変更されました。
    • persistConn.readLoop メソッド内の resp.Body.(*bodyEOFSignal).fn のコールバックロジックが修正され、エラーハンドリングと alive フラグの更新が改善されました。
      • waitForBodyRead チャネルへの送信値が true から alive1 (コネクションの健全性を示すブール値) に変更されました。
      • readLoopwaitForBodyRead から alive の値を受け取るようになりました。
  2. src/pkg/net/http/server.go:

    • loggingConn 構造体と、その newLoggingConn, Write, Read, Close メソッドが追加されました。
    • Server.newConn メソッド内で debugServerConnections フラグが true の場合に newLoggingConn を使用してコネクションをラップするロジックが追加されました。
  3. src/pkg/net/http/transport_test.go:

    • TestIssue4191_InfiniteGetTimeoutTestIssue4191_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/atomicsync.Once の組み合わせに代わり、muclosed, 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.fnnil でない場合のみ実行されます。
    • err == io.EOF の場合、errnil に設定します。これは、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 }: ボディの読み込みがエラー(例: タイムアウト)で終了した場合、そのコネクションは再利用すべきではないため、alive1false に設定します。
  • if alive1 && !pc.t.putIdleConn(pc) { alive1 = false }: コネクションがまだ「生きていて」、かつアイドルプールに正常に入れられなかった場合(例: プールが満杯)、そのコネクションは再利用できないと判断し、alive1false に設定します。
  • if !alive1 || pc.isBroken() { pc.close() }: 上記の条件で alive1false になった場合、またはコネクションが壊れていると判断された場合、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
}
  • loggingConnnet.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言語の公式ドキュメント (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多重化に関するオペレーティングシステムの概念