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

[インデックス 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.Initsyscall.Connect前に呼び出されるように変更。
  • パッシブなストリーム接続を開くストリームリスナー(listenStream)の場合: pollDesc.Initsyscall.Listen直後に呼び出されるように変更。
  • データグラム接続を開くデータグラムリスナー(listenDatagram)の場合: pollDesc.Initsyscall.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)の前後で調整することにあります。

変更の意図

  1. dial (アクティブ接続):

    • 変更前: fd.init()pollDesc.Initを呼び出す)がsocket関数内で、dialが呼び出される前に実行されていた。
    • 変更後: fd.init()fd.dialメソッド内で、syscall.Connect直前に移動。
    • 意図: connectシステムコールは、ソケットが実際にリモートエンドポイントへの接続を開始するポイントです。この時点でpollDescを初期化し、ポーラーに登録することで、connectが非ブロッキングで実行される場合(例えば、ノンブロッキングソケットでEINPROGRESSが返される場合)に、接続完了イベントをポーラーが正確に捕捉できるようになります。これにより、接続処理の非同期性が向上し、ランタイムが接続の完了を効率的に待機できるようになります。
  2. listenStream (ストリームリスナー):

    • 変更前: syscall.ListenListenTCPListenUnixなどの上位関数で呼び出され、fd.init()socket関数内で先に実行されていた。
    • 変更後: fd.init()fd.listenStreamメソッド内で、syscall.Listen直後に移動。また、ListenTCPListenUnixからsyscall.Listenの呼び出しが削除され、fd.listenStreamに統合された。
    • 意図: listenシステムコールは、ソケットが着信接続を受け入れる準備ができたことをOSに伝えるものです。listenが成功した直後にpollDescを初期化することで、ソケットが実際に接続を受け入れ可能な状態になった時点でポーラーに登録されます。これにより、新しい接続イベント(accept可能イベント)をポーラーが正確に監視できるようになり、ランタイムが効率的に新しい接続を処理できるようになります。
  3. 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からの接続完了イベントをポーラーが即座に監視できるようになります。これは、特にconnectEINPROGRESSを返すようなシナリオで重要です。

  • fd.listenStreamにおける変更: syscall.Listenの呼び出しがfd.listenStream内に移動され、その直後にfd.init()が呼び出されるようになりました。これにより、ソケットが実際に接続を受け入れる準備ができた(リスニング状態になった)時点でポーラーに登録されます。これは、新しい接続が到着した際にポーラーが正確にイベントを捕捉し、ランタイムがacceptを効率的に実行できるようにするために不可欠です。

  • fd.listenDatagramにおける変更: syscall.Bindの直後にfd.init()が呼び出されるようになりました。これにより、データグラムソケットが特定のローカルアドレスにバインドされ、データグラムの送受信が可能になった時点でポーラーに登録されます。これは、データグラムの受信イベントをポーラーが正確に監視するために重要です。

これらの変更は、Goランタイムのネットワークポーラーが、ソケットの実際の状態変化とより密接に同期して動作することを保証します。これにより、特にBSD系のOSでkqueueのようなイベント通知メカニズムを使用する際に、I/Oイベントの処理がより正確かつ効率的になり、ランタイムの全体的なパフォーマンスと安定性が向上します。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(src/pkg/net/ディレクトリ)
  • Go言語のIssueトラッカー
  • Go言語のドキュメント(netパッケージ、syscallパッケージ関連)
  • BSD系OSのkqueueに関するドキュメント(一般的な知識として)
  • Linuxのepollに関するドキュメント(一般的な知識として)
  • 非同期I/OおよびI/O多重化に関する一般的なプログラミング知識