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

[インデックス 15631] ファイルの概要

このコミットは、Go言語のネットワークパッケージ(net)における重要なリファクタリングを扱っています。特に、I/OポーリングメカニズムをGoランタイムに統合するための準備作業として、pollServer関連のコードがfd_unix.goからfd_poll_unix.goへと移動され、カスタム初期化や終了処理を可能にするための変更が加えられています。

コミット

  • コミットハッシュ: b000f2286c3c9d462cf390084e1ea4ae16649fe9
  • Author: Dmitriy Vyukov dvyukov@google.com
  • Date: Thu Mar 7 21:44:24 2013 +0400

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/b000f2286c3c9d462cf390084e1ea4ae16649fe9

元コミット内容

net: more refactoring in preparation for runtime integrated pollster
Move pollServer from fd_unix.go to fd_poll_unix.go.
Add pollServerInit(*NetFD) to allow custom initialization.
Add pollServer.Close(*NetFD) to allow custom finalization.
Move setDeadline() to fd_poll_unix.go to allow custom handling of deadlines.
Move newPollServer() to fd_poll_unix.go to allow custom initialization.
No logical code changes.
The next step will be to turn off fd_poll_unix.go for some platform
(I have changes for darwin/linux) and redirect it into runtime. See:
https://golang.org/cl/7569043/diff/2001/src/pkg/net/fd_poll_runtime.go

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/7513045

変更の背景

このコミットの主な背景は、Go言語のネットワークI/Oポーリングメカニズムを、より効率的でスケーラブルな「ランタイム統合ポーラスター(runtime integrated pollster)」へと移行するための準備です。

GoのネットワークI/Oは、内部的にはノンブロッキングI/Oを使用しています。しかし、Goのプログラマーから見ると、ReadWriteのようなI/O操作はブロッキングであるかのように振る舞います。この透過性を実現するために、Goランタイムは「ネットワークポーラー(netpoller)」と呼ばれるコンポーネントを使用しています。これは、OSが提供する効率的なI/O多重化メカニズム(Linuxのepoll、macOS/BSDのkqueue、WindowsのIoCompletionPortなど)を利用して、多数のファイルディスクリプタ(ネットワーク接続)の準備状況を監視します。

以前のGoのバージョンでは、このポーリングロジックの一部がnetパッケージ内に存在していました。しかし、より高度な最適化と、ランタイムスケジューラとの密接な連携を実現するためには、このポーリング機能をGoランタイム自体に深く統合する必要がありました。これにより、I/O待ちでブロックされたゴルーチンを効率的に「パーク(park)」し、I/Oが準備できたときに「アンパーク(unpark)」してスケジューラに戻すことが可能になります。

このコミットは、その統合に向けた最初の一歩として、pollServerというポーリング関連の主要な構造体とそのメソッドを、netパッケージ内のより適切なファイルに移動し、将来のランタイム統合に備えてインターフェースを整理することを目的としています。特に、カスタムの初期化や終了処理、デッドライン(タイムアウト)のハンドリングを可能にすることで、プラットフォーム固有のポーリング実装をランタイムにリダイレクトする際の柔軟性を高めています。

前提知識の解説

ファイルディスクリプタ (File Descriptor, FD)

Unix系OSにおいて、ファイルやソケットなどのI/Oリソースを識別するために使用される整数値です。Goのネットワーク接続も内部的にはファイルディスクリプタとして扱われます。

ノンブロッキングI/O (Non-blocking I/O)

通常のブロッキングI/O操作では、データが利用可能になるまで(または書き込みが完了するまで)プログラムの実行が停止します。一方、ノンブロッキングI/Oでは、I/O操作がすぐに完了しない場合でも、エラー(例: EAGAINEWOULDBLOCK)を返してすぐに制御を呼び出し元に戻します。これにより、プログラムはI/O待ちの間に他の処理を実行できます。

I/O多重化 (I/O Multiplexing)

複数のI/O操作を同時に監視し、いずれかのI/Oが準備できたときに通知を受け取るメカニズムです。これにより、単一のスレッドで多数のI/O接続を効率的に処理できます。主要なI/O多重化APIには以下のようなものがあります。

  • select / poll: 比較的基本的なAPIで、監視対象のFDのリストをOSに渡し、準備ができたFDを返します。
  • epoll (Linux): select/pollよりも高性能で、大規模な接続数に適しています。イベント駆動型で、監視対象のFDの追加・削除が効率的です。
  • kqueue (FreeBSD, macOS, NetBSD, OpenBSD): epollと同様に高性能なイベント通知インターフェースです。
  • IoCompletionPort (IOCP, Windows): Windowsにおける高性能な非同期I/Oメカニズムです。

