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

[インデックス 18469] ファイルの概要

このコミットは、Goランタイムのネットワークポーリング(netpoll)におけるI/Oイベントのトリガーメカニズムに関するリファクタリングです。特に、Solarisオペレーティングシステムに特化していたレベルトリガーI/Oの扱いを汎用化し、netpollarmreadnetpollarmwriteという2つの関数をnetpollarmという単一の関数に統合することで、コードの重複を排除し、インターフェースの一貫性を向上させています。

コミット

commit 2ea859a7797472d6c7e401057d313c1d468a7a09
Author: Dmitriy Vyukov <dvyukov@google.com>
Date:   Wed Feb 12 22:24:29 2014 +0400

    runtime: refactor level-triggered IO support
    Remove GOOS_solaris ifdef from netpoll code,
    instead introduce runtime edge/level triggered IO flag.
    Replace armread/armwrite with a single arm(mode) function,
    that's how all other interfaces look like and these functions
    will need to do roughly the same thing anyway.
    
    LGTM=rsc
    R=golang-codereviews, dave, rsc
    CC=golang-codereviews
    https://golang.org/cl/55500044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/2ea859a7797472d6c7e401057d313c1d468a7a09

元コミット内容

    runtime: refactor level-triggered IO support
    Remove GOOS_solaris ifdef from netpoll code,
    instead introduce runtime edge/level triggered IO flag.
    Replace armread/armwrite with a single arm(mode) function,
    that's how all other interfaces look like and these functions
    will need to do roughly the same thing anyway.

変更の背景

このコミットの主な背景には、GoランタイムのネットワークI/Oポーリングメカニズムの改善と、コードの汎用性および保守性の向上が挙げられます。

  1. Solaris固有のI/O処理の抽象化: 以前のコードでは、Solarisオペレーティングシステムに特化したレベルトリガーI/Oの処理が#ifdef GOOS_solarisというプリプロセッサディレクティブによって直接埋め込まれていました。これは、コードの可読性を損ない、他のOSでのI/Oモデルとの一貫性を欠いていました。このコミットは、Solaris固有の条件分岐を削除し、ランタイム全体で利用可能な「エッジ/レベルトリガーI/Oフラグ」を導入することで、より抽象的で汎用的なI/O処理の枠組みを構築しようとしています。これにより、将来的に他のOSがレベルトリガーI/Oを必要とする場合でも、コードの変更を最小限に抑えることができます。

  2. 関数インターフェースの一貫性: netpollarmreadnetpollarmwriteという2つの関数が存在していましたが、これらは本質的に類似した操作(I/Oイベントの監視を「アーム」する、つまり有効にする)を行っていました。Goランタイムの他のインターフェースが単一の関数でモードを引数として受け取る形式であることに倣い、これらをnetpollarm(mode)という単一の関数に統合することで、APIの一貫性を高め、コードの重複を削減しています。これは、コードベース全体の設計原則に合致させるためのリファクタリングです。

  3. コードの簡素化と保守性向上: 上記の変更により、netpoll.goc内の条件付きコンパイルブロックが削除され、netpollarmという統一されたインターフェースが提供されます。これにより、コードがより簡潔になり、異なるOSやI/Oモデルに対応する際の保守が容易になります。

要するに、このコミットは、GoランタイムのI/Oサブシステムをよりクリーンで、汎用的で、保守しやすいものにするための重要なステップでした。

前提知識の解説

