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

[インデックス 17019] ファイルの概要

このコミットは、Go言語のnetパッケージにおけるWindows環境でのAcceptシステムコールの同時実行に関するバグ修正と、それに関連するテストの追加を扱っています。具体的には、複数のゴルーチンが同時にAcceptを呼び出した際に発生する「fatal error: netpollblock: double wait」というランタイムエラーを解消することを目的としています。

コミット

commit 77f21eea597f374e025497c167caccfd72864e13
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Sun Aug 4 23:31:23 2013 +0400

    net: fix concurrent Accept on windows
    Runtime netpoll supports at most one read waiter
    and at most one write waiter. It's responsibility
    of net package to ensure that. Currently windows
    implementation allows more than one waiter in Accept.
    It leads to "fatal error: netpollblock: double wait".
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12400045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/77f21eea597f374e025497c167caccfd72864e13

元コミット内容

net: fix concurrent Accept on windows
Runtime netpoll supports at most one read waiter
and at most one write waiter. It's responsibility
of net package to ensure that. Currently windows
implementation allows more than one waiter in Accept.
It leads to "fatal error: netpollblock: double wait".

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12400045

変更の背景

Goのランタイムには、ネットワークI/Oを効率的に処理するためのnetpollというメカニズムがあります。netpollは、OSの非同期I/O機能(Linuxのepoll、macOSのkqueue、WindowsのI/O Completion Ports (IOCP) など)を抽象化し、複数のネットワーク操作を単一のスレッドで多重化して処理することを可能にします。

このnetpollの設計上の制約として、「最大で1つの読み込み待機者(read waiter)と最大で1つの書き込み待機者(write waiter)しかサポートしない」というものがあります。これは、特定のファイルディスクリプタ(またはWindowsのソケットハンドル)に対して、同時に複数のゴルーチンが読み込みまたは書き込みを待機することを許容しないということを意味します。

しかし、WindowsにおけるnetパッケージのAccept実装では、このnetpollの制約が適切に守られていませんでした。具体的には、複数のゴルーチンが同時にnet.Listenで作成されたリスナーに対してAcceptを呼び出すと、内部的に同じソケットハンドルに対して複数の読み込み待機が発生する可能性がありました。これがnetpollの「double wait」状態を引き起こし、「fatal error: netpollblock: double wait」というランタイムパニックに繋がっていました。

このコミットは、このWindows特有のAcceptにおける同時実行の問題を解決し、netpollの制約を遵守することで、ランタイムパニックを防ぐことを目的としています。

前提知識の解説

GoのネットワークI/Oとnetpoll

GoのネットワークI/Oは、ユーザーレベルのゴルーチンとOSレベルのスレッドを効率的に連携させることで実現されています。

  • ゴルーチン: Goの軽量スレッド。数百万のゴルーチンを同時に実行できる。
  • M(Machine): OSスレッド。GoランタイムはゴルーチンをMにスケジューリングして実行する。
  • P(Processor): 論理プロセッサ。ゴルーチンをMに割り当てるためのコンテキスト。

ネットワークI/O操作(Read, Write, Acceptなど)は、通常ブロッキング操作です。しかし、Goではこれらの操作をノンブロッキングに扱い、netpollメカニズムを通じて効率的に多重化します。

  1. ゴルーチンがネットワークI/O操作を開始すると、Goランタイムは対応するファイルディスクリプタ(またはソケットハンドル)をnetpollに登録します。
  2. もしI/Oがすぐに完了しない場合(データがまだ来ていない、バッファが満杯など)、ゴルーチンはnetpollによってブロックされ、Pから切り離されます。このゴルーチンは、I/Oが完了するまで待機状態に入ります。
  3. Pは別の実行可能なゴルーチンに切り替わり、CPU時間を有効活用します。
  4. OSがI/Oイベント(データ受信、接続確立など)を通知すると、netpollはそれを検知し、待機していたゴルーチンを再び実行可能状態に戻します。
  5. スケジューラは、そのゴルーチンをPに割り当て、I/O操作を再開させます。

このメカニズムにより、Goは多数の同時接続を効率的に処理できます。しかし、netpollは内部的に各ファイルディスクリプタに対して「読み込み待機は1つまで」「書き込み待機は1つまで」という制約を持っています。これは、OSの非同期I/O APIが通常、特定のI/O操作(読み込み、書き込み)に対して一度に1つの非同期リクエストしか受け付けないことに起因します。複数のリクエストが同時に発行されると、競合状態や未定義の動作を引き起こす可能性があります。

