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

[インデックス 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ブロッキングが発生する可能性のある操作(例: ReadWrite)を非同期的に処理します。具体的には、ゴルーチンが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)を持っていました。

変更後:

  1. pollDesc構造体の導入: src/pkg/net/fd_poll_unix.gopollDescという新しい構造体が定義されました。

    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
    }
    

    この構造体は、ポーラーサーバーへの参照、関連するシステムファイルディスクリプタ、読み書きの完了を通知するためのチャネル、そして読み書きのデッドラインなど、ポーリングに関連するすべての状態をカプセル化します。

  2. netFDからのフィールド削除とpollDescの埋め込み: src/pkg/net/fd_unix.gonetFD構造体から、cr, cw, rdeadline, wdeadline, ncr, ncw, pollServerといったポーラー関連のフィールドが削除されました。代わりに、pollDesc型のフィールドpdが埋め込まれました。

    type netFD struct {
        // ... 既存のフィールド ...
        pd pollDesc // 新しく追加されたpollDescフィールド
    }
    

    これにより、netFDpollDescの機能にアクセスできるようになりますが、ポーリングの内部実装はpollDescに隠蔽されます。

  3. ポーラー関連関数のレシーバ変更: src/pkg/net/fd_poll_unix.goにおいて、pollServerのメソッドであったPrepareRead, PrepareWrite, WaitRead, WaitWrite, Evictなどが、pollDescのメソッドとして再定義されました。例えば、func (s *pollServer) PrepareRead(fd *netFD) errorfunc (pd *pollDesc) PrepareRead() errorに変更されました。これにより、これらの操作はpollDescインスタンスを通じて直接行われるようになり、netFDからpollServerへの直接的な依存が減少しました。

  4. pollServerpendingマップの型変更: src/pkg/net/fd_poll_unix.gopollServer構造体内のpendingマップの型がmap[int]*netFDからmap[int]*pollDescに変更されました。これは、ポーラーサーバーが直接netFDではなくpollDescを管理するようにするためです。

  5. setReadDeadlinesetWriteDeadlineの変更: src/pkg/net/fd_poll_unix.gosetReadDeadlinesetWriteDeadline関数が、netFDrdeadlinewdeadlineを直接操作する代わりに、netFD.pd.rdeadlinenetFD.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における「関心の分離」を徹底した点にあります。

  1. pollDescの導入による責務の明確化: 以前のnetFDは、ファイルディスクリプタの基本的な情報と、そのディスクリプタに対するI/Oの準備状況を監視するポーラーのロジックが混在していました。pollDescを導入することで、netFDは純粋にソケットの識別子、アドレス情報、接続状態といった「ファイルディスクリプタそのもの」に関する責務を持つようになりました。一方、pollDescは、I/Oのデッドライン管理、ポーラーサーバーへの登録・解除、I/O完了通知チャネルの管理といった「ポーリングに関する責務」を専門に担うようになりました。

  2. ポーラーサーバーとのインタラクションの抽象化: pollServerは、OSの低レベルなポーリングメカニズム(epoll, kqueueなど)を抽象化し、複数のファイルディスクリプタのI/Oイベントを監視する役割を担います。このコミットにより、pollServerは直接netFDを扱うのではなく、pollDescを介してI/Oイベントを管理するようになりました。pollServer.pendingマップがmap[int]*pollDescになったことがその証拠です。これにより、pollServerはポーリングの「対象」がpollDescであることを認識し、pollDescがその対象の「状態」を管理するという、よりクリーンなアーキテクチャが実現されました。

  3. メソッドのレシーバ変更の意義: PrepareRead, WaitReadなどのI/O待機関連の関数がpollServerのメソッドからpollDescのメソッドに変更されたことは重要です。これは、これらの操作が特定のnetFD(正確にはそのpollDesc部分)の状態に直接作用するものであるため、その責務をpollDesc自身に持たせるのが自然だからです。例えば、pd.PrepareRead()は、そのpollDescが管理する読み込みデッドラインが期限切れでないかを確認し、必要であればポーラーに登録する準備をします。これにより、コードの局所性が高まり、netFDのI/Oメソッド(例: netFD.Read)は、自身のpdフィールドを通じてポーリング操作を委譲する形になります。

  4. 将来の拡張性への寄与: このリファクタリングは、Goのネットワークポーラーがより複雑な機能(例: 異なるポーリング戦略、より高度なタイムアウト処理、非同期I/Oのさらなる最適化)を統合する際の基盤となります。netFDpollDescが明確に分離されたことで、ポーラーの実装詳細がnetFDから隠蔽され、ポーラー側の変更がnetFDのコードに与える影響を最小限に抑えることができます。

総じて、このコミットはGoのnetパッケージにおける内部構造の健全性を高め、将来のパフォーマンス改善や機能拡張のための重要な一歩となりました。

関連リンク

  • Go言語のnetパッケージのドキュメント: https://pkg.go.dev/net
  • Go言語の非同期I/Oとネットワークポーラーに関する議論(当時のメーリングリストなど):
  • 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 of netFD and pollDesc.

I am now done with this request.