このコミットを理解するためには、以下の概念について知っておく必要があります。

  1. Goランタイム (Go Runtime): Goランタイムは、Goプログラムの実行を管理するシステムです。これには、ガベージコレクション、スケジューラ(ゴルーチンの管理)、メモリ管理、そしてネットワークI/Oの処理などが含まれます。Goの並行処理モデル(ゴルーチンとチャネル)は、このランタイムによって効率的に実現されています。

  2. ネットワークポーリング (Network Polling / netpoll): GoのネットワークI/Oは、ノンブロッキングI/Oとイベント駆動型モデルに基づいて実装されています。netpollは、複数のネットワーク接続(ファイルディスクリプタ)からのI/Oイベント(読み込み可能、書き込み可能など)を効率的に監視するためのメカニズムです。OSが提供するI/O多重化メカニズム(Linuxのepoll、macOS/FreeBSDのkqueue、WindowsのIOCPなど)を利用して、I/O準備ができたファイルディスクリプタを検出し、それに対応するゴルーチンをスケジューラに投入します。これにより、多数の同時接続を効率的に処理できます。

  3. ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおいて、ファイルやソケットなどのI/Oリソースを識別するために使用される整数値です。ネットワークI/Oでは、ソケットがファイルディスクリプタとして扱われます。

  4. エッジトリガー (Edge-triggered) と レベルトリガー (Level-triggered) I/O: これは、I/O多重化メカニズム(epoll, kqueueなど)がイベントを通知する方法に関する重要な概念です。

    • レベルトリガー (Level-triggered): I/Oリソースが「準備完了」状態にある限り、イベントが繰り返し通知されます。例えば、ソケットに読み取り可能なデータがある限り、readイベントが通知され続けます。データが完全に読み取られるまで、またはバッファが満杯になるまで、イベントは発生し続けます。これは、イベントを処理し忘れた場合でも、次のポーリングサイクルで再度通知されるため、プログラミングが比較的容易ですが、イベントの重複処理に注意が必要です。Solarisの/dev/pollや一部の古いselect/poll実装はレベルトリガーです。

    • エッジトリガー (Edge-triggered): I/Oリソースの状態が「変化した」ときにのみ、イベントが一度だけ通知されます。例えば、ソケットに新しいデータが到着したときに一度だけreadイベントが通知されます。そのイベントを受け取ったら、アプリケーションは利用可能なデータをすべて読み取る責任があります。もし一部だけ読み取って残りのデータを放置した場合、そのデータに対しては二度とイベントが通知されません(新しいデータが到着しない限り)。これは、イベントの処理をより厳密に制御する必要があり、プログラミングは複雑になりますが、イベントの重複通知がないため、より効率的です。Linuxのepollはデフォルトでエッジトリガーモードをサポートしており、高性能なサーバーアプリケーションでよく利用されます。

  5. #ifdef GOOS_solaris: C言語のプリプロセッサディレクティブで、GOOS_solarisというマクロが定義されている場合にのみ、そのブロック内のコードがコンパイルされることを意味します。GOOSはGoのビルドシステムが提供する環境変数で、ターゲットOSを示します。これは、特定のOSに依存するコードを条件付きで含めるために使用されます。

  6. runtime·netpollarmread / runtime·netpollarmwrite: これらは、Goランタイム内部で使用される関数で、特定のファイルディスクリプタ(fd)に対して読み取り(read)または書き込み(write)イベントの監視を「アーム」(有効化)する役割を担っていました。つまり、これらの関数が呼び出されることで、netpollシステムがそのfdからのI/Oイベントを監視し始めます。

これらの知識を前提として、コミットはSolaris固有のレベルトリガーI/Oの扱いを汎用化し、I/Oイベント監視のアーム関数を統合することで、GoランタイムのI/Oサブシステムをより洗練されたものにしていると理解できます。

技術的詳細

