[インデックス 16635] ファイルの概要
このコミットは、Goランタイムのネットワークポーリング(netpoll)メカニズムに対する重要な変更を導入しています。特に、Windowsオペレーティングシステムでの実装を準備するための基盤となる修正が含まれています。主な目的は、I/O操作が中断された後でも、そのI/Oが実際に完了したかどうかを正確に判断できるようにすることです。
コミット
commit 8486d96a2720d6ce36b8125f636306f9f224fcf3
Author: Alex Brainman <alex.brainman@gmail.com>
Date: Tue Jun 25 12:29:00 2013 +1000
runtime: change netpoll in preparation for windows implementation
- change runtime_pollWait so it does not return
closed or timeout if IO is ready - windows must
know if IO has completed or not even after
interruption;
- add (*pollDesc).Prepare(mode int) that can be
used for both read and write, same for Wait;
- introduce runtime_pollWaitCanceled and expose
it in net as (*pollDesc).WaitCanceled(mode int);
Full windows netpoll changes are
here https://golang.org/cl/8670044/.
R=golang-dev, dvyukov
CC=golang-dev
https://golang.org/cl/10485043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8486d96a2720d6ce36b8125f636306f9f224fcf3
元コミット内容
このコミットは、Goランタイムのネットワークポーリング(netpoll)の動作を変更し、特にWindowsでの実装に備えるものです。
runtime_pollWait
関数の変更:I/Oが準備できた場合でも、クローズまたはタイムアウトを返さないように修正されました。これは、WindowsがI/O操作の中断後もその完了状態を知る必要があるためです。(*pollDesc).Prepare(mode int)
メソッドの追加:読み込みと書き込みの両方に使用できる汎用的なPrepare
メソッドが導入されました。同様にWait
メソッドも汎用化されました。runtime_pollWaitCanceled
関数の導入:この関数はnet
パッケージの(*pollDesc).WaitCanceled(mode int)
として公開され、キャンセルされたI/O操作の完了を待つために使用されます。
このコミットは、より広範なWindows netpollの変更(https://golang.org/cl/8670044/
で参照可能)の一部です。
変更の背景
GoのネットワークI/Oは、OSが提供するI/O多重化メカニズム(Linuxのepoll、FreeBSD/macOSのkqueue、WindowsのIOCPなど)を利用して非同期に処理されます。これらのメカニズムはそれぞれ異なるセマンティクスを持っていますが、特にWindowsのI/O Completion Ports (IOCP) は、他のUnix系OSのモデルとは大きく異なります。
Unix系のI/O多重化モデル(epollなど)では、ファイルディスクリプタが読み書き可能になったときに通知されます。一方、IOCPは、非同期I/O操作が「完了した」ときに通知するモデルです。これは、I/O操作が開始された後、たとえその操作が中断されたとしても(例えば、ネットワーク接続がクローズされた場合など)、その操作が最終的に成功したのか、失敗したのか、あるいは部分的に完了したのかといった「完了状態」をアプリケーションが知る必要があることを意味します。
既存のGoのnetpoll
実装は、主にUnix系のI/O多重化モデルを念頭に置いて設計されていました。そのため、I/O操作が中断された際に、その操作が実際に完了したのか、それとも単に中断されただけなのかを区別するメカニズムが不足していました。この不足は、WindowsのIOCPの特性と直接的に衝突します。Windowsで堅牢なネットワークI/Oを実現するためには、中断されたI/O操作の完了状態を正確に追跡し、適切に処理できるようなnetpoll
の変更が必要でした。
このコミットは、WindowsのIOCPのセマンティクスに適合するようにruntime_pollWait
の動作を調整し、runtime_pollWaitCanceled
という新しいメカニズムを導入することで、この課題に対処しています。これにより、GoのネットワークスタックがWindows上でも効率的かつ正確に動作するための基盤が築かれました。
前提知識の解説
このコミットの理解には、以下の概念が不可欠です。
- Goランタイム (Go Runtime): Goプログラムの実行を管理するシステム。ゴルーチン(goroutine)のスケジューリング、メモリ管理(ガベージコレクション)、ネットワークI/Oの処理など、多岐にわたる機能を提供します。
- ゴルーチン (Goroutine): Goにおける軽量な実行スレッド。OSのスレッドよりもはるかに軽量で、数百万のゴルーチンを同時に実行することも可能です。Goランタイムのスケジューラによって管理されます。
- ネットワーキングI/O (Networking I/O): ネットワークソケットからのデータの読み書き。Goでは、このI/O操作は非同期に行われ、ゴルーチンがブロックされることなく他の処理を進められるようになっています。
- I/O多重化 (I/O Multiplexing): 複数のI/O操作を同時に監視し、いずれかのI/Oが準備できたときに通知を受け取るメカニズム。これにより、単一のスレッドで多数のI/O接続を効率的に処理できます。主要なI/O多重化APIには以下のようなものがあります。
- epoll (Linux): Linuxカーネルが提供する高性能なI/Oイベント通知メカニズム。
- kqueue (FreeBSD/macOS): BSD系OSで利用されるI/Oイベント通知メカニズム。
- I/O Completion Ports (IOCP) (Windows): Windowsが提供する非同期I/Oの完了通知メカニズム。他のOSのI/O多重化とは異なり、I/O操作が「完了した」ときに通知される点が特徴です。これは、I/O操作が開始された後、その操作が最終的に成功したか失敗したかを知る必要がある場合に特に重要です。
PollDesc
構造体: Goランタイムのnetpoll
において、個々のネットワークファイルディスクリプタ(またはソケット)に関連するポーリング状態を管理するための内部構造体。I/O操作を待機しているゴルーチンや、デッドライン情報などが含まれます。- Cgo: GoプログラムからC言語のコードを呼び出すためのGoの機能。GoランタイムのOS固有の部分(例えば、
netpoll
の実装)は、Cgoを使ってC言語で書かれていることがあります。.goc
ファイルは、CgoでGoとCのコードを連携させるために使用される特殊なファイルです。
技術的詳細
このコミットは、Goのnetpoll
メカニズムをWindowsのIOCPモデルに適合させるために、いくつかの重要な技術的変更を導入しています。
-
runtime_pollWait
のセマンティクス変更:- 以前の
runtime_pollWait
は、I/Oが準備できたと判断されると、すぐにcheckerr
を呼び出してエラーがなければ戻っていました。しかし、WindowsのIOCPでは、I/O操作が中断された場合でも、その操作が最終的に完了したかどうかを知る必要があります。 - このコミットでは、
runtime_pollWait
がI/Oが準備できた場合でも、内部的にnetpollblock
を呼び出すように変更されました。これにより、netpollblock
がI/Oの完了状態をより詳細に制御し、Windowsの要件に合わせた動作を保証できるようになります。 - 具体的には、
runtime_pollWait
内で!netpollblock(pd, mode)
がtrue
の場合(つまり、netpollblock
がタイムアウトやクローズによってブロック解除された場合)、再度checkerr
を呼び出すことで、I/Oが実際に完了したのか、それとも他の理由でブロック解除されたのかを区別します。
- 以前の
-
netpollblock
関数の戻り値の変更:static void netpollblock(PollDesc*, int32);
からstatic bool netpollblock(PollDesc*, int32);
へとシグネチャが変更されました。- 戻り値が
bool
になったことで、netpollblock
がブロック解除された理由(I/Oが実際に準備できたのか、それともタイムアウトやクローズによってブロックが解除されたのか)を明示的に呼び出し元に伝えることができるようになりました。true
はI/Oが準備できたことを、false
はタイムアウトまたはクローズによってブロック解除されたことを示します。
-
netpollunblock
関数の引数追加とロジック変更:static G* netpollunblock(PollDesc*, int32);
からstatic G* netpollunblock(PollDesc*, int32, bool);
へとシグネチャが変更され、ioready
という新しいbool
型の引数が追加されました。- この
ioready
引数は、アンブロックの理由がI/Oの準備完了によるものなのか、それとも他の理由(例えば、デッドラインの期限切れやPollDesc
のクローズ)によるものなのかを明示的に示します。 netpollunblock
内で、ioready
がtrue
の場合にのみ*gpp = READY;
を設定するようになりました。これにより、I/Oが実際に準備できた場合にのみ、ゴルーチンが「準備完了」状態としてマークされるようになります。- さらに、アンブロックされるゴルーチン
g
のparam
フィールドにioready
の値を設定することで、ブロック解除されたゴルーチンが自身がなぜブロック解除されたのか(I/Oが準備できたためか、それともキャンセルされたためか)を知ることができるようになりました。
-
runtime_pollWaitCanceled
関数の導入:- この新しい関数は、キャンセルされたI/O操作の完了を待つために導入されました。
runtime_pollWait
とは異なり、runtime_pollWaitCanceled
はnetpollblock
をループで呼び出し、I/Oが準備できるまでひたすら待ちます。この関数はcheckerr
を呼び出さず、エラー状態を無視します。- これは、WindowsのIOCPがI/O操作の完了を保証する特性に対応するために重要です。たとえ操作がキャンセルされたとしても、その操作が最終的に完了した(または失敗した)という通知を受け取る必要があるため、この関数はその完了を待機します。
-
PollDesc
メソッドの汎用化:src/pkg/net/fd_poll_runtime.go
において、PrepareRead()
とPrepareWrite()
がPrepare(mode int)
を、WaitRead()
とWaitWrite()
がWait(mode int)
を呼び出すように変更されました。- これにより、読み書き両方に対応する汎用的な
Prepare
とWait
メソッドが導入され、コードの重複が削減され、将来的な拡張性が向上しました。 - 同様に、
WaitCanceled(mode int)
と、それを利用するWaitCanceledRead()
、WaitCanceledWrite()
が追加されました。
これらの変更は、Goのnetpoll
がWindowsの非同期I/Oモデル(特にIOCP)の複雑なセマンティクスを正確に処理できるようにするための重要なステップであり、Goのクロスプラットフォーム対応を強化するものです。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は、以下の2つのファイルに集中しています。
-
src/pkg/net/fd_poll_runtime.go
:func runtime_pollWaitCanceled(ctx uintptr, mode int) int
の関数宣言が追加されました。(*pollDesc).PrepareRead()
と(*pollDesc).PrepareWrite()
が、新しい汎用メソッドpd.Prepare(mode)
を呼び出すように変更されました。func (pd *pollDesc) Prepare(mode int) error
メソッドが追加されました。(*pollDesc).WaitRead()
と(*pollDesc).WaitWrite()
が、新しい汎用メソッドpd.Wait(mode)
を呼び出すように変更されました。func (pd *pollDesc) Wait(mode int) error
メソッドが追加されました。func (pd *pollDesc) WaitCanceled(mode int)
メソッドが追加されました。func (pd *pollDesc) WaitCanceledRead()
とfunc (pd *pollDesc) WaitCanceledWrite()
メソッドが追加されました。
-
src/pkg/runtime/netpoll.goc
:static void netpollblock(PollDesc*, int32);
のシグネチャがstatic bool netpollblock(PollDesc*, int32);
に変更され、戻り値がbool
になりました。static G* netpollunblock(PollDesc*, int32);
のシグネチャがstatic G* netpollunblock(PollDesc*, int32, bool);
に変更され、ioready
引数が追加されました。func runtime_pollWait(pd *PollDesc, mode int) (err int)
の内部ロジックが変更され、netpollblock
の戻り値をチェックするようになりました。func runtime_pollWaitCanceled(pd *PollDesc, mode int)
関数が新規に追加されました。runtime_pollUnblock
およびruntime·netpollready
内でnetpollunblock
を呼び出す際に、新しいioready
引数(true
またはfalse
)が渡されるようになりました。netpollblock
の実装が変更され、g->param
を利用してアンブロックされたゴルーチンにI/O準備状態を伝えるようになりました。netpollunblock
の実装が変更され、ioready
がtrue
の場合にのみ*gpp = READY;
を設定するようになりました。
コアとなるコードの解説
このコミットの核心は、Goの非同期I/O処理における「ブロック解除」のセマンティクスを、WindowsのIOCPモデルに合わせてより厳密に定義し直した点にあります。
-
runtime/netpoll.goc
の変更:netpollblock
の役割の明確化: 以前はvoid
を返していたnetpollblock
がbool
を返すようになったことで、この関数がゴルーチンをブロック解除した際に、その理由が「I/Oが準備できたため」なのか、それとも「タイムアウトやクローズなど、I/O以外の理由でブロックが解除されたため」なのかを明示的に呼び出し元に伝えるようになりました。これは、特にWindowsのIOCPにおいて、I/O操作が中断された場合でもその完了状態を正確に把握するために不可欠です。netpollunblock
の精密化:ioready
引数の導入により、ゴルーチンをアンブロックする際に、そのアンブロックがI/Oの準備完了によるものなのか、それとも他の理由によるものなのかを区別できるようになりました。これにより、PollDesc
の状態管理がより正確になり、不要なREADY
状態への遷移を防ぎます。また、old->param = (void*)ioready;
の行は、アンブロックされたゴルーチン(old
)に、なぜ自身がブロック解除されたのかという情報(ioready
の値)を渡すための重要なメカニズムです。ゴルーチンはこの情報を使って、その後の処理を適切に分岐できます。runtime_pollWait
の堅牢化:runtime_pollWait
は、netpollblock
の戻り値をチェックし、I/Oが準備できていない(netpollblock
がfalse
を返した)場合にのみcheckerr
を再度呼び出すようになりました。これにより、I/Oが実際に完了したことを確認し、Windowsのセマンティクスに合わせた動作を実現します。runtime_pollWaitCanceled
の導入: この新しい関数は、キャンセルされたI/O操作の完了を待つためのものです。while(!netpollblock(pd, mode))
ループは、I/Oが準備できるまで(netpollblock
がtrue
を返すまで)ゴルーチンをブロックし続けます。これは、WindowsのIOCPがI/O操作の完了を保証するため、たとえキャンセルされた操作であっても、その完了通知を待つ必要があるという要件に対応しています。
-
net/fd_poll_runtime.go
の変更:- このファイルは、
net
パッケージからruntime
パッケージのnetpoll
機能へのGo言語側のインターフェースを提供します。 PollDesc
のPrepare
、Wait
、WaitCanceled
メソッドの汎用化は、コードの重複を減らし、APIの一貫性を高めるだけでなく、内部のruntime_pollReset
、runtime_pollWait
、runtime_pollWaitCanceled
への呼び出しを抽象化します。これにより、net
パッケージのユーザーは、読み書きのモードを意識することなく、より統一されたインターフェースでポーリング操作を行えるようになります。- 特に
runtime_pollWaitCanceled
の公開は、アプリケーションがI/O操作のキャンセル後もその完了を待つ必要があるシナリオ(Windows環境で特に重要)に対応するための重要なAPIです。
- このファイルは、
これらの変更は、Goのネットワークスタックが、異なるOSのI/O多重化メカニズムの特性を吸収し、一貫した高性能な非同期I/Oを提供するためのGoランタイムの継続的な進化を示しています。
関連リンク
- このコミットのコードレビュー: https://golang.org/cl/10485043
- Windows netpollの完全な変更セット: https://golang.org/cl/8670044/
参考にした情報源リンク
- Go言語の公式ドキュメント
- Goのソースコード(特に
src/pkg/runtime/netpoll.goc
とsrc/pkg/net/fd_poll_runtime.go
) - Microsoft Docs: I/O Completion Ports (IOCP)
- Goのネットワーキングに関する技術記事やブログポスト(一般的なGoのネットワーキングモデルと
netpoll
の概念理解のため) - Goのランタイムスケジューラに関する資料(ゴルーチンとOSスレッドの関係、ブロック/アンブロックのメカニズム理解のため)