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

[インデックス 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での実装に備えるものです。

  1. runtime_pollWait 関数の変更:I/Oが準備できた場合でも、クローズまたはタイムアウトを返さないように修正されました。これは、WindowsがI/O操作の中断後もその完了状態を知る必要があるためです。
  2. (*pollDesc).Prepare(mode int) メソッドの追加:読み込みと書き込みの両方に使用できる汎用的な Prepare メソッドが導入されました。同様に Wait メソッドも汎用化されました。
  3. 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モデルに適合させるために、いくつかの重要な技術的変更を導入しています。

  1. 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が実際に完了したのか、それとも他の理由でブロック解除されたのかを区別します。
  2. netpollblock 関数の戻り値の変更:

    • static void netpollblock(PollDesc*, int32); から static bool netpollblock(PollDesc*, int32); へとシグネチャが変更されました。
    • 戻り値がboolになったことで、netpollblockがブロック解除された理由(I/Oが実際に準備できたのか、それともタイムアウトやクローズによってブロックが解除されたのか)を明示的に呼び出し元に伝えることができるようになりました。trueはI/Oが準備できたことを、falseはタイムアウトまたはクローズによってブロック解除されたことを示します。
  3. netpollunblock 関数の引数追加とロジック変更:

    • static G* netpollunblock(PollDesc*, int32); から static G* netpollunblock(PollDesc*, int32, bool); へとシグネチャが変更され、ioreadyという新しいbool型の引数が追加されました。
    • このioready引数は、アンブロックの理由がI/Oの準備完了によるものなのか、それとも他の理由(例えば、デッドラインの期限切れやPollDescのクローズ)によるものなのかを明示的に示します。
    • netpollunblock内で、ioreadytrueの場合にのみ*gpp = READY;を設定するようになりました。これにより、I/Oが実際に準備できた場合にのみ、ゴルーチンが「準備完了」状態としてマークされるようになります。
    • さらに、アンブロックされるゴルーチンgparamフィールドにioreadyの値を設定することで、ブロック解除されたゴルーチンが自身がなぜブロック解除されたのか(I/Oが準備できたためか、それともキャンセルされたためか)を知ることができるようになりました。
  4. runtime_pollWaitCanceled 関数の導入:

    • この新しい関数は、キャンセルされたI/O操作の完了を待つために導入されました。
    • runtime_pollWaitとは異なり、runtime_pollWaitCancelednetpollblockをループで呼び出し、I/Oが準備できるまでひたすら待ちます。この関数はcheckerrを呼び出さず、エラー状態を無視します。
    • これは、WindowsのIOCPがI/O操作の完了を保証する特性に対応するために重要です。たとえ操作がキャンセルされたとしても、その操作が最終的に完了した(または失敗した)という通知を受け取る必要があるため、この関数はその完了を待機します。
  5. PollDesc メソッドの汎用化:

    • src/pkg/net/fd_poll_runtime.goにおいて、PrepareRead()PrepareWrite()Prepare(mode int)を、WaitRead()WaitWrite()Wait(mode int)を呼び出すように変更されました。
    • これにより、読み書き両方に対応する汎用的なPrepareWaitメソッドが導入され、コードの重複が削減され、将来的な拡張性が向上しました。
    • 同様に、WaitCanceled(mode int)と、それを利用するWaitCanceledRead()WaitCanceledWrite()が追加されました。

これらの変更は、GoのnetpollがWindowsの非同期I/Oモデル(特にIOCP)の複雑なセマンティクスを正確に処理できるようにするための重要なステップであり、Goのクロスプラットフォーム対応を強化するものです。

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

このコミットによる主要なコード変更は、以下の2つのファイルに集中しています。

  1. 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() メソッドが追加されました。
  2. 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 の実装が変更され、ioreadytrueの場合にのみ*gpp = READY;を設定するようになりました。

コアとなるコードの解説

このコミットの核心は、Goの非同期I/O処理における「ブロック解除」のセマンティクスを、WindowsのIOCPモデルに合わせてより厳密に定義し直した点にあります。

  • runtime/netpoll.goc の変更:

    • netpollblock の役割の明確化: 以前はvoidを返していたnetpollblockboolを返すようになったことで、この関数がゴルーチンをブロック解除した際に、その理由が「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が準備できていない(netpollblockfalseを返した)場合にのみcheckerrを再度呼び出すようになりました。これにより、I/Oが実際に完了したことを確認し、Windowsのセマンティクスに合わせた動作を実現します。
    • runtime_pollWaitCanceled の導入: この新しい関数は、キャンセルされたI/O操作の完了を待つためのものです。while(!netpollblock(pd, mode)) ループは、I/Oが準備できるまで(netpollblocktrueを返すまで)ゴルーチンをブロックし続けます。これは、WindowsのIOCPがI/O操作の完了を保証するため、たとえキャンセルされた操作であっても、その完了通知を待つ必要があるという要件に対応しています。
  • net/fd_poll_runtime.go の変更:

    • このファイルは、netパッケージからruntimeパッケージのnetpoll機能へのGo言語側のインターフェースを提供します。
    • PollDescPrepareWaitWaitCanceledメソッドの汎用化は、コードの重複を減らし、APIの一貫性を高めるだけでなく、内部のruntime_pollResetruntime_pollWaitruntime_pollWaitCanceledへの呼び出しを抽象化します。これにより、netパッケージのユーザーは、読み書きのモードを意識することなく、より統一されたインターフェースでポーリング操作を行えるようになります。
    • 特にruntime_pollWaitCanceledの公開は、アプリケーションがI/O操作のキャンセル後もその完了を待つ必要があるシナリオ(Windows環境で特に重要)に対応するための重要なAPIです。

これらの変更は、Goのネットワークスタックが、異なるOSのI/O多重化メカニズムの特性を吸収し、一貫した高性能な非同期I/Oを提供するためのGoランタイムの継続的な進化を示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Goのソースコード(特にsrc/pkg/runtime/netpoll.gocsrc/pkg/net/fd_poll_runtime.go
  • Microsoft Docs: I/O Completion Ports (IOCP)
  • Goのネットワーキングに関する技術記事やブログポスト(一般的なGoのネットワーキングモデルとnetpollの概念理解のため)
  • Goのランタイムスケジューラに関する資料(ゴルーチンとOSスレッドの関係、ブロック/アンブロックのメカニズム理解のため)