このコミットは、Goランタイムのnetpoll(ネットワークポーリング)サブシステムにおけるI/Oイベントの監視方法をリファクタリングしています。主要な変更点は以下の通りです。

  1. Solaris固有のレベルトリガーI/O処理の抽象化:

    • 以前のsrc/pkg/runtime/netpoll.gocでは、runtime_pollWait関数内で#ifdef GOOS_solarisというプリプロセッサディレクティブを使用して、Solarisの場合にのみruntime·netpollarmreadまたはruntime·netpollarmwriteを呼び出していました。これは、SolarisのI/O多重化メカニズム(例えば/dev/poll)がレベルトリガーであるため、イベントを消費した後に再度アームする必要があったためと考えられます。
    • このコミットでは、この#ifdefブロックを削除し、代わりにif(Solaris)というランタイムチェックを導入しています。ここでいうSolarisは、Goランタイム内で定義されるブール型のフラグであり、現在のOSがSolarisであるかどうかを示します。これにより、コンパイル時に特定のOSに依存するのではなく、実行時にOSを判別してレベルトリガーI/Oのアーム処理を行うようになります。
    • さらに、runtime·netpollarmread(pd->fd)runtime·netpollarmwrite(pd->fd)の呼び出しが、runtime·netpollarm(pd->fd, mode)という単一の呼び出しに置き換えられています。これは、後述する関数の統合と関連しています。
  2. netpollarmreadnetpollarmwritenetpollarmへの統合:

    • 以前は、読み取りイベントのアームにはruntime·netpollarmread、書き込みイベントのアームにはruntime·netpollarmwriteという個別の関数が使用されていました。
    • このコミットでは、これらの関数をruntime·netpollarm(uintptr fd, int32 mode)という単一の関数に統合しています。mode引数('r'または'w')によって、読み取りイベントをアームするのか、書き込みイベントをアームするのかを区別します。
    • src/pkg/runtime/runtime.hの関数プロトタイプが変更され、netpollarmreadnetpollarmwriteの宣言が削除され、netpollarmの宣言が追加されています。
    • src/pkg/runtime/netpoll_epoll.csrc/pkg/runtime/netpoll_kqueue.csrc/pkg/runtime/netpoll_windows.cといった各OS固有のnetpoll実装ファイルには、新しく追加されたruntime·netpollarm関数のスタブが追加されています。これらのスタブは、runtime·throw("unused")を呼び出すことで、これらのOSではnetpollarmが使用されないことを明示しています。これは、Linux (epoll)、macOS/FreeBSD (kqueue)、Windows (IOCP) など、他の主要なOSのI/O多重化メカニズムが主にエッジトリガーであるか、またはレベルトリガーであってもイベントを消費した後の再アームが不要な設計になっているためと考えられます。Solarisのみが、このnetpollarm関数を実際に利用する特殊なケースとして扱われます。
  3. runtime_pollWaitCanceled関数のコメント更新:

    • runtime_pollWaitCanceled関数内のコメントが更新され、この関数が「Windowsでのみ、保留中の非同期I/O操作のキャンセルが失敗した後に使用される」ことが明記されています。以前のコメントはSolaris固有の#ifdefブロックと関連していましたが、そのブロックが削除されたため、コメントも実態に合わせて修正されました。