Acceptシステムコール

Acceptは、サーバープログラムがクライアントからの新しい接続を受け入れるために使用するシステムコールです。

  • サーバーはまずListenを呼び出して、特定のIPアドレスとポートで接続を待ち受けるリスナーソケットを作成します。
  • その後、Acceptを呼び出して、新しい接続が来るのを待ちます。
  • Acceptが成功すると、新しい接続を表す新しいソケットディスクリプタ(またはハンドル)が返され、クライアントとの通信に使用されます。

WindowsにおけるI/O Completion Ports (IOCP)

Windowsでは、高性能な非同期I/OのためにI/O Completion Ports (IOCP) が広く利用されます。IOCPは、複数の非同期I/O操作の結果を効率的にキューイングし、複数のスレッドで処理することを可能にします。GoのnetpollのWindows実装は、このIOCPを内部的に利用しています。

AcceptExなどのWindows APIは、IOCPと組み合わせて使用されることが多く、非同期に接続を受け入れることができます。しかし、GoのnetpollがIOCPをどのようにラップしているか、そしてその内部的な状態管理がどのように行われているかが、この問題の核心です。

sync.Mutex

sync.MutexはGoの標準ライブラリsyncパッケージで提供されるミューテックス(相互排他ロック)です。複数のゴルーチンが共有リソースに同時にアクセスする際に、データ競合を防ぐために使用されます。

  • Lock(): ミューテックスをロックします。既にロックされている場合は、ロックが解放されるまでゴルーチンはブロックされます。
  • Unlock(): ミューテックスをアンロックします。

deferキーワードと組み合わせることで、関数の終了時に確実にロックが解放されるようにすることができます。

技術的詳細

このコミットの技術的詳細は、GoランタイムのnetpollnetパッケージのWindows実装における同期の問題に集約されます。

  1. 問題の特定: netpollは、各ファイルディスクリプタ(ソケットハンドル)に対して、読み込み操作と書き込み操作それぞれについて、同時に1つの待機ゴルーチンしか許可しません。しかし、Windowsのnet.Acceptの実装では、複数のゴルーチンが同時にAcceptを呼び出すと、同じソケットハンドルに対して複数の読み込み待機がnetpollに登録されてしまう可能性がありました。
  2. 「double wait」エラー: netpollが同じソケットハンドルに対して複数の読み込み待機を検出すると、内部的な状態が矛盾し、「fatal error: netpollblock: double wait」というパニックが発生します。これは、netpollが想定していない競合状態であり、Goランタイムの安定性を損なうものです。
  3. 原因: src/pkg/net/fd_windows.go内のnetFD.acceptメソッドが、新しい接続を受け入れるためのacceptOpnetpollにサブミットする際に、複数のゴルーチンからの同時呼び出しに対する排他制御が不足していました。
  4. 解決策: netFD.acceptメソッド内で、acceptOpをサブミットする直前にfd.rio.Lock()を呼び出し、defer fd.rio.Unlock()でロックを解放するように変更されました。
    • fd.rionetFD構造体内のフィールドで、おそらく読み込みI/O操作に関連するロックを管理するためのsync.Mutexインスタンスです。
    • このロックにより、一度に1つのゴルーチンだけがacceptOpのサブミット処理を実行できるようになります。これにより、netpollに対して同時に複数の読み込み待機が登録されることがなくなり、netpollの制約が守られるようになります。
  5. テストの追加: 問題が修正されたことを確認するため、src/pkg/net/tcp_test.goTestTCPConcurrentAcceptという新しいテストケースが追加されました。このテストは、複数のゴルーチンが同時にAcceptを呼び出すシナリオをシミュレートし、パニックが発生しないことを検証します。

この修正は、Goのネットワークスタックの堅牢性を高め、特にWindows環境での高負荷なサーバーアプリケーションの安定性向上に貢献します。

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

src/pkg/net/fd_windows.go

--- a/src/pkg/net/fd_windows.go
+++ b/src/pkg/net/fd_windows.go
@@ -579,6 +579,8 @@ func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) {
 	}
 
 	// Submit accept request.
+	fd.rio.Lock()
+	defer fd.rio.Unlock()
 	var o acceptOp
 	o.Init(fd, 'r')
 	o.newsock = s

