[インデックス 16888] ファイルの概要
このコミットは、Goランタイムのネットワークポーリング(netpoll)機能のうち、特にWindowsオペレーティングシステムに特化した実装ファイルである src/pkg/runtime/netpoll_windows.c
に対する修正です。このファイルは、GoプログラムがWindows上で効率的にネットワークI/Oを処理するために、I/O Completion Ports (IOCP) を利用するロジックを含んでいます。
コミット
commit 91d35ad1b86bd9835596704e8ecf1856d3851390
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Sat Jul 27 13:46:40 2013 +0400
runtime: fix potential deadlock in netpoll on windows
If netpoll has been told to block, it must not return with nil,
otherwise scheduler assumes that netpoll is disabled.
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/11920044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/91d35ad1b86bd9835596704e8ecf1856d3851390
元コミット内容
runtime: fix potential deadlock in netpoll on windows
If netpoll has been told to block, it must not return with nil,
otherwise scheduler assumes that netpoll is disabled.
変更の背景
このコミットは、GoランタイムのWindows版 netpoll
における潜在的なデッドロックを修正するために導入されました。Goのスケジューラは、netpoll
関数が block
モードで呼び出された際に、戻り値が nil
であると、ネットワークポーリング機能が「無効になっている」と誤って判断する可能性がありました。
netpoll
は、ネットワークI/Oイベントを待機し、準備ができたゴルーチンをスケジューラに返す役割を担っています。block
引数が true
の場合、netpoll
はイベントが利用可能になるまでブロック(待機)することが期待されます。しかし、何らかの理由で netpoll
が nil
を返した場合、スケジューラは「待機すべきイベントがない、または netpoll
が機能していない」と解釈し、ネットワークI/Oに関連するゴルーチンが永久に実行されない状態、すなわちデッドロックに陥る可能性がありました。
この問題は、特にWindowsのI/O Completion Ports (IOCP) の動作特性と、Goランタイムのスケジューラが netpoll
の戻り値をどのように解釈するかの間のミスマッチに起因していました。netpoll
が nil
を返すのは、通常、処理すべきイベントがなかった場合ですが、block
モードではこれは予期しない動作であり、スケジューラの誤った判断を招いていました。
前提知識の解説
GoランタイムスケジューラとGoroutine
Goは、軽量な並行処理の単位である「ゴルーチン(goroutine)」を提供します。Goランタイムスケジューラは、これらのゴルーチンをOSスレッド(M:Nスケジューリング)に効率的にマッピングし、実行を管理します。ゴルーチンがネットワークI/Oなどのブロッキング操作を実行しようとすると、Goランタイムはそれを検出し、ゴルーチンを「パーク」(一時停止)させ、OSスレッドを解放して他のゴルーチンを実行できるようにします。
ネットワークポーリング (netpoll)
netpoll
は、Goランタイムの重要なコンポーネントであり、ネットワークI/O操作の非同期処理を可能にします。GoのプログラマはブロッキングI/Oのようにコードを書きますが、内部的には netpoll
がOS固有の非同期I/Oメカニズム(Linuxのepoll
、macOS/BSDのkqueue
、WindowsのIOCP
)を利用して、実際のI/Oが完了するまでゴルーチンをパークします。I/Oが完了すると、netpoll
は対応するゴルーチンをアンパークし、スケジューラがそれを再開できるようにします。
Windows I/O Completion Ports (IOCP)
WindowsのI/O Completion Ports (IOCP) は、高効率な非同期I/O処理のためのメカニズムです。アプリケーションはI/O操作を開始し、その完了をIOCPを通じて通知されます。これにより、多数の同時I/O操作を少数のスレッドで処理することが可能になり、スケーラビリティが向上します。Goの netpoll
はWindows上でこのIOCPを利用してネットワークイベントを監視しています。
netpoll
のブロッキング動作と nil
戻り値
runtime·netpoll(bool block)
関数は、block
引数によって動作が変わります。
block = false
の場合:netpoll
は現在準備ができているイベントをチェックし、すぐに結果を返します。イベントがなければnil
を返します。block = true
の場合:netpoll
はイベントが利用可能になるまでブロックします。このモードでnetpoll
がnil
を返すことは、通常、予期されない状況です。スケジューラは、netpoll
がnil
を返した場合、ネットワークポーリングが「無効になっている」と解釈し、ネットワークI/Oを待機しているゴルーチンを再開できなくなる可能性があります。これがデッドロックの原因となります。
技術的詳細
このデッドロックは、runtime·netpoll
関数が block
引数が true
の状態で呼び出されたにもかかわらず、有効なゴルーチン(gp
)を返さずに nil
を返してしまう場合に発生します。Goのスケジューラは、netpoll
が nil
を返すと、ネットワークI/Oの準備ができたゴルーチンが一つもなかった、あるいは netpoll
が何らかの理由で機能していないと判断します。特に block
モードでは、これは「イベントを待機しているが、イベントが来ない」という状況を示唆し、スケジューラがネットワーク関連の処理を停止してしまう可能性があります。
修正前のコードでは、runtime·netpoll
がIOCPからイベントを取得できなかった場合、gp
が nil
のまま関数を終了していました。block
が true
であっても、gp
が nil
であれば、スケジューラは netpoll
が「無効」であると誤解し、ネットワークI/Oを待機しているゴルーチンを永久に実行キューに戻さない状態に陥る可能性がありました。
このコミットの修正は、この誤解を防ぐために、block
が true
であり、かつ gp
が nil
の場合に、runtime·netpoll
がイベントを再試行するように変更することで、デッドロックの可能性を排除します。これにより、netpoll
は block
モードで呼び出された際には、必ず有効なゴルーチンを返すか、またはイベントを待機し続けることが保証されます。
コアとなるコードの変更箇所
--- a/src/pkg/runtime/netpoll_windows.c
+++ b/src/pkg/runtime/netpoll_windows.c
@@ -71,6 +71,8 @@ runtime·netpoll(bool block)
if(iocphandle == INVALID_HANDLE_VALUE)
return nil;
+ gp = nil;
+retry:
o = nil;
errno = 0;
qty = 0;
@@ -104,7 +106,8 @@ runtime·netpoll(bool block)
}
o->errno = errno;
o->qty = qty;
- gp = nil;
runtime·netpollready(&gp, (void*)o->runtimeCtx, mode);
+ if(block && gp == nil)
+ goto retry;
return gp;
}
コアとなるコードの解説
変更は src/pkg/runtime/netpoll_windows.c
ファイル内の runtime·netpoll
関数に集中しています。
-
gp = nil;
の追加:gp = nil;
がretry:
ラベルの直前に追加されました。これは、netpoll
がイベントを待機するループに入る前に、ゴルーチンポインタgp
を明示的にnil
に初期化することを保証します。これにより、以前の呼び出しで設定された可能性のある古いgp
の値が残ることを防ぎ、クリーンな状態から処理を開始できます。 -
retry:
ラベルの追加:retry:
ラベルが導入されました。これは、特定の条件下でruntime·netpoll
関数が処理を再試行するためのジャンプ先として機能します。 -
if(block && gp == nil) goto retry;
の追加: これがこのコミットの最も重要な変更点です。block
:runtime·netpoll
がブロッキングモードで呼び出されたかどうかを示すブール値です。true
の場合、関数はイベントが利用可能になるまで待機することが期待されます。gp == nil
:runtime·netpollready
関数が呼び出された後でも、gp
がnil
のままであることを意味します。これは、IOCPからイベントが取得されなかったか、取得されたイベントに対応するゴルーチンがなかったことを示します。goto retry;
: もしblock
がtrue
であり、かつgp
がnil
であった場合、関数はretry:
ラベルにジャンプし、イベントの取得処理を最初からやり直します。
この修正により、runtime·netpoll
は block
モードで呼び出された際に、有効なゴルーチンを返すまで、またはイベントが利用可能になるまで無限に再試行するようになります。これにより、スケジューラが netpoll
が「無効」であると誤解する状況が回避され、潜在的なデッドロックが解消されます。
関連リンク
- Go CL 11920044: https://golang.org/cl/11920044
参考にした情報源リンク
- GoのnetpollとIOCPに関する情報:
- Goランタイムスケジューラとnetpollの動作に関する情報:
- Goのデッドロックに関する一般的な情報:
- https://stackoverflow.com/questions/tagged/go-deadlock
- https://www.reddit.com/r/golang/comments/ (具体的なスレッドは特定できませんでしたが、Goのデッドロックに関する議論が多数存在します)
- GitHub上の関連Issue (直接このコミットに関連するものではありませんが、netpollやデッドロックに関する議論の例):
- YouTubeのGo netpoll解説動画:
- https://www.youtube.com/watch?v= (具体的な動画は特定できませんでしたが、Go netpollに関する解説動画が多数存在します)
- CSDNのGo netpoll解説記事:
- https://www.csdn.net/ (具体的な記事は特定できませんでしたが、Go netpollに関する解説記事が多数存在します)
- StackAcademicのGo netpoll解説記事:
- https://stackacademic.com/ (具体的な記事は特定できませんでしたが、Go netpollに関する解説記事が多数存在します)
- MediumのGoスケジューラ解説記事:
- https://medium.com/ (具体的な記事は特定できませんでしたが、Goスケジューラに関する解説記事が多数存在します)
- gVisorに関する情報 (netpollerを回避する特殊なケースの例):