これらの変更により、Goランタイムのnetpollコードは、OS固有のI/Oトリガーモデルの違いをより抽象化し、統一されたインターフェースを通じて処理できるようになりました。特に、SolarisのレベルトリガーI/Oの特殊性を、より汎用的な方法で扱うための基盤が整備されています。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/pkg/runtime/netpoll.goc:

    • runtime_pollWait関数内の#ifdef GOOS_solarisブロックが削除され、代わりにif(Solaris)というランタイムチェックが導入されました。
    • runtime·netpollarmread(pd->fd)runtime·netpollarmwrite(pd->fd)の呼び出しが、runtime·netpollarm(pd->fd, mode)という単一の呼び出しに置き換えられました。
    • runtime_pollWaitCanceled関数内のコメントが更新されました。
    --- a/src/pkg/runtime/netpoll.goc
    +++ b/src/pkg/runtime/netpoll.goc
    @@ -134,12 +134,9 @@ ret:
     func runtime_pollWait(pd *PollDesc, mode int) (err int) {
      	err = checkerr(pd, mode);
      	if(err == 0) {
    -#ifdef GOOS_solaris
    -\t\tif(mode == 'r')
    -\t\t\truntime·netpollarmread(pd->fd);\n
    -\t\telse if(mode == 'w')
    -\t\t\truntime·netpollarmwrite(pd->fd);\n
    -#endif
    +\t\t// As for now only Solaris uses level-triggered IO.
    +\t\tif(Solaris)
    +\t\t\truntime·netpollarm(pd->fd, mode);
      		while(!netpollblock(pd, mode, false)) {
      		\terr = checkerr(pd, mode);
      		\tif(err != 0)
    @@ -152,13 +149,8 @@ func runtime_pollWait(pd *PollDesc, mode int) (err int) {
     }
      
     func runtime_pollWaitCanceled(pd *PollDesc, mode int) {
    -#ifdef GOOS_solaris
    -\tif(mode == 'r')
    -\t\truntime·netpollarmread(pd->fd);\n
    -\telse if(mode == 'w')
    -\t\truntime·netpollarmwrite(pd->fd);\n
    -#endif
    -\t// wait for ioready, ignore closing or timeouts.\n
    +\t// This function is used only on windows after a failed attempt to cancel\n
    +\t// a pending async IO operation. Wait for ioready, ignore closing or timeouts.\n
      	while(!netpollblock(pd, mode, true))
      	\t;\n
     }
    
  2. src/pkg/runtime/netpoll_epoll.c, src/pkg/runtime/netpoll_kqueue.c, src/pkg/runtime/netpoll_windows.c:

    • これらのファイルに、新しいruntime·netpollarm関数のスタブが追加されました。これらのスタブは、対応するOSではこの関数が使用されないため、runtime·throw("unused")を呼び出して未使用であることを示しています。
    --- a/src/pkg/runtime/netpoll_epoll.c
    +++ b/src/pkg/runtime/netpoll_epoll.c
    @@ -52,6 +52,13 @@ runtime·netpollclose(uintptr fd)
      	return -res;
     }
      
    +void
    +runtime·netpollarm(uintptr fd, int32 mode)
    +{
    +\tUSED(fd, mode);
    +\truntime·throw("unused");
    +}
    +
     // polls for ready network connections
     // returns list of goroutines that become runnable
     G*
    

    netpoll_kqueue.cnetpoll_windows.cも同様の変更)

  3. src/pkg/runtime/runtime.h:

    • runtime·netpollarmreadruntime·netpollarmwriteの関数プロトタイプが削除されました。
    • 新しいruntime·netpollarm関数のプロトタイプが追加されました。
    --- a/src/pkg/runtime/runtime.h
    +++ b/src/pkg/runtime/runtime.h
    @@ -893,8 +893,7 @@ int32	runtime·netpollopen(uintptr, PollDesc*);
     int32   runtime·netpollclose(uintptr);
     void	runtime·netpollready(G**, PollDesc*, int32);
     uintptr	runtime·netpollfd(PollDesc*);
    -void	runtime·netpollarmread(uintptr fd);
    -void	runtime·netpollarmwrite(uintptr fd);
    +void	runtime·netpollarm(uintptr, int32);
     void	runtime·crash(void);
     void	runtime·parsedebugvars(void);
     void	_rt0_go(void);
    

これらの変更は、GoランタイムのI/Oポーリング層における抽象化と汎用性を高めることを目的としています。

コアとなるコードの解説

このコミットのコアとなる変更は、src/pkg/runtime/netpoll.goc内のruntime_pollWait関数と、runtime.hにおける関数プロトタイプの変更、そして各OS固有のnetpoll実装ファイルへのruntime·netpollarmスタブの追加です。

src/pkg/runtime/netpoll.goc の変更

runtime_pollWait関数は、GoのネットワークI/Oにおいて、特定のファイルディスクリプタ(fd)がI/O準備完了になるまでゴルーチンをブロックし、イベントを待機する役割を担っています。

変更前は、この関数内に以下のようなSolaris固有の条件分岐がありました。

#ifdef GOOS_solaris
	if(mode == 'r')
		runtime·netpollarmread(pd->fd);
	else if(mode == 'w')
		runtime·netpollarmwrite(pd->fd);
#endif

これは、SolarisのI/O多重化メカニズムがレベルトリガーであるため、イベントを処理した後も、そのイベントを再度監視対象として「アーム」(有効化)する必要があったためです。#ifdefはコンパイル時にSolarisの場合にのみこのコードを含めることを意味します。

変更後、このブロックは以下のように変わりました。

		// As for now only Solaris uses level-triggered IO.
		if(Solaris)
			runtime·netpollarm(pd->fd, mode);
  • #ifdef GOOS_solaris の削除と if(Solaris) への置き換え: これは、コンパイル時ではなく、ランタイム時(実行時)にOSがSolarisであるかをチェックするように変更されたことを意味します。SolarisはGoランタイム内で定義されるグローバルなブール変数で、現在のOSがSolarisかどうかを示します。これにより、コードがより汎用的になり、特定のOSに依存するコンパイル時ディレクティブが減少し、コードの可読性と保守性が向上します。

  • runtime·netpollarmread / runtime·netpollarmwrite から runtime·netpollarm への統合: 以前は読み取りと書き込みで個別の関数が呼び出されていましたが、これらがruntime·netpollarm(pd->fd, mode)という単一の関数に統合されました。mode引数('r'または'w')によって、読み取りイベントをアームするのか、書き込みイベントをアームするのかを区別します。これは、インターフェースの一貫性を高め、コードの重複を排除するためのリファクタリングです。

runtime_pollWaitCanceled関数のコメント変更も重要です。以前はSolarisの#ifdefブロックと関連していましたが、そのブロックが削除されたため、この関数がWindowsでのみ使用されることを明確にするようにコメントが更新されました。

src/pkg/runtime/runtime.h の変更

このヘッダーファイルは、GoランタイムのC部分で利用される関数のプロトタイプを定義しています。

変更前は、以下の2つの関数プロトタイプがありました。

void	runtime·netpollarmread(uintptr fd);
void	runtime·netpollarmwrite(uintptr fd);

変更後、これらは削除され、新しい統合された関数のプロトタイプが追加されました。

void	runtime·netpollarm(uintptr, int32);

これは、GoランタイムのC部分のAPIが、読み取りと書き込みのアーム操作を単一の関数で提供するように変更されたことを示しています。

各OS固有の netpoll 実装ファイル (netpoll_epoll.c, netpoll_kqueue.c, netpoll_windows.c) の変更

これらのファイルは、Linux (epoll)、macOS/FreeBSD (kqueue)、Windows (IOCP) など、各OS固有のI/O多重化メカニズムの実装を含んでいます。

これらのファイルには、新しく追加されたruntime·netpollarm関数のスタブが追加されました。

void
runtime·netpollarm(uintptr fd, int32 mode)
{
	USED(fd, mode);
	runtime·throw("unused");
}
  • USED(fd, mode);: これは、コンパイラの警告を抑制するためのマクロで、引数fdmodeが関数内で使用されていないことを示します。
  • runtime·throw("unused");: これは、この関数がこれらのOSでは実際に呼び出されるべきではないことを示すためのものです。もし誤って呼び出された場合、ランタイムパニックを引き起こします。

このスタブの存在は、runtime·netpollarm関数が主にSolarisのようなレベルトリガーI/Oを必要とするOSでのみ意味を持ち、他の主要なOSではその機能が不要であることを明確にしています。これらのOSのI/O多重化メカニズムは、イベントを一度通知すれば、アプリケーションがすべてのデータを消費するまで再アームする必要がない(エッジトリガー)か、またはレベルトリガーであっても再アームの必要がない設計になっているためです。

これらの変更全体として、GoランタイムのI/Oサブシステムが、異なるOSのI/Oモデルの特性をより抽象化し、コードの重複を減らし、将来的な拡張性を高めるように設計されていることがわかります。

関連リンク

参考にした情報源リンク