src/pkg/net/tcp_test.go

--- a/src/pkg/net/tcp_test.go
+++ b/src/pkg/net/tcp_test.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"reflect"
 	"runtime"
+	"sync"
 	"testing"
 	"time"
 )
@@ -294,3 +295,35 @@ func TestIPv6LinkLocalUnicastTCP(t *testing.T) {
 		<-done
 	}
 }
+
+func TestTCPConcurrentAccept(t *testing.T) {
+	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+	ln, err := Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("Listen failed: %v", err)
+	}
+	const N = 10
+	var wg sync.WaitGroup
+	wg.Add(N)
+	for i := 0; i < N; i++ {
+		go func() {
+			for {
+				c, err := ln.Accept()
+				if err != nil {
+					break
+				}
+				c.Close()
+			}
+			wg.Done()
+		}()
+	}
+	for i := 0; i < 10*N; i++ {
+		c, err := Dial("tcp", ln.Addr().String())
+		if err != nil {
+			t.Fatalf("Dial failed: %v", err)
+		}
+		c.Close()
+	}
+	ln.Close()
+	wg.Wait()
+}

コアとなるコードの解説

src/pkg/net/fd_windows.goの変更

  • fd.rio.Lock(): netFD構造体のrioフィールド(おそらくsync.Mutex型)をロックします。これにより、このロックが解放されるまで、他のゴルーチンはacceptOpのサブミット処理に進むことができません。
  • defer fd.rio.Unlock(): deferキーワードにより、fd.accept関数が終了する際に必ずfd.rio.Unlock()が呼び出され、ロックが解放されることを保証します。これにより、ロックの取り忘れによるデッドロックを防ぎます。

この変更により、複数のゴルーチンが同時にfd.acceptを呼び出しても、acceptOpのサブミットは一度に1つのゴルーチンによってのみ行われるようになり、netpollの「1つの読み込み待機者」という制約が守られます。