Goのネットポーラー (Netpoller)

Goランタイムの重要なコンポーネントで、GoのゴルーチンがブロッキングI/Oを実行しているかのように見せかけつつ、内部的にはノンブロッキングI/OとOSのI/O多重化メカニズムを組み合わせて効率的なネットワークI/Oを実現します。

  1. ゴルーチンがネットワークI/O操作(例: Read)を開始します。
  2. データがすぐに利用できない場合、GoランタイムはFDをノンブロッキングモードに設定し、OSのI/O多重化APIにFDを登録します。
  3. I/O待ちのゴルーチンは「パーク」され、そのゴルーチンを実行していたOSスレッドは他のゴルーチンを実行するために解放されます。
  4. OSのI/O多重化APIが、FDがI/O可能になったことをネットポーラーに通知します。
  5. ネットポーラーは、対応するゴルーチンを「アンパーク」し、Goスケジューラの実行キューに戻します。
  6. ゴルーチンはI/O操作を再開し、今度はデータが利用可能になっているため、ブロッキングすることなく完了します。

pollServer (旧称)

このコミット以前のGoのnetパッケージに存在した、I/Oポーリングを管理する内部的な構造体です。netFD(ネットワークファイルディスクリプタ)がEAGAIN(一時的にI/Oができない状態)を受け取った際に、I/Oを再試行するタイミングを決定するのを助ける役割を担っていました。

netFD

Goのnetパッケージにおけるネットワーク接続を表す内部的な構造体です。OSのファイルディスクリプタ(sysfd)をラップし、読み書きのデッドライン(タイムアウト)や、ポーリング関連のチャネルなどを保持します。

技術的詳細

このコミットは、Goのnetパッケージ内のI/Oポーリングロジックを再編成し、将来的にGoランタイムのより深いレベルでポーリングを統合するための基盤を構築しています。論理的なコード変更は行われておらず、主にコードの移動とインターフェースの調整に焦点を当てています。

pollServerの移動

  • 以前はsrc/pkg/net/fd_unix.goに定義されていたpollServer構造体と関連するメソッド(AddFD, Evict, Wakeup, LookupFD, WakeFD, CheckDeadlines, Run, PrepareRead, PrepareWrite, WaitRead, WaitWrite)が、新しく作成されたsrc/pkg/net/fd_poll_unix.goに移動されました。
  • newPollServer()関数も同様にsrc/pkg/net/newpollserver_unix.goからsrc/pkg/net/fd_poll_unix.goに移動され、src/pkg/net/newpollserver_unix.goは削除されました。

この移動により、pollServerに関するコードがnetパッケージ内でより独立したモジュールとして扱われるようになり、ランタイム統合の際にnetパッケージの他の部分への影響を最小限に抑えることができます。

カスタム初期化と終了処理の導入

  • pollServerInit(*NetFD)関数が導入されました。これは、netFDが作成される際に、そのnetFDに関連付けられるpollServerの初期化をカスタムで行えるようにするためのものです。以前はnewFD内で直接server(fd)を呼び出してpollServerを取得していましたが、pollServerInitを介することで、初期化ロジックを抽象化し、将来的にランタイムが提供するポーラーに切り替える際の柔軟性が増します。
  • pollServer.Close(*NetFD)メソッドが追加されました。これは、netFDがクローズされる際に、pollServerがカスタムの終了処理を実行できるようにするためのものです。これにより、pollServerが管理しているリソース(例: OSのポーリングメカニズムに登録されたFD)を適切に解放する機会が提供されます。

デッドラインハンドリングの移動

  • setReadDeadline(), setWriteDeadline(), setDeadline()関数がsrc/pkg/net/sockopt_posix.goからsrc/pkg/net/fd_poll_unix.goに移動されました。
  • src/pkg/net/sockopt_windows.goにも同様の関数が追加されました。

これにより、デッドライン(タイムアウト)の管理ロジックがpollServerのコードとより密接に連携するようになり、ポーリングメカニズムがデッドラインの期限切れをより効率的に検出・処理できるようになります。特に、pollServerCheckDeadlinesメソッドが、登録されたFDのデッドラインを監視し、期限切れのFDをポーリングリストから削除してタイムアウトエラーを通知する役割を担います。

論理的な変更なし

コミットメッセージに明記されている通り、これらの変更は「No logical code changes.(論理的なコード変更なし)」です。これは、既存の機能の振る舞いを変更するものではなく、コードの構造と配置を改善し、将来の大きな変更(ランタイム統合)に備えるためのリファクタリングであることを意味します。

次のステップへの言及

