[インデックス 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
メカニズムを通じて効率的に多重化します。
- ゴルーチンがネットワークI/O操作を開始すると、Goランタイムは対応するファイルディスクリプタ(またはソケットハンドル)を
netpoll
に登録します。 - もしI/Oがすぐに完了しない場合(データがまだ来ていない、バッファが満杯など)、ゴルーチンは
netpoll
によってブロックされ、Pから切り離されます。このゴルーチンは、I/Oが完了するまで待機状態に入ります。 - Pは別の実行可能なゴルーチンに切り替わり、CPU時間を有効活用します。
- OSがI/Oイベント(データ受信、接続確立など)を通知すると、
netpoll
はそれを検知し、待機していたゴルーチンを再び実行可能状態に戻します。 - スケジューラは、そのゴルーチンを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ランタイムのnetpoll
とnet
パッケージのWindows実装における同期の問題に集約されます。
- 問題の特定:
netpoll
は、各ファイルディスクリプタ(ソケットハンドル)に対して、読み込み操作と書き込み操作それぞれについて、同時に1つの待機ゴルーチンしか許可しません。しかし、Windowsのnet.Accept
の実装では、複数のゴルーチンが同時にAccept
を呼び出すと、同じソケットハンドルに対して複数の読み込み待機がnetpoll
に登録されてしまう可能性がありました。 - 「double wait」エラー:
netpoll
が同じソケットハンドルに対して複数の読み込み待機を検出すると、内部的な状態が矛盾し、「fatal error: netpollblock: double wait」というパニックが発生します。これは、netpoll
が想定していない競合状態であり、Goランタイムの安定性を損なうものです。 - 原因:
src/pkg/net/fd_windows.go
内のnetFD.accept
メソッドが、新しい接続を受け入れるためのacceptOp
をnetpoll
にサブミットする際に、複数のゴルーチンからの同時呼び出しに対する排他制御が不足していました。 - 解決策:
netFD.accept
メソッド内で、acceptOp
をサブミットする直前にfd.rio.Lock()
を呼び出し、defer fd.rio.Unlock()
でロックを解放するように変更されました。fd.rio
はnetFD
構造体内のフィールドで、おそらく読み込みI/O操作に関連するロックを管理するためのsync.Mutex
インスタンスです。- このロックにより、一度に1つのゴルーチンだけが
acceptOp
のサブミット処理を実行できるようになります。これにより、netpoll
に対して同時に複数の読み込み待機が登録されることがなくなり、netpoll
の制約が守られるようになります。
- テストの追加: 問題が修正されたことを確認するため、
src/pkg/net/tcp_test.go
にTestTCPConcurrentAccept
という新しいテストケースが追加されました。このテストは、複数のゴルーチンが同時に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
メカニズムを通じて効率的に多重化します。
- ゴルーチンがネットワークI/O操作を開始すると、Goランタイムは対応するファイルディスクリプタ(またはソケットハンドル)を
netpoll
に登録します。 - もしI/Oがすぐに完了しない場合(データがまだ来ていない、バッファが満杯など)、ゴルーチンは
netpoll
によってブロックされ、Pから切り離されます。このゴルーチンは、I/Oが完了するまで待機状態に入ります。 - Pは別の実行可能なゴルーチンに切り替わり、CPU時間を有効活用します。
- OSがI/Oイベント(データ受信、接続確立など)を通知すると、
netpoll
はそれを検知し、待機していたゴルーチンを再び実行可能状態に戻します。 - スケジューラは、そのゴルーチンを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ランタイムのnetpoll
とnet
パッケージのWindows実装における同期の問題に集約されます。
- 問題の特定:
netpoll
は、各ファイルディスクリプタ(ソケットハンドル)に対して、読み込み操作と書き込み操作それぞれについて、同時に1つの待機ゴルーチンしか許可しません。しかし、Windowsのnet.Accept
の実装では、複数のゴルーチンが同時にAccept
を呼び出すと、同じソケットハンドルに対して複数の読み込み待機がnetpoll
に登録されてしまう可能性がありました。 - 「double wait」エラー:
netpoll
が同じソケットハンドルに対して複数の読み込み待機を検出すると、内部的な状態が矛盾し、「fatal error: netpollblock: double wait」というパニックが発生します。これは、netpoll
が想定していない競合状態であり、Goランタイムの安定性を損なうものです。 - 原因:
src/pkg/net/fd_windows.go
内のnetFD.accept
メソッドが、新しい接続を受け入れるためのacceptOp
をnetpoll
にサブミットする際に、複数のゴルーチンからの同時呼び出しに対する排他制御が不足していました。 - 解決策:
netFD.accept
メソッド内で、acceptOp
をサブミットする直前にfd.rio.Lock()
を呼び出し、defer fd.rio.Unlock()
でロックを解放するように変更されました。fd.rio
はnetFD
構造体内のフィールドで、おそらく読み込みI/O操作に関連するロックを管理するためのsync.Mutex
インスタンスです。- このロックにより、一度に1つのゴルーチンだけが
acceptOp
のサブミット処理を実行できるようになります。これにより、netpoll
に対して同時に複数の読み込み待機が登録されることがなくなり、netpoll
の制約が守られるようになります。
- テストの追加: 問題が修正されたことを確認するため、
src/pkg/net/tcp_test.go
にTestTCPConcurrentAccept
という新しいテストケースが追加されました。このテストは、複数のゴルーチンが同時に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のドキュメント