src/pkg/net/tcp_test.goの追加

  • TestTCPConcurrentAccept関数が追加されました。
  • defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)): テストの実行中に使用されるOSスレッドの最大数を4に設定します。これにより、並行性が高まり、競合状態が発生しやすい環境をシミュレートします。テスト終了時には元の設定に戻します。
  • ln, err := Listen("tcp", "127.0.0.1:0"): ローカルホストの利用可能なポートでTCPリスナーを作成します。
  • const N = 10: 10個のゴルーチンを起動してAcceptを同時に実行させます。
  • var wg sync.WaitGroup: 起動したゴルーチンの完了を待つためのWaitGroupを宣言します。
  • for i := 0; i < N; i++ { go func() { ... }(): N個のゴルーチンを起動し、それぞれが無限ループでln.Accept()を呼び出し、接続を受け入れたらすぐにc.Close()で閉じます。リスナーが閉じられるとAcceptはエラーを返すため、ループを抜けます。
  • for i := 0; i < 10*N; i++ { ... Dial(...) }: 起動したAcceptゴルーチンが接続を受け入れるように、多数のクライアント接続を試みます。これにより、Acceptが頻繁に呼び出される状況を作り出します。
  • ln.Close(): 全てのクライアント接続が試行された後、リスナーを閉じます。これにより、Acceptを呼び出しているゴルーチンがエラーを受け取り、ループを終了します。
  • wg.Wait(): 全てのAcceptゴルーチンが終了するのを待ちます。

このテストは、修正前のコードであれば「fatal error: netpollblock: double wait」でパニックを引き起こす可能性のあるシナリオを再現し、修正後のコードがそのパニックなしに正常に動作することを確認します。

関連リンク

  • Goのnetpollに関する議論やドキュメントは、Goのソースコード内のruntime/netpoll.goや関連するOS固有のファイル(例: runtime/netpoll_windows.go)を参照すると理解が深まります。
  • Goのsyncパッケージのドキュメント: https://pkg.go.dev/sync

参考にした情報源リンク

  • Goのコミット履歴とソースコード: https://github.com/golang/go
  • Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されているhttps://golang.org/cl/12400045は、このGerritの変更リストへのリンクです。)
  • Goのnetpollに関する一般的な情報(Goの内部動作に関するブログ記事やドキュメントなど)
  • WindowsのIOCPに関するMicrosoftのドキュメント```

[インデックス 17019] ファイルの概要

このコミットは、Go言語のnetパッケージにおけるWindows環境でのAcceptシステムコールの同時実行に関するバグ修正と、それに関連するテストの追加を扱っています。具体的には、複数のゴルーチンが同時にAcceptを呼び出した際に発生する「fatal error: netpollblock: double wait」というランタイムエラーを解消することを目的としています。

コミット

commit 77f21eea597f374e025497c167caccfd72864e13
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Sun Aug 4 23:31:23 2013 +0400

    net: fix concurrent Accept on windows
    Runtime netpoll supports at most one read waiter
    and at most one write waiter. It's responsibility
    of net package to ensure that. Currently windows
    implementation allows more than one waiter in Accept.
    It leads to "fatal error: netpollblock: double wait".
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12400045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/77f21eea597f374e025497c167caccfd72864e13

元コミット内容

net: fix concurrent Accept on windows
Runtime netpoll supports at most one read waiter
and at most one write waiter. It's responsibility
of net package to ensure that. Currently windows
implementation allows more than one waiter in Accept.
It leads to "fatal error: netpollblock: double wait".

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12400045

変更の背景

Goのランタイムには、ネットワークI/Oを効率的に処理するためのnetpollというメカニズムがあります。netpollは、OSの非同期I/O機能(Linuxのepoll、macOSのkqueue、WindowsのI/O Completion Ports (IOCP) など)を抽象化し、複数のネットワーク操作を単一のスレッドで多重化して処理することを可能にします。

このnetpollの設計上の制約として、「最大で1つの読み込み待機者(read waiter)と最大で1つの書き込み待機者(write waiter)しかサポートしない」というものがあります。これは、特定のファイルディスクリプタ(またはWindowsのソケットハンドル)に対して、同時に複数のゴルーチンが読み込みまたは書き込みを待機することを許容しないということを意味します。

しかし、WindowsにおけるnetパッケージのAccept実装では、このnetpollの制約が適切に守られていませんでした。具体的には、複数のゴルーチンが同時にnet.Listenで作成されたリスナーに対してAcceptを呼び出すと、内部的に同じソケットハンドルに対して複数の読み込み待機が発生する可能性がありました。これがnetpollの「double wait」状態を引き起こし、「fatal error: netpollblock: double wait」というランタイムパニックに繋がっていました。

このコミットは、このWindows特有のAcceptにおける同時実行の問題を解決し、netpollの制約を遵守することで、ランタイムパニックを防ぐことを目的としています。

前提知識の解説

GoのネットワークI/Oとnetpoll

GoのネットワークI/Oは、ユーザーレベルのゴルーチンとOSレベルのスレッドを効率的に連携させることで実現されています。

  • ゴルーチン: Goの軽量スレッド。数百万のゴルーチンを同時に実行できる。
  • M(Machine): OSスレッド。GoランタイムはゴルーチンをMにスケジューリングして実行する。
  • P(Processor): 論理プロセッサ。ゴルーチンをMに割り当てるためのコンテキスト。

ネットワークI/O操作(Read, Write, Acceptなど)は、通常ブロッキング操作です。しかし、Goではこれらの操作をノンブロッキングに扱い、netpollメカニズムを通じて効率的に多重化します。

  1. ゴルーチンがネットワークI/O操作を開始すると、Goランタイムは対応するファイルディスクリプタ(またはソケットハンドル)をnetpollに登録します。
  2. もしI/Oがすぐに完了しない場合(データがまだ来ていない、バッファが満杯など)、ゴルーチンはnetpollによってブロックされ、Pから切り離されます。このゴルーチンは、I/Oが完了するまで待機状態に入ります。
  3. Pは別の実行可能なゴルーチンに切り替わり、CPU時間を有効活用します。
  4. OSがI/Oイベント(データ受信、接続確立など)を通知すると、netpollはそれを検知し、待機していたゴルーチンを再び実行可能状態に戻します。
  5. スケジューラは、そのゴルーチンをPに割り当て、I/O操作を再開させます。

このメカニズムにより、Goは多数の同時接続を効率的に処理できます。しかし、netpollは内部的に各ファイルディスクリプタに対して「読み込み待機は1つまで」「書き込み待機は1つまで」という制約を持っています。これは、OSの非同期I/O APIが通常、特定のI/O操作(読み込み、書き込み)に対して一度に1つの非同期リクエストしか受け付けないことに起因します。複数のリクエストが同時に発行されると、競合状態や未定義の動作を引き起こす可能性があります。

Acceptシステムコール

Acceptは、サーバープログラムがクライアントからの新しい接続を受け入れるために使用するシステムコールです。

  • サーバーはまずListenを呼び出して、特定のIPアドレスとポートで接続を待ち受けるリスナーソケットを作成します。
  • その後、Acceptを呼び出して、新しい接続が来るのを待ちます。
  • Acceptが成功すると、新しい接続を表す新しいソケットディスクリプタ(またはハンドル)が返され、クライアントとの通信に使用されます。

WindowsにおけるI/O Completion Ports (IOCP)

Windowsでは、高性能な非同期I/OのためにI/O Completion Ports (IOCP) が広く利用されます。IOCPは、複数の非同期I/O操作の結果を効率的にキューイングし、複数のスレッドで処理することを可能にします。GoのnetpollのWindows実装は、このIOCPを内部的に利用しています。

AcceptExなどのWindows APIは、IOCPと組み合わせて使用されることが多く、非同期に接続を受け入れることができます。しかし、GoのnetpollがIOCPをどのようにラップしているか、そしてその内部的な状態管理がどのように行われているかが、この問題の核心です。

sync.Mutex

sync.MutexはGoの標準ライブラリsyncパッケージで提供されるミューテックス(相互排他ロック)です。複数のゴルーチンが共有リソースに同時にアクセスする際に、データ競合を防ぐために使用されます。

  • Lock(): ミューテックスをロックします。既にロックされている場合は、ロックが解放されるまでゴルーチンはブロックされます。
  • Unlock(): ミューテックスをアンロックします。

deferキーワードと組み合わせることで、関数の終了時に確実にロックが解放されるようにすることができます。

技術的詳細

このコミットの技術的詳細は、GoランタイムのnetpollnetパッケージのWindows実装における同期の問題に集約されます。

  1. 問題の特定: netpollは、各ファイルディスクリプタ(ソケットハンドル)に対して、読み込み操作と書き込み操作それぞれについて、同時に1つの待機ゴルーチンしか許可しません。しかし、Windowsのnet.Acceptの実装では、複数のゴルーチンが同時にAcceptを呼び出すと、同じソケットハンドルに対して複数の読み込み待機がnetpollに登録されてしまう可能性がありました。
  2. 「double wait」エラー: netpollが同じソケットハンドルに対して複数の読み込み待機を検出すると、内部的な状態が矛盾し、「fatal error: netpollblock: double wait」というパニックが発生します。これは、netpollが想定していない競合状態であり、Goランタイムの安定性を損なうものです。
  3. 原因: src/pkg/net/fd_windows.go内のnetFD.acceptメソッドが、新しい接続を受け入れるためのacceptOpnetpollにサブミットする際に、複数のゴルーチンからの同時呼び出しに対する排他制御が不足していました。
  4. 解決策: netFD.acceptメソッド内で、acceptOpをサブミットする直前にfd.rio.Lock()を呼び出し、defer fd.rio.Unlock()でロックを解放するように変更されました。
    • fd.rionetFD構造体内のフィールドで、おそらく読み込みI/O操作に関連するロックを管理するためのsync.Mutexインスタンスです。
    • このロックにより、一度に1つのゴルーチンだけがacceptOpのサブミット処理を実行できるようになります。これにより、netpollに対して同時に複数の読み込み待機が登録されることがなくなり、netpollの制約が守られるようになります。
  5. テストの追加: 問題が修正されたことを確認するため、src/pkg/net/tcp_test.goTestTCPConcurrentAcceptという新しいテストケースが追加されました。このテストは、複数のゴルーチンが同時にAcceptを呼び出すシナリオをシミュレートし、パニックが発生しないことを検証します。

この修正は、Goのネットワークスタックの堅牢性を高め、特にWindows環境での高負荷なサーバーアプリケーションの安定性向上に貢献します。

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

src/pkg/net/fd_windows.goの変更

--- a/src/pkg/net/fd_windows.go
+++ b/src/pkg/net/fd_windows.go
@@ -579,6 +579,8 @@ func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (*netFD, error) {
 	}
 
 	// Submit accept request.
+	fd.rio.Lock()
+	defer fd.rio.Unlock()
 	var o acceptOp
 	o.Init(fd, 'r')
 	o.newsock = s

src/pkg/net/tcp_test.goの追加

--- a/src/pkg/net/tcp_test.go
+++ b/src/pkg/net/tcp_test.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"reflect"
 	"runtime"
+	"sync"
 	"testing"
 	"time"
 )
@@ -294,3 +295,35 @@ func TestIPv6LinkLocalUnicastTCP(t *testing.T) {
 		<-done
 	}
 }
+
+func TestTCPConcurrentAccept(t *testing.T) {
+	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+	ln, err := Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("Listen failed: %v", err)
+	}
+	const N = 10
+	var wg sync.WaitGroup
+	wg.Add(N)
+	for i := 0; i < N; i++ {
+		go func() {
+			for {
+				c, err := ln.Accept()
+				if err != nil {
+					break
+				}
+				c.Close()
+			}
+			wg.Done()
+		}()
+	}
+	for i := 0; i < 10*N; i++ {
+		c, err := Dial("tcp", ln.Addr().String())
+		if err != nil {
+			t.Fatalf("Dial failed: %v", err)
+		}
+		c.Close()
+	}
+	ln.Close()
+	wg.Wait()
+}

コアとなるコードの解説

src/pkg/net/fd_windows.goの変更

  • fd.rio.Lock(): netFD構造体のrioフィールド(おそらくsync.Mutex型)をロックします。これにより、このロックが解放されるまで、他のゴルーチンはacceptOpのサブミット処理に進むことができません。
  • defer fd.rio.Unlock(): deferキーワードにより、fd.accept関数が終了する際に必ずfd.rio.Unlock()が呼び出され、ロックが解放されることを保証します。これにより、ロックの取り忘れによるデッドロックを防ぎます。

この変更により、複数のゴルーチンが同時にfd.acceptを呼び出しても、acceptOpのサブミットは一度に1つのゴルーチンによってのみ行われるようになり、netpollの「1つの読み込み待機者」という制約が守られます。

src/pkg/net/tcp_test.goの追加

  • TestTCPConcurrentAccept関数が追加されました。
  • defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)): テストの実行中に使用されるOSスレッドの最大数を4に設定します。これにより、並行性が高まり、競合状態が発生しやすい環境をシミュレートします。テスト終了時には元の設定に戻します。
  • ln, err := Listen("tcp", "127.0.0.1:0"): ローカルホストの利用可能なポートでTCPリスナーを作成します。
  • const N = 10: 10個のゴルーチンを起動してAcceptを同時に実行させます。
  • var wg sync.WaitGroup: 起動したゴルーチンの完了を待つためのWaitGroupを宣言します。
  • for i := 0; i < N; i++ { go func() { ... }(): N個のゴルーチンを起動し、それぞれが無限ループでln.Accept()を呼び出し、接続を受け入れたらすぐにc.Close()で閉じます。リスナーが閉じられるとAcceptはエラーを返すため、ループを抜けます。
  • for i := 0; i < 10*N; i++ { ... Dial(...) }: 起動したAcceptゴルーチンが接続を受け入れるように、多数のクライアント接続を試みます。これにより、Acceptが頻繁に呼び出される状況を作り出します。
  • ln.Close(): 全てのクライアント接続が試行された後、リスナーを閉じます。これにより、Acceptを呼び出しているゴルーチンがエラーを受け取り、ループを終了します。
  • wg.Wait(): 全てのAcceptゴルーチンが終了するのを待ちます。

このテストは、修正前のコードであれば「fatal error: netpollblock: double wait」でパニックを引き起こす可能性のあるシナリオを再現し、修正後のコードがそのパニックなしに正常に動作することを確認します。

関連リンク

  • Goのnetpollに関する議論やドキュメントは、Goのソースコード内のruntime/netpoll.goや関連するOS固有のファイル(例: runtime/netpoll_windows.go)を参照すると理解が深まります。
  • Goのsyncパッケージのドキュメント: https://pkg.go.dev/sync

参考にした情報源リンク

  • Goのコミット履歴とソースコード: https://github.com/golang/go
  • Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されているhttps://golang.org/cl/12400045は、このGerritの変更リストへのリンクです。)
  • Goのnetpollに関する一般的な情報(Goの内部動作に関するブログ記事やドキュメントなど)
  • WindowsのIOCPに関するMicrosoftのドキュメント