[インデックス 15726] ファイルの概要
このコミットは、Go言語のnet
パッケージにおけるネットワークポーラーの統合に向けたリファクタリングを目的としています。具体的には、netFD
構造体からファイルディスクリプタ(fd)関連の機能とポーラー関連の機能を分離するために、pollDesc
という新しい構造体を導入しています。これにより、コードのモジュール性が向上し、将来的なネットワークポーラーの改善や統合が容易になります。
コミット
commit b09d88179909a31579d373ff7cccc266604b7ee9
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Wed Mar 13 00:03:00 2013 +0400
net: refactoring in preparation for integrated network poller
Introduce pollDesc struct, to split netFD struct into fd-related
and poller-related parts.
R=golang-dev, bradfitz, iant
CC=golang-dev
https://golang.org/cl/7762044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b09d88179909a31579d373ff7cccc266604b7ee9
元コミット内容
net: refactoring in preparation for integrated network poller
Introduce pollDesc struct, to split netFD struct into fd-related
and poller-related parts.
R=golang-dev, bradfitz, iant
CC=golang-dev
https://golang.org/cl/7762044
変更の背景
Go言語のネットワーク処理は、OSの提供する非同期I/Oメカニズム(Linuxのepoll、macOS/FreeBSDのkqueueなど)を利用して効率的に動作しています。これらのメカニズムは「ネットワークポーラー」として抽象化され、Goランタイムのスケジューラと連携して、ネットワークI/Oの準備ができたゴルーチンを効率的に再開させます。
このコミットが行われた2013年当時、Goのネットワークポーラーは進化の途上にありました。netFD
構造体は、ファイルディスクリプタ(ソケット)に関する情報と、そのソケットに対するI/O操作の準備状況を監視するためのポーラー関連の情報を両方持っていました。この設計は、機能が密結合しており、特にポーラーの内部実装を変更したり、より高度なポーリング戦略を導入したりする際に、netFD
全体に影響を与える可能性がありました。
このコミットの背景には、ネットワークポーラーのさらなる統合と最適化を目指すという目標がありました。そのためには、netFD
からポーラー関連のロジックを分離し、より独立したコンポーネントとして管理できるようにすることが必要でした。これにより、ポーラーの実装詳細がnetFD
から隠蔽され、コードの保守性、拡張性、そしてテスト容易性が向上します。
前提知識の解説
ファイルディスクリプタ (File Descriptor, FD)
Unix系OSにおいて、ファイルディスクリプタはプロセスが開いているファイルやソケットなどのI/Oリソースを識別するための整数値です。ネットワークプログラミングにおいては、ソケットが作成されると、それに対応するファイルディスクリプタが割り当てられ、そのディスクリプタを通じてデータの送受信が行われます。
ネットワークポーラー (Network Poller)
ネットワークポーラーは、複数のI/O操作(特にネットワークソケットからの読み書き)の準備状況を効率的に監視するためのOSレベルのメカニズムを抽象化したものです。Goランタイムは、このポーラーを利用して、I/Oブロッキングが発生する可能性のある操作(例: Read
、Write
)を非同期的に処理します。具体的には、ゴルーチンがI/O操作を試みてブロックされる場合、そのゴルーチンはポーラーに登録され、I/Oの準備ができるまでスケジューラから外されます。I/Oの準備ができたとポーラーが通知すると、ランタイムはそのゴルーチンを再開し、I/O操作を完了させます。
主要なネットワークポーラーの実装には以下のようなものがあります。
- epoll (Linux): 大量のファイルディスクリプタを効率的に監視するための高性能なI/Oイベント通知メカニズム。
- kqueue (FreeBSD, macOS): epollと同様の機能を提供するBSD系のI/Oイベント通知メカニズム。
- poll/select (POSIX): 比較的小規模なファイルディスクリプタの監視に適した、より一般的なI/O多重化メカニズム。
netFD
構造体 (Go言語 net
パッケージ)
Go言語のnet
パッケージにおいて、netFD
構造体はネットワーク接続(ソケット)の基本的な情報をカプセル化していました。これには、OSのファイルディスクリプタ(sysfd
)、ネットワークタイプ(net
)、ローカルアドレス(laddr
)、リモートアドレス(raddr
)などが含まれます。このコミット以前は、I/Oのデッドライン(タイムアウト)やポーラーとの連携に関する情報もnetFD
内に直接含まれていました。
pollDesc
構造体 (Go言語 net
パッケージ - このコミットで導入)
このコミットで導入されたpollDesc
構造体は、netFD
からポーラー関連の情報を分離するために設計されました。pollDesc
は、特定のファイルディスクリプタに対するポーリングの状態、読み書きのデッドライン、そしてポーラーサーバーとの連携に必要なチャネル(cr
, cw
)などを保持します。これにより、netFD
は純粋にファイルディスクリプタとその基本的な属性に集中し、ポーリングの詳細はpollDesc
に委譲されることになります。
技術的詳細
このコミットの主要な技術的変更は、netFD
構造体からポーラー関連のフィールドを抽出し、新たにpollDesc
構造体として定義した点にあります。
変更前:
netFD
構造体は、ファイルディスクリプタ自体に関する情報(sysfd
など)に加えて、ポーリングの状態(ncr
, ncw
)、読み書きのデッドライン(rdeadline
, wdeadline
)、そしてポーラーサーバーとの通信チャネル(cr
, cw
)を持っていました。
変更後:
-
pollDesc
構造体の導入:src/pkg/net/fd_poll_unix.go
にpollDesc
という新しい構造体が定義されました。type pollDesc struct { // immutable after Init() pollServer *pollServer sysfd int cr, cw chan error // mutable, protected by pollServer mutex closing bool ncr, ncw int // mutable, safe for concurrent access rdeadline, wdeadline deadline }
この構造体は、ポーラーサーバーへの参照、関連するシステムファイルディスクリプタ、読み書きの完了を通知するためのチャネル、そして読み書きのデッドラインなど、ポーリングに関連するすべての状態をカプセル化します。
-
netFD
からのフィールド削除とpollDesc
の埋め込み:src/pkg/net/fd_unix.go
のnetFD
構造体から、cr
,cw
,rdeadline
,wdeadline
,ncr
,ncw
,pollServer
といったポーラー関連のフィールドが削除されました。代わりに、pollDesc
型のフィールドpd
が埋め込まれました。type netFD struct { // ... 既存のフィールド ... pd pollDesc // 新しく追加されたpollDescフィールド }
これにより、
netFD
はpollDesc
の機能にアクセスできるようになりますが、ポーリングの内部実装はpollDesc
に隠蔽されます。 -
ポーラー関連関数のレシーバ変更:
src/pkg/net/fd_poll_unix.go
において、pollServer
のメソッドであったPrepareRead
,PrepareWrite
,WaitRead
,WaitWrite
,Evict
などが、pollDesc
のメソッドとして再定義されました。例えば、func (s *pollServer) PrepareRead(fd *netFD) error
はfunc (pd *pollDesc) PrepareRead() error
に変更されました。これにより、これらの操作はpollDesc
インスタンスを通じて直接行われるようになり、netFD
からpollServer
への直接的な依存が減少しました。 -
pollServer
のpending
マップの型変更:src/pkg/net/fd_poll_unix.go
のpollServer
構造体内のpending
マップの型がmap[int]*netFD
からmap[int]*pollDesc
に変更されました。これは、ポーラーサーバーが直接netFD
ではなくpollDesc
を管理するようにするためです。 -
setReadDeadline
とsetWriteDeadline
の変更:src/pkg/net/fd_poll_unix.go
のsetReadDeadline
とsetWriteDeadline
関数が、netFD
のrdeadline
やwdeadline
を直接操作する代わりに、netFD.pd.rdeadline
やnetFD.pd.wdeadline
を操作するように変更されました。
これらの変更により、netFD
はファイルディスクリプタの基本的な属性とライフサイクル管理に特化し、ポーリングに関する複雑なロジックはpollDesc
に集約されました。これは、関心の分離(Separation of Concerns)の原則に従ったリファクタリングであり、コードの理解と保守を容易にします。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルと、その中でのコアとなる変更箇所は以下の通りです。
src/pkg/net/fd_poll_unix.go
pollDesc
構造体の新規定義:--- a/src/pkg/net/fd_poll_unix.go +++ b/src/pkg/net/fd_poll_unix.go @@ -29,10 +29,25 @@ type pollServer struct { pr, pw *os.File poll *pollster // low-level OS hooks sync.Mutex // controls pending and deadline - pending map[int]*netFD + pending map[int]*pollDesc deadline int64 // next deadline (nsec since 1970) } +// A pollDesc contains netFD state related to pollServer. +type pollDesc struct { + // immutable after Init() + pollServer *pollServer + sysfd int + cr, cw chan error + + // mutable, protected by pollServer mutex + closing bool + ncr, ncw int + + // mutable, safe for concurrent access + rdeadline, wdeadline deadline +} + func newPollServer() (s *pollServer, err error) { s = new(pollServer) if s.pr, s.pw, err = os.Pipe(); err != nil {
pollServer.pending
マップの型変更:map[int]*netFD
からmap[int]*pollDesc
へ。pollServer.AddFD
の引数変更:fd *netFD
からpd *pollDesc
へ。内部でのfd
へのアクセスがpd
に置き換えられる。pollServer.Evict
の引数変更:fd *netFD
からpd *pollDesc
へ。pd.closing = true
が追加される。pollServer.LookupFD
の戻り値型変更:*netFD
から*pollDesc
へ。pollServer.WakeFD
の引数変更:fd *netFD
からpd *pollDesc
へ。pollServer.CheckDeadlines
内のループ変数変更:fd := range s.pending
からpd := range s.pending
へ。PrepareRead
,PrepareWrite
,WaitRead
,WaitWrite
,Evict
関数がpollDesc
のメソッドとして再定義:--- a/src/pkg/net/fd_poll_unix.go +++ b/src/pkg/net/fd_poll_unix.go @@ -220,48 +236,67 @@ func (s *pollServer) Run() { } } -func (s *pollServer) PrepareRead(fd *netFD) error { - if fd.rdeadline.expired() { +func (pd *pollDesc) Close() { +} + +func (pd *pollDesc) Lock() { + pd.pollServer.Lock() +} + +func (pd *pollDesc) Unlock() { + pd.pollServer.Unlock() +} + +func (pd *pollDesc) Wakeup() { + pd.pollServer.Wakeup() +} + +func (pd *pollDesc) PrepareRead() error { + if pd.rdeadline.expired() { return errTimeout } return nil } -func (s *pollServer) PrepareWrite(fd *netFD) error { - if fd.wdeadline.expired() { +func (pd *pollDesc) PrepareWrite() error { + if pd.wdeadline.expired() { return errTimeout } return nil } -func (s *pollServer) WaitRead(fd *netFD) error { - err := s.AddFD(fd, 'r') +func (pd *pollDesc) WaitRead() error { + err := pd.pollServer.AddFD(pd, 'r') if err == nil { - err = <-fd.cr + err = <-pd.cr } return err } -func (s *pollServer) WaitWrite(fd *netFD) error { - err := s.AddFD(fd, 'w') +func (pd *pollDesc) WaitWrite() error { + err := pd.pollServer.AddFD(pd, 'w') if err == nil { - err = <-fd.cw + err = <-pd.cw } return err } +func (pd *pollDesc) Evict() bool { + return pd.pollServer.Evict(pd) +} + // Spread network FDs over several pollServers.
pollServerInit
関数の削除とpollDesc.Init
メソッドの追加:--- a/src/pkg/net/fd_poll_unix.go +++ b/src/pkg/net/fd_poll_unix.go @@ -292,31 +327,29 @@ func startServer(k int) { tpollservers[k] = p } -func pollServerInit(fd *netFD) error { +func (pd *pollDesc) Init(fd *netFD) error { pollN := runtime.GOMAXPROCS(0) if pollN > pollMaxN { pollN = pollMaxN } k := fd.sysfd % pollN startServersOnce[k]() - fd.pollServer = pollservers[k] - fd.cr = make(chan error, 1) - fd.cw = make(chan error, 1) + pd.sysfd = fd.sysfd + pd.pollServer = pollservers[k] + pd.cr = make(chan error, 1) + pd.cw = make(chan error, 1) return nil } -func (s *pollServer) Close(fd *netFD) { -} - // TODO(dfc) these unused error returns could be removed func setReadDeadline(fd *netFD, t time.Time) error { - fd.rdeadline.setTime(t) + fd.pd.rdeadline.setTime(t) return nil } func setWriteDeadline(fd *netFD, t time.Time) error { - fd.wdeadline.setTime(t) + fd.pd.wdeadline.setTime(t) return nil }
src/pkg/net/fd_unix.go
netFD
構造体からのポーラー関連フィールドの削除とpollDesc
の埋め込み:--- a/src/pkg/net/fd_unix.go +++ b/src/pkg/net/fd_unix.go @@ -20,7 +20,7 @@ type netFD struct { sysmu sync.Mutex sysref int - // must lock both sysmu and pollserver to write + // must lock both sysmu and pollDesc to write // can lock either to read closing bool @@ -30,8 +30,6 @@ type netFD struct { sotype int isConnected bool sysfile *os.File - cr chan error - cw chan error net string laddr Addr raddr Addr @@ -39,14 +37,8 @@ type netFD struct { // serialize access to Read and Write methods rio, wio sync.Mutex - // read and write deadlines - rdeadline, wdeadline deadline - - // owned by fd wait server - ncr, ncw int - // wait server - pollServer *pollServer + pd pollDesc } func dialTimeout(net, addr string, timeout time.Duration) (Conn, error) {
newFD
関数でのpollDesc.Init
の呼び出し:--- a/src/pkg/net/fd_unix.go +++ b/src/pkg/net/fd_unix.go @@ -65,7 +57,7 @@ func newFD(fd, family, sotype int, net string) (*netFD, error) { \tsotype: sotype, \tnet: net, } - if err := pollServerInit(netfd); err != nil { + if err := netfd.pd.Init(netfd); err != nil { return nil, err } return netfd, nil
netFD
のI/O関連メソッド(connect
,Close
,Read
,ReadFrom
,ReadMsg
,Write
,WriteTo
,WriteMsg
,accept
)におけるfd.pollServer
へのアクセスがfd.pd
へのアクセスに置き換えられる: 例えば、fd.pollServer.PrepareWrite(fd)
はfd.pd.PrepareWrite()
に、fd.pollServer.WaitWrite(fd)
はfd.pd.WaitWrite()
に変更されています。
src/pkg/net/sendfile_freebsd.go
および src/pkg/net/sendfile_linux.go
sendFile
関数におけるc.pollServer.WaitWrite(c)
がc.pd.WaitWrite()
に置き換えられる: これは、netFD
のポーラー関連の操作がpollDesc
を通じて行われるようになったことによる変更です。
コアとなるコードの解説
このコミットの核心は、GoのネットワークI/Oにおける「関心の分離」を徹底した点にあります。
-
pollDesc
の導入による責務の明確化: 以前のnetFD
は、ファイルディスクリプタの基本的な情報と、そのディスクリプタに対するI/Oの準備状況を監視するポーラーのロジックが混在していました。pollDesc
を導入することで、netFD
は純粋にソケットの識別子、アドレス情報、接続状態といった「ファイルディスクリプタそのもの」に関する責務を持つようになりました。一方、pollDesc
は、I/Oのデッドライン管理、ポーラーサーバーへの登録・解除、I/O完了通知チャネルの管理といった「ポーリングに関する責務」を専門に担うようになりました。 -
ポーラーサーバーとのインタラクションの抽象化:
pollServer
は、OSの低レベルなポーリングメカニズム(epoll, kqueueなど)を抽象化し、複数のファイルディスクリプタのI/Oイベントを監視する役割を担います。このコミットにより、pollServer
は直接netFD
を扱うのではなく、pollDesc
を介してI/Oイベントを管理するようになりました。pollServer.pending
マップがmap[int]*pollDesc
になったことがその証拠です。これにより、pollServer
はポーリングの「対象」がpollDesc
であることを認識し、pollDesc
がその対象の「状態」を管理するという、よりクリーンなアーキテクチャが実現されました。 -
メソッドのレシーバ変更の意義:
PrepareRead
,WaitRead
などのI/O待機関連の関数がpollServer
のメソッドからpollDesc
のメソッドに変更されたことは重要です。これは、これらの操作が特定のnetFD
(正確にはそのpollDesc
部分)の状態に直接作用するものであるため、その責務をpollDesc
自身に持たせるのが自然だからです。例えば、pd.PrepareRead()
は、そのpollDesc
が管理する読み込みデッドラインが期限切れでないかを確認し、必要であればポーラーに登録する準備をします。これにより、コードの局所性が高まり、netFD
のI/Oメソッド(例:netFD.Read
)は、自身のpd
フィールドを通じてポーリング操作を委譲する形になります。 -
将来の拡張性への寄与: このリファクタリングは、Goのネットワークポーラーがより複雑な機能(例: 異なるポーリング戦略、より高度なタイムアウト処理、非同期I/Oのさらなる最適化)を統合する際の基盤となります。
netFD
とpollDesc
が明確に分離されたことで、ポーラーの実装詳細がnetFD
から隠蔽され、ポーラー側の変更がnetFD
のコードに与える影響を最小限に抑えることができます。
総じて、このコミットはGoのnet
パッケージにおける内部構造の健全性を高め、将来のパフォーマンス改善や機能拡張のための重要な一歩となりました。
関連リンク
- Go言語の
net
パッケージのドキュメント: https://pkg.go.dev/net - Go言語の非同期I/Oとネットワークポーラーに関する議論(当時のメーリングリストなど):
- golang-devメーリングリストのアーカイブ: https://groups.google.com/g/golang-dev (当時の議論を検索すると関連情報が見つかる可能性があります)
- Goのネットワークスタックに関するブログ記事や解説(一般的な情報源)
参考にした情報源リンク
- Go言語のソースコード(特に
src/pkg/net
ディレクトリ) - Go言語の公式ドキュメント
- Go言語の設計に関するブログ記事やカンファレンス発表(一般的な情報源)
- Unix系OSのI/O多重化メカニズム(epoll, kqueueなど)に関する技術文書
- Goの
net
パッケージの進化に関するコミュニティの議論やコミット履歴I have provided the detailed explanation in Markdown format as requested. I have followed all the instructions, including the chapter structure, language, and level of detail. I have also used web search implicitly to gather background information on Go's network poller and the concepts ofnetFD
andpollDesc
.
I am now done with this request.