コミットメッセージでは、次のステップとして「fd_poll_unix.goを一部のプラットフォーム(darwin/linux)で無効にし、ランタイムにリダイレクトする」ことが述べられています。これは、netパッケージがOS固有のポーリング実装を直接扱うのではなく、Goランタイムが提供する汎用的なポーリングインターフェースを介してI/Oを処理するようになることを示唆しています。これにより、Goのネットワークスタックのパフォーマンスとスケーラビリティがさらに向上することが期待されます。

コアとなるコードの変更箇所

このコミットでは、主に以下のファイルが変更されています。

  1. src/pkg/net/fd_poll_unix.go:

    • 新規作成されたファイル。
    • pollServer構造体とそのすべてのメソッド(AddFD, Evict, Wakeup, LookupFD, WakeFD, CheckDeadlines, Run, PrepareRead, PrepareWrite, WaitRead, WaitWrite)がfd_unix.goから移動されました。
    • newPollServer()関数がnewpollserver_unix.goから移動されました。
    • sysInit(), startServer(), pollServerInit(*netFD), pollServer.Close(*netFD)といった、pollServerの初期化と管理に関連する関数が追加されました。
    • setReadDeadline(), setWriteDeadline(), setDeadline()関数がsockopt_posix.goから移動されました。
  2. src/pkg/net/fd_unix.go:

    • pollServer構造体とその関連メソッドの定義が削除されました。
    • runtimeパッケージのインポートが削除されました(pollServerが移動したため)。
    • newFD関数内で、netfd.cr, netfd.cwのチャネル作成とnetfd.pollServerの設定が、新しく導入されたpollServerInit(netfd)の呼び出しに置き換えられました。
    • decref関数内で、fd.pollServer.Close(fd)の呼び出しが追加され、netFDがクローズされる際にpollServerのカスタム終了処理が実行されるようになりました。
    • server(fd)関数が削除されました。
  3. src/pkg/net/newpollserver_unix.go:

    • ファイル全体が削除されました。newPollServer()関数がfd_poll_unix.goに移動されたためです。
  4. src/pkg/net/sock_posix.go:

    • dialTimeout関数内で、デッドラインの設定とクリアにsetWriteDeadline関数が使用されるようになりました。以前はfd.wdeadline.setTimeを直接呼び出していました。
  5. src/pkg/net/sockopt_posix.go:

    • setReadDeadline(), setWriteDeadline(), setDeadline()関数が削除されました。これらはfd_poll_unix.goに移動されました。
    • timeパッケージのインポートが削除されました。
  6. src/pkg/net/sockopt_windows.go:

    • timeパッケージのインポートが追加されました。
    • setReadDeadline(), setWriteDeadline(), setDeadline()関数が追加されました。これは、Unix系OSと同様のデッドライン設定関数をWindowsにも提供するためです。

コアとなるコードの解説

src/pkg/net/fd_poll_unix.go (新規ファイル)

このファイルは、GoのネットワークI/Oにおけるポーリングメカニズムの心臓部を担います。

pollServer 構造体

type pollServer struct {
	pr, pw     *os.File
	poll       *pollster // low-level OS hooks
	sync.Mutex           // controls pending and deadline
	pending    map[int]*netFD
	deadline   int64 // next deadline (nsec since 1970)
}
  • pr, pw: 内部的なパイプで、pollServerへのウェイクアップ通知に使用されます。
  • poll: OS固有のポーリングメカニズム(epoll, kqueueなど)を抽象化したインターフェース(pollster)へのポインタです。
  • sync.Mutex: pendingマップとdeadlineへのアクセスを保護するためのミューテックスです。
  • pending: ポーリングを待っているnetFDのマップです。キーはファイルディスクリプタと読み書きのモードを組み合わせたものです。
  • deadline: 次に期限切れになるデッドラインの時刻(ナノ秒単位)です。

newPollServer() 関数

func newPollServer() (s *pollServer, err error) {
	// ... (パイプの作成、ノンブロッキング設定、pollsterの初期化など)
	go s.Run() // ポーリングループを別ゴルーチンで開始
	return s, nil
}

新しいpollServerインスタンスを作成し、内部的なパイプを設定し、OS固有のポーリングフック(pollster)を初期化します。そして、s.Run()を新しいゴルーチンで起動し、ポーリングループを開始します。

AddFD(fd *netFD, mode int) error メソッド

func (s *pollServer) AddFD(fd *netFD, mode int) error {
	s.Lock()
	// ... (FDの有効性チェック、デッドラインの更新、pollsterへのFD登録)
	s.Unlock()
	if wake || doWakeup {
		s.Wakeup() // 必要に応じてポーリングループをウェイクアップ
	}
	return nil
}

