[インデックス 17054] ファイルの概要
このコミットは、Go言語のnet
パッケージにおいて、ネットワークファイルディスクリプタ(netFD
)の割り当てと、そのポーリング記述子(pollDesc
)の初期化を分離する変更を導入しています。これにより、ソケットの状態に応じたポーラーの初期化が可能になり、特にBSD系のOSにおけるkqueue
のようなランタイム統合型ネットワークポーラーへの対応を容易にすることを目的としています。
コミット
commit 6a76bca362083a2794b5c7d4ccc61d5c9bec7111
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Aug 6 23:42:33 2013 +0900
net: separate pollster initialization from network file descriptor allocation
Unlike the existing net package own pollster, runtime-integrated
network pollster on BSD variants, actually kqueue, requires a socket
that has beed passed to syscall.Listen previously for a stream
listener.
This CL separates pollDesc.Init (actually runtime_pollOpen) from newFD
to allow control of each state of sockets and adds init method to netFD
instead. Upcoming CLs will rearrange the call order of runtime-integrated
pollster and syscall functions like the following;
- For dialers that open active connections, runtime_pollOpen will be
called in between syscall.Bind and syscall.Connect.
- For stream listeners that open passive stream connections,
runtime_pollOpen will be called just after syscall.Listen.
- For datagram listeners that open datagram connections,
runtime_pollOpen will be called just after syscall.Bind.
This is in preparation for runtime-integrated network pollster for BSD
variants.
Update #5199
R=dvyukov, alex.brainman, minux.ma
CC=golang-dev
https://golang.org/cl/8608044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6a76bca362083a2794b5c7d4ccc61d5c9bec7111
元コミット内容
上記の「コミット」セクションに記載されている内容が、このコミットの元の内容です。
変更の背景
この変更の主な背景は、Go言語のネットワークパッケージが、BSD系のOS(FreeBSD, OpenBSD, NetBSDなど)で利用されるkqueue
のようなランタイム統合型ネットワークポーラーとより適切に連携できるようにすることです。
従来のGoのnet
パッケージでは、ネットワークファイルディスクリプタ(netFD
)が作成される際に、そのポーリング記述子(pollDesc
)の初期化(実質的にはruntime_pollOpen
の呼び出し)が同時に行われていました。しかし、kqueue
のような一部のポーラーは、ストリームリスナーの場合、ソケットがsyscall.Listen
に渡された後でなければ適切に初期化できないという制約がありました。つまり、ソケットが特定の状態にあることをポーラーの初期化が要求するケースが存在したのです。
この制約のため、netFD
の作成と同時にpollDesc
を初期化する既存の設計では、kqueue
のようなポーラーをGoのランタイムに統合する際に問題が生じました。このコミットは、この問題を解決し、将来的にBSD向けのランタイム統合型ネットワークポーラーを実装するための準備として行われました。関連するIssueは#5199
です。
前提知識の解説
このコミットを理解するためには、以下の概念を把握しておく必要があります。
- ファイルディスクリプタ (File Descriptor, FD): オペレーティングシステムがファイルやソケットなどのI/Oリソースを識別するために用いる整数値です。ネットワーク通信においては、ソケットがファイルディスクリプタとして扱われます。
- ネットワークポーラー (Network Poller): 多数のI/O操作(特にネットワークI/O)を効率的に監視し、準備ができたI/Oイベントをアプリケーションに通知するためのメカニズムです。これにより、アプリケーションはブロッキングI/Oを避け、多数の同時接続を処理できます。一般的なポーラーには、Linuxの
epoll
、BSD系のkqueue
、WindowsのIOCP
(I/O Completion Ports)などがあります。 pollDesc
(Polling Descriptor): Goのnet
パッケージ内部で、特定のファイルディスクリプタ(ソケット)に対するI/Oイベントの監視状態を管理するための構造体です。pollDesc
は、ランタイムのポーラーと連携して、ソケットの読み書き準備ができたことをGoのゴルーチンに通知します。netFD
(Network File Descriptor): Goのnet
パッケージにおけるネットワーク接続を表す内部構造体です。これには、実際のシステムコールで使用されるファイルディスクリプタ(sysfd
)や、そのソケットのタイプ、ネットワークアドレス情報、そして関連するpollDesc
などが含まれます。syscall.Listen
: サーバーソケットを作成し、指定されたアドレスとポートで接続を待ち受ける状態にするシステムコールです。syscall.Bind
: ソケットにローカルアドレス(IPアドレスとポート番号)を割り当てるシステムコールです。syscall.Connect
: クライアントソケットをリモートアドレスに接続するシステムコールです。runtime_pollOpen
: Goランタイム内部で、特定のファイルディスクリプタをポーラーに登録し、I/Oイベントの監視を開始するための関数です。
技術的詳細
このコミットの核心は、netFD
構造体の生成と、そのpollDesc
の初期化(runtime_pollOpen
の呼び出し)のタイミングを分離した点にあります。
変更前は、newFD
関数(src/pkg/net/fd_unix.go
やsrc/pkg/net/fd_windows.go
に存在)が新しいnetFD
インスタンスを作成する際に、その内部でnetfd.pd.Init(netfd)
を呼び出し、pollDesc
の初期化を行っていました。これは、ソケットが作成された直後にポーリングの準備が整うことを前提としていました。
しかし、BSD系のkqueue
のようなポーラーは、ストリームリスナーの場合、ソケットがsyscall.Listen
システムコールに渡され、実際に接続待ち受け状態になった後でなければ、ポーラーに登録できないという特性を持っていました。newFD
の段階では、まだListen
が呼び出されていないため、pollDesc.Init
を呼び出すと問題が発生する可能性がありました。
このコミットでは、以下の変更が行われました。
newFD
からのpollDesc.Init
の削除:newFD
関数は、単にnetFD
構造体を初期化して返すだけの役割になりました。これにより、ソケットの作成とポーラーの初期化が分離されます。netFD.init()
メソッドの導入:netFD
構造体に新しいメソッドinit()
が追加されました。このメソッドがfd.pd.Init(fd)
を呼び出し、pollDesc
の初期化を行います。init()
メソッドの呼び出しタイミングの変更:netFD.accept
:syscall.Accept
が成功し、新しい接続ソケットが作成された後にnetfd.init()
が呼び出されるようになりました。これにより、リスナーソケットから派生した接続ソケットが適切にポーラーに登録されます。newFileFD
(Unix):os.File
からnetFD
を作成する際にnetfd.init()
が呼び出されるようになりました。socket
(Posix): 新しいソケットが作成された後にfd.init()
が呼び出されるようになりました。
この分離により、Goのネットワークスタックは、ソケットのライフサイクルにおけるより適切なタイミングでポーラーを初期化できるようになります。コミットメッセージに記載されているように、将来の変更では、接続タイプ(ダイアラー、ストリームリスナー、データグラムリスナー)に応じてruntime_pollOpen
の呼び出し順序が再調整される予定です。
- ダイアラー(アクティブな接続を開く場合):
syscall.Bind
とsyscall.Connect
の間でruntime_pollOpen
が呼び出される。 - ストリームリスナー(パッシブなストリーム接続を開く場合):
syscall.Listen
の直後にruntime_pollOpen
が呼び出される。 - データグラムリスナー(データグラム接続を開く場合):
syscall.Bind
の直後にruntime_pollOpen
が呼び出される。
これらの変更は、GoのネットワークI/Oモデルが、異なるOSのポーリングメカニズム(特にBSDのkqueue
)とより柔軟かつ効率的に連携するための基盤を築くものです。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルと、その変更内容は以下の通りです。
src/pkg/net/fd_poll_runtime.go
pollDesc.Close()
とpollDesc.Evict()
メソッドに、pd.runtimeCtx == 0
の場合の早期リターンと、pd.runtimeCtx = 0
によるクリーンアップが追加されました。これにより、これらのメソッドが複数回呼び出された場合の安全性と、リソースの適切な解放が保証されます。
src/pkg/net/fd_unix.go
newFD
関数からnetfd.pd.Init(netfd)
の呼び出しが削除されました。func (fd *netFD) init() error
という新しいメソッドが追加され、この中でfd.pd.Init(fd)
が呼び出されます。netFD.accept
メソッド内で、syscall.Accept
で新しいソケットが作成された後にnetfd.init()
が呼び出されるようになりました。
src/pkg/net/fd_windows.go
fd_unix.go
と同様に、newFD
関数からfd.pd.Init(fd)
の呼び出しが削除されました。func (fd *netFD) init() error
という新しいメソッドが追加され、この中でfd.pd.Init(fd)
が呼び出されます。netFD.accept
メソッド内で、netfd.init()
が呼び出されるようになりました。
src/pkg/net/file_unix.go
newFileFD
関数内で、netfd.init()
が呼び出されるようになりました。
src/pkg/net/sock_posix.go
socket
関数内で、fd.init()
が呼び出されるようになりました。
コアとなるコードの解説
変更の核心は、netFD
の生成とpollDesc
の初期化を分離し、netFD.init()
という新しいメソッドを導入した点にあります。
src/pkg/net/fd_unix.go
(抜粋)
変更前:
func newFD(fd, family, sotype int, net string) (*netFD, error) {
netfd := &netFD{
sysfd: fd,
family: family,
sotype: sotype,
net: net,
}
if err := netfd.pd.Init(netfd); err != nil { // ここでpollDesc.Initが呼ばれていた
return nil, err
}
return netfd, nil
}
変更後:
func newFD(sysfd, family, sotype int, net string) (*netFD, error) {
return &netFD{sysfd: sysfd, family: family, sotype: sotype, net: net}, nil // pollDesc.Initの呼び出しがなくなった
}
func (fd *netFD) init() error { // 新しいinitメソッド
if err := fd.pd.Init(fd); err != nil {
return err
}
return nil
}
この変更により、newFD
は単にnetFD
構造体を割り当てるだけの役割となり、pollDesc
の初期化はnetFD.init()
メソッドに委譲されました。そして、このinit()
メソッドは、ソケットが特定の状態になった後、例えばnetFD.accept
で新しい接続が確立された後や、socket
関数でソケットが作成された後に明示的に呼び出されるようになりました。
src/pkg/net/fd_unix.go
(netFD.accept 抜粋)
変更前:
(netfd.pd.Init
の呼び出しはnewFD
内で行われていたため、accept
には直接的な呼び出しはなかった)
変更後:
func (fd *netFD) accept(toAddr func(syscall.Sockaddr) Addr) (netfd *netFD, err error) {
// ... (既存のコード) ...
if err = netfd.init(); err != nil { // 新しく追加されたinit()の呼び出し
fd.Close()
return nil, err
}
// ... (既存のコード) ...
}
この変更は、ソケットのライフサイクルとポーラーの初期化のタイミングをより細かく制御できるようにするための重要なリファクタリングです。特に、BSDのkqueue
のような、ソケットが特定のシステムコール(例: Listen
)を通過した後にポーラーに登録する必要があるケースに対応するために不可欠でした。
fd_poll_runtime.go
におけるpollDesc.Close()
とpollDesc.Evict()
の変更は、runtimeCtx
がゼロ(未初期化またはクローズ済み)の場合に安全に処理をスキップし、リソースの二重解放や不正なアクセスを防ぐための防御的なプログラミングです。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/6a76bca362083a2794b5c7d4ccc61d5c9bec7111
- Go Code Review (CL): https://golang.org/cl/8608044
- 関連Issue: https://golang.org/issue/5199
参考にした情報源リンク
- Go言語のソースコード (上記コミットの差分)
- Go言語のIssueトラッカー (
#5199
) - kqueueに関する一般的なドキュメント (例: FreeBSD man pages for kqueue)
- Go言語のネットワークパッケージに関する一般的な解説記事 (必要に応じて検索)
- Go言語のランタイムポーラーに関する一般的な解説記事 (必要に応じて検索) I have generated the detailed technical explanation of the commit in Markdown format, following all the specified instructions and chapter structure. The output has been sent to standard output.