[インデックス 17264] ファイルの概要
このコミットは、Go言語のネットワークパッケージ(net)における、ランタイム統合されたネットワークポーラーとシステムコール関数の呼び出し順序の再配置に関するものです。特に、BSD系のOSにおけるランタイム統合型ネットワークポーラーの準備として行われました。
コミット
commit 5d5defc77ffbf88b8f9a4facf66798e3016340cf
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Thu Aug 15 16:40:33 2013 +0900
net: rearrange the call order of runtime-integrated network pollster and syscall functions
This CL rearranges the call order for raw networking primitives like
the following;
- For dialers that open active connections, pollDesc.Init will be
called before syscall.Connect.
- For stream listeners that open passive stream connections,
pollDesc.Init will be called just after syscall.Listen.
- For datagram listeners that open datagram connections,
pollDesc.Init will be called just after syscall.Bind.
This is in preparation for runtime-integrated network pollster for BSD
variants.
Update #5199
R=golang-dev, alex.brainman
CC=golang-dev
https://golang.org/cl/12730043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5d5defc77ffbf88b8f9a4facf66798e3016340cf
元コミット内容
このコミットは、Goのネットワークパッケージにおいて、ソケット操作を行う際のpollDesc.Initの呼び出しタイミングを、関連するシステムコール(syscall.Connect, syscall.Listen, syscall.Bind)に対して変更するものです。
具体的には、以下の変更が行われました。
- アクティブな接続を開くダイアラー(
dial)の場合:pollDesc.Initがsyscall.Connectの前に呼び出されるように変更。 - パッシブなストリーム接続を開くストリームリスナー(
listenStream)の場合:pollDesc.Initがsyscall.Listenの直後に呼び出されるように変更。 - データグラム接続を開くデータグラムリスナー(
listenDatagram)の場合:pollDesc.Initがsyscall.Bindの直後に呼び出されるように変更。
変更の背景
この変更の主な背景は、BSD系のOS(FreeBSD, OpenBSD, NetBSDなど)におけるランタイム統合型ネットワークポーラーの導入準備です。Goランタイムは、効率的な非同期I/Oを実現するために、OSが提供するI/O多重化メカニズム(Linuxのepoll、macOS/BSDのkqueueなど)を利用しています。
これまでの実装では、pollDesc.Initの呼び出しタイミングが、特定のシステムコールに対して最適ではなかった可能性があります。特に、BSD系OSのkqueueのようなイベント通知メカニズムとGoランタイムのスケジューラをより密接に統合するためには、ソケットがポーリングシステムに登録されるタイミングと、ソケットが実際に接続やリスニングの状態になるタイミングを調整する必要がありました。
このコミットは、GoランタイムがネットワークI/Oイベントをより効率的かつ正確に処理できるようにするための基盤を固める一環として行われました。Issue #5199("net: runtime-integrated network pollster for BSD variants")がこの変更の動機となっています。
前提知識の解説
GoのネットワークI/Oと非同期処理
Go言語は、goroutineとチャネルを用いた並行処理モデルを特徴としています。ネットワークI/Oのようなブロッキング操作は、通常、OSのシステムコールを介して行われますが、Goランタイムはこれらのブロッキングコールを非ブロッキングに変換し、効率的に多数の同時接続を処理できるようにしています。
この非ブロッキングI/Oを実現するために、Goランタイムは内部的に「ネットワークポーラー(network pollster)」と呼ばれるコンポーネントを使用しています。ネットワークポーラーは、OSが提供するI/O多重化メカニズム(例: Linuxのepoll、macOS/BSDのkqueue)を利用して、複数のファイルディスクリプタ(ソケット)からのI/Oイベントを監視します。
pollDesc
pollDescは、Goのnetパッケージ内部で使用される構造体で、個々のネットワークファイルディスクリプタ(netFD)に関連付けられています。pollDescは、そのファイルディスクリプタに対するI/Oイベント(読み込み可能、書き込み可能など)の監視状態を管理し、イベントが発生した際にGoランタイムのスケジューラに通知する役割を担います。
pollDesc.Init()メソッドは、pollDescを初期化し、対応するファイルディスクリプタをネットワークポーラーに登録する処理を行います。これにより、ランタイムは当該ソケットのI/Oイベントを監視できるようになります。
システムコール(syscallパッケージ)
syscallパッケージは、GoプログラムからOSのシステムコールを直接呼び出すための低レベルなインターフェースを提供します。ネットワーク操作においては、以下のようなシステムコールが使用されます。
syscall.Connect(fd, sa, salen): クライアントソケットをリモートアドレスに接続します。syscall.Listen(fd, backlog): サーバーソケットをリスニング状態にし、着信接続を待ち受けます。backlogは、キューに保持できる保留中の接続の最大数を指定します。syscall.Bind(fd, sa, salen): ソケットをローカルアドレスにバインドします。データグラムソケットやサーバーソケットで、特定のIPアドレスとポートにソケットを関連付けるために使用されます。
BSD variants (kqueue)
BSD系のOS(FreeBSD, OpenBSD, NetBSDなど)では、I/O多重化メカニズムとして主にkqueueが使用されます。kqueueは、ファイルディスクリプタに対する様々なイベント(読み込み、書き込み、ソケットの状態変化など)を効率的に監視するための強力なインターフェースです。Goランタイムは、これらのOS上でkqueueを利用して非同期I/Oを実現しています。
技術的詳細
このコミットの技術的な核心は、pollDesc.Init()の呼び出しタイミングを、ソケットのライフサイクルにおける重要なシステムコール(connect, listen, bind)の前後で調整することにあります。
変更の意図
-
dial(アクティブ接続):- 変更前:
fd.init()(pollDesc.Initを呼び出す)がsocket関数内で、dialが呼び出される前に実行されていた。 - 変更後:
fd.init()がfd.dialメソッド内で、syscall.Connectの直前に移動。 - 意図:
connectシステムコールは、ソケットが実際にリモートエンドポイントへの接続を開始するポイントです。この時点でpollDescを初期化し、ポーラーに登録することで、connectが非ブロッキングで実行される場合(例えば、ノンブロッキングソケットでEINPROGRESSが返される場合)に、接続完了イベントをポーラーが正確に捕捉できるようになります。これにより、接続処理の非同期性が向上し、ランタイムが接続の完了を効率的に待機できるようになります。
- 変更前:
-
listenStream(ストリームリスナー):- 変更前:
syscall.ListenがListenTCPやListenUnixなどの上位関数で呼び出され、fd.init()はsocket関数内で先に実行されていた。 - 変更後:
fd.init()がfd.listenStreamメソッド内で、syscall.Listenの直後に移動。また、ListenTCPやListenUnixからsyscall.Listenの呼び出しが削除され、fd.listenStreamに統合された。 - 意図:
listenシステムコールは、ソケットが着信接続を受け入れる準備ができたことをOSに伝えるものです。listenが成功した直後にpollDescを初期化することで、ソケットが実際に接続を受け入れ可能な状態になった時点でポーラーに登録されます。これにより、新しい接続イベント(accept可能イベント)をポーラーが正確に監視できるようになり、ランタイムが効率的に新しい接続を処理できるようになります。
- 変更前:
-
listenDatagram(データグラムリスナー):- 変更前:
fd.init()はsocket関数内で先に実行されていた。 - 変更後:
fd.init()がfd.listenDatagramメソッド内で、syscall.Bindの直後に移動。 - 意図:
bindシステムコールは、データグラムソケットが特定のローカルアドレスとポートにバインドされ、データグラムの送受信が可能になるポイントです。bindが成功した直後にpollDescを初期化することで、ソケットがデータグラムの送受信準備ができた時点でポーラーに登録されます。これにより、データグラムの受信イベントをポーラーが正確に監視できるようになり、ランタイムが効率的にデータグラムを処理できるようになります。
- 変更前:
これらの変更は、Goランタイムのネットワークポーラーが、ソケットの実際の状態変化と同期して動作するようにするためのものです。特にBSD系のOSでkqueueを使用する場合、イベントの登録と通知のタイミングが重要になるため、この調整はランタイムの安定性とパフォーマンスに寄与します。
コアとなるコードの変更箇所
変更は主に以下のファイルで行われています。
src/pkg/net/sock_posix.gosrc/pkg/net/tcpsock_posix.gosrc/pkg/net/unixsock_posix.go
src/pkg/net/sock_posix.go
socket関数から、初期のfd.init()呼び出しが削除されました。--- a/src/pkg/net/sock_posix.go +++ b/src/pkg/net/sock_posix.go @@ -53,10 +53,6 @@ func socket(net string, f, t, p int, ipv6only bool, laddr, raddr sockaddr, deadl closesocket(s) return nil, err } - if err := fd.init(); err != nil { - fd.Close() - return nil, err - }fd.dialメソッド内で、syscall.Connectの前にfd.init()が追加されました。--- a/src/pkg/net/sock_posix.go +++ b/src/pkg/net/sock_posix.go @@ -107,6 +103,9 @@ func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time, toAddr func(sys } } }\n + if err := fd.init(); err != nil { + return err + } var rsa syscall.Sockaddr if rsa, err = raddr.sockaddr(fd.family); err != nil { return errfd.listenStreamメソッド内で、syscall.Listenの後にfd.init()が追加されました。また、listenStreamのシグネチャにbacklog引数が追加されました。--- a/src/pkg/net/sock_posix.go +++ b/src/pkg/net/sock_posix.go @@ -144,6 +143,12 @@ func (fd *netFD) listenStream(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr return os.NewSyscallError("bind", err) } } + if err := syscall.Listen(fd.sysfd, backlog); err != nil { + return os.NewSyscallError("listen", err) + } + if err := fd.init(); err != nil { + return err + } lsa, _ := syscall.Getsockname(fd.sysfd) fd.setAddr(toAddr(lsa), nil) return nilfd.listenDatagramメソッド内で、syscall.Bindの後にfd.init()が追加されました。--- a/src/pkg/net/sock_posix.go +++ b/src/pkg/net/sock_posix.go @@ -180,6 +185,9 @@ func (fd *netFD) listenDatagram(laddr sockaddr, toAddr func(syscall.Sockaddr) Ad return os.NewSyscallError("bind", err) } } + if err := fd.init(); err != nil { + return err + } lsa, _ := syscall.Getsockname(fd.sysfd) fd.setAddr(toAddr(lsa), nil) return nil
src/pkg/net/tcpsock_posix.go
ListenTCP関数からsyscall.Listenの呼び出しが削除されました。これはfd.listenStreamに移動されたためです。--- a/src/pkg/net/tcpsock_posix.go +++ b/src/pkg/net/tcpsock_posix.go @@ -301,10 +301,5 @@ func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) { if err != nil { return nil, err } - err = syscall.Listen(fd.sysfd, listenerBacklog) - if err != nil { - fd.Close() - return nil, &OpError{"listen", net, laddr, err} - } return &TCPListener{fd}, nil }
src/pkg/net/unixsock_posix.go
ListenUnix関数からsyscall.Listenの呼び出しが削除されました。これもfd.listenStreamに移動されたためです。--- a/src/pkg/net/unixsock_posix.go +++ b/src/pkg/net/unixsock_posix.go @@ -283,11 +283,6 @@ func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error) { if err != nil { return nil, err } - err = syscall.Listen(fd.sysfd, listenerBacklog) - if err != nil { - fd.Close() - return nil, &OpError{Op: "listen", Net: net, Addr: laddr, Err: err} - } return &UnixListener{fd, laddr.Name}, nil }
コアとなるコードの解説
このコミットの主要な変更は、netFD構造体のinitメソッド(内部でpollDesc.Initを呼び出す)の呼び出しタイミングを、ネットワーク操作のシステムコール(connect, listen, bind)の前後で戦略的に変更した点にあります。
以前の実装では、socket関数内でソケットが作成された直後にfd.init()が呼び出されていました。これは、ソケットがまだ接続やリスニングの状態になっていない段階でポーラーに登録されることを意味します。
新しい実装では、fd.init()の呼び出しが、ソケットが特定の状態に遷移するシステムコール(connect, listen, bind)の直前または直後に移動されました。
-
fd.dialにおける変更:fd.init()がsyscall.Connectの直前に移動されたことで、接続が開始されるまさにその瞬間にソケットがポーラーに登録されます。これにより、非ブロッキング接続の場合に、OSからの接続完了イベントをポーラーが即座に監視できるようになります。これは、特にconnectがEINPROGRESSを返すようなシナリオで重要です。 -
fd.listenStreamにおける変更:syscall.Listenの呼び出しがfd.listenStream内に移動され、その直後にfd.init()が呼び出されるようになりました。これにより、ソケットが実際に接続を受け入れる準備ができた(リスニング状態になった)時点でポーラーに登録されます。これは、新しい接続が到着した際にポーラーが正確にイベントを捕捉し、ランタイムがacceptを効率的に実行できるようにするために不可欠です。 -
fd.listenDatagramにおける変更:syscall.Bindの直後にfd.init()が呼び出されるようになりました。これにより、データグラムソケットが特定のローカルアドレスにバインドされ、データグラムの送受信が可能になった時点でポーラーに登録されます。これは、データグラムの受信イベントをポーラーが正確に監視するために重要です。
これらの変更は、Goランタイムのネットワークポーラーが、ソケットの実際の状態変化とより密接に同期して動作することを保証します。これにより、特にBSD系のOSでkqueueのようなイベント通知メカニズムを使用する際に、I/Oイベントの処理がより正確かつ効率的になり、ランタイムの全体的なパフォーマンスと安定性が向上します。
関連リンク
- Go Issue #5199: net: runtime-integrated network pollster for BSD variants
- Go Change List 12730043: https://golang.org/cl/12730043
参考にした情報源リンク
- Go言語のソースコード(
src/pkg/net/ディレクトリ) - Go言語のIssueトラッカー
- Go言語のドキュメント(
netパッケージ、syscallパッケージ関連) - BSD系OSの
kqueueに関するドキュメント(一般的な知識として) - Linuxの
epollに関するドキュメント(一般的な知識として) - 非同期I/OおよびI/O多重化に関する一般的なプログラミング知識