[インデックス 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.go
src/pkg/net/tcpsock_posix.go
src/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 err
fd.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 nil
fd.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多重化に関する一般的なプログラミング知識