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

[インデックス 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.gosrc/pkg/net/fd_windows.goに存在)が新しいnetFDインスタンスを作成する際に、その内部でnetfd.pd.Init(netfd)を呼び出し、pollDescの初期化を行っていました。これは、ソケットが作成された直後にポーリングの準備が整うことを前提としていました。

しかし、BSD系のkqueueのようなポーラーは、ストリームリスナーの場合、ソケットがsyscall.Listenシステムコールに渡され、実際に接続待ち受け状態になった後でなければ、ポーラーに登録できないという特性を持っていました。newFDの段階では、まだListenが呼び出されていないため、pollDesc.Initを呼び出すと問題が発生する可能性がありました。

このコミットでは、以下の変更が行われました。

  1. newFDからのpollDesc.Initの削除: newFD関数は、単にnetFD構造体を初期化して返すだけの役割になりました。これにより、ソケットの作成とポーラーの初期化が分離されます。
  2. netFD.init()メソッドの導入: netFD構造体に新しいメソッドinit()が追加されました。このメソッドがfd.pd.Init(fd)を呼び出し、pollDescの初期化を行います。
  3. init()メソッドの呼び出しタイミングの変更:
    • netFD.accept: syscall.Acceptが成功し、新しい接続ソケットが作成された後にnetfd.init()が呼び出されるようになりました。これにより、リスナーソケットから派生した接続ソケットが適切にポーラーに登録されます。
    • newFileFD (Unix): os.FileからnetFDを作成する際にnetfd.init()が呼び出されるようになりました。
    • socket (Posix): 新しいソケットが作成された後にfd.init()が呼び出されるようになりました。

この分離により、Goのネットワークスタックは、ソケットのライフサイクルにおけるより適切なタイミングでポーラーを初期化できるようになります。コミットメッセージに記載されているように、将来の変更では、接続タイプ(ダイアラー、ストリームリスナー、データグラムリスナー)に応じてruntime_pollOpenの呼び出し順序が再調整される予定です。

  • ダイアラー(アクティブな接続を開く場合): syscall.Bindsyscall.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がゼロ(未初期化またはクローズ済み)の場合に安全に処理をスキップし、リソースの二重解放や不正なアクセスを防ぐための防御的なプログラミングです。

関連リンク

参考にした情報源リンク

  • 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.