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

[インデックス 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 はイベントが利用可能になるまでブロック(待機)することが期待されます。しかし、何らかの理由で netpollnil を返した場合、スケジューラは「待機すべきイベントがない、または netpoll が機能していない」と解釈し、ネットワークI/Oに関連するゴルーチンが永久に実行されない状態、すなわちデッドロックに陥る可能性がありました。

この問題は、特にWindowsのI/O Completion Ports (IOCP) の動作特性と、Goランタイムのスケジューラが netpoll の戻り値をどのように解釈するかの間のミスマッチに起因していました。netpollnil を返すのは、通常、処理すべきイベントがなかった場合ですが、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 はイベントが利用可能になるまでブロックします。このモードで netpollnil を返すことは、通常、予期されない状況です。スケジューラは、netpollnil を返した場合、ネットワークポーリングが「無効になっている」と解釈し、ネットワークI/Oを待機しているゴルーチンを再開できなくなる可能性があります。これがデッドロックの原因となります。

技術的詳細

このデッドロックは、runtime·netpoll 関数が block 引数が true の状態で呼び出されたにもかかわらず、有効なゴルーチン(gp)を返さずに nil を返してしまう場合に発生します。Goのスケジューラは、netpollnil を返すと、ネットワークI/Oの準備ができたゴルーチンが一つもなかった、あるいは netpoll が何らかの理由で機能していないと判断します。特に block モードでは、これは「イベントを待機しているが、イベントが来ない」という状況を示唆し、スケジューラがネットワーク関連の処理を停止してしまう可能性があります。

修正前のコードでは、runtime·netpoll がIOCPからイベントを取得できなかった場合、gpnil のまま関数を終了していました。blocktrue であっても、gpnil であれば、スケジューラは netpoll が「無効」であると誤解し、ネットワークI/Oを待機しているゴルーチンを永久に実行キューに戻さない状態に陥る可能性がありました。

このコミットの修正は、この誤解を防ぐために、blocktrue であり、かつ gpnil の場合に、runtime·netpoll がイベントを再試行するように変更することで、デッドロックの可能性を排除します。これにより、netpollblock モードで呼び出された際には、必ず有効なゴルーチンを返すか、またはイベントを待機し続けることが保証されます。

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

--- 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 関数に集中しています。

  1. gp = nil; の追加: gp = nil;retry: ラベルの直前に追加されました。これは、netpoll がイベントを待機するループに入る前に、ゴルーチンポインタ gp を明示的に nil に初期化することを保証します。これにより、以前の呼び出しで設定された可能性のある古い gp の値が残ることを防ぎ、クリーンな状態から処理を開始できます。

  2. retry: ラベルの追加: retry: ラベルが導入されました。これは、特定の条件下で runtime·netpoll 関数が処理を再試行するためのジャンプ先として機能します。

  3. if(block && gp == nil) goto retry; の追加: これがこのコミットの最も重要な変更点です。

    • block: runtime·netpoll がブロッキングモードで呼び出されたかどうかを示すブール値です。true の場合、関数はイベントが利用可能になるまで待機することが期待されます。
    • gp == nil: runtime·netpollready 関数が呼び出された後でも、gpnil のままであることを意味します。これは、IOCPからイベントが取得されなかったか、取得されたイベントに対応するゴルーチンがなかったことを示します。
    • goto retry;: もし blocktrue であり、かつ gpnil であった場合、関数は retry: ラベルにジャンプし、イベントの取得処理を最初からやり直します。

この修正により、runtime·netpollblock モードで呼び出された際に、有効なゴルーチンを返すまで、またはイベントが利用可能になるまで無限に再試行するようになります。これにより、スケジューラが netpoll が「無効」であると誤解する状況が回避され、潜在的なデッドロックが解消されます。

関連リンク

参考にした情報源リンク