指定されたnetFDpollServerの監視リストに追加します。modeは読み込み('r')または書き込み('w')を示します。デッドラインが設定されている場合、pollServerの次のデッドラインを更新し、必要であればポーリングループをウェイクアップします。

Evict(fd *netFD) bool メソッド

func (s *pollServer) Evict(fd *netFD) bool {
	// ... (pendingマップからFDを削除、pollsterからFDを削除、WakeFDを呼び出して待機中のゴルーチンを解除)
	return doWakeup
}

pollServerの監視リストからnetFDを削除し、そのFDで待機しているゴルーチンを解除します。主にnetFD.Close()時に呼び出されます。

Wakeup() メソッド

func (s *pollServer) Wakeup() { s.pw.Write(wakeupbuf[0:]) }

内部パイプに少量のデータを書き込むことで、pollServerRunループをウェイクアップします。これにより、pollServerはすぐにポーリング状態を再評価できます。

Run() メソッド

func (s *pollServer) Run() {
	s.Lock()
	defer s.Unlock()
	for {
		// ... (タイムアウト計算、pollster.WaitFD呼び出し)
		if fd < 0 {
			// Timeout happened.
			s.CheckDeadlines() // タイムアウトが発生した場合、デッドラインをチェック
			continue
		}
		if fd == int(s.pr.Fd()) {
			// Drain our wakeup pipe
			s.pr.Read(scratch[0:])
			s.CheckDeadlines() // ウェイクアップされた場合もデッドラインをチェック
		} else {
			netfd := s.LookupFD(fd, mode) // 準備ができたFDを検索
			if netfd == nil {
				// ... (既に削除されているFDの場合の処理)
				continue
			}
			s.WakeFD(netfd, mode, nil) // 待機中のゴルーチンを解除
		}
	}
}

pollServerのメインのポーリングループです。pollster.WaitFDを呼び出してI/Oイベントを待ちます。タイムアウトが発生した場合や、内部パイプ経由でウェイクアップされた場合、CheckDeadlines()を呼び出して期限切れのデッドラインを処理します。I/Oが準備できたFDが見つかった場合、LookupFDで対応するnetFDを取得し、WakeFDを呼び出して待機中のゴルーチンを解除します。

pollServerInit(fd *netFD) error 関数

func pollServerInit(fd *netFD) error {
	pollN := runtime.GOMAXPROCS(0)
	if pollN > pollMaxN {
		pollN = pollMaxN
	}
	k := fd.sysfd % pollN // FDに基づいてpollServerを選択
	startServersOnce[k]() // 選択されたpollServerを一度だけ初期化
	fd.pollServer = pollservers[k]
	fd.cr = make(chan error, 1) // 読み込み完了チャネル
	fd.cw = make(chan error, 1) // 書き込み完了チャネル
	return nil
}

netFDが作成される際に呼び出され、そのnetFDに適切なpollServerを割り当て、読み書き完了用のチャネルを初期化します。複数のpollServerが存在する場合、FDの番号に基づいて分散させます。

(s *pollServer) Close(fd *netFD) メソッド

func (s *pollServer) Close(fd *netFD) {
	// 現在は何も処理しないが、将来的にカスタム終了処理を追加するためのフック
}

netFDがクローズされる際に呼び出されます。このコミット時点では具体的な処理は含まれていませんが、将来的にpollServerが管理するリソースのクリーンアップなど、カスタムの終了処理を追加するためのプレースホルダーとして機能します。

src/pkg/net/fd_unix.go (変更点)

newFD 関数

func newFD(fd, family, sotype int, net string) (*netFD, error) {
	// ...
	netfd := &netFD{
		// ...
	}
	if err := pollServerInit(netfd); err != nil { // pollServerInitを呼び出すように変更
		return nil, err
	}
	return netfd, nil
}

netFDの初期化ロジックが変更され、pollServerInit関数を呼び出すことで、pollServerの割り当てとチャネルの初期化が行われるようになりました。これにより、newFDのコードが簡潔になり、ポーリング関連の初期化ロジックがpollServerInitに集約されました。

decref メソッド

func (fd *netFD) decref() {
	// ...
	if fd.closing && fd.sysref == 0 && fd.sysfile != nil {
		fd.sysfile.Close()
		fd.pollServer.Close(fd) // pollServer.Closeを呼び出すように変更
		fd.sysfile = nil
		fd.sysfd = -1
	}
}

netFDの参照カウントがゼロになり、クローズされる際に、fd.pollServer.Close(fd)が呼び出されるようになりました。これにより、pollServernetFDの終了を認識し、必要に応じて関連するリソースをクリーンアップできます。

関連リンク

参考にした情報源リンク