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

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

このコミットは、Go言語の os/exec パッケージにおけるテスト TestExtraFiles の信頼性を向上させるための修正です。具体的には、テスト実行前に環境に漏洩している可能性のあるファイルディスクリプタを閉じ、また、オープンされているファイルディスクリプタをリストアップするためのコマンドをオペレーティングシステムに応じて適切に選択するように変更しています。これにより、テストの実行環境に依存しない、より堅牢なテストが実現されます。

コミット

commit 1677f1a1632ac2204ad6ed3e892b5beed8e1b654
Author: Joel Sing <jsing@google.com>
Date:   Tue Jan 31 22:09:06 2012 +1100

    os/exec: TestExtraFiles - close any leaked file descriptors

    Ensure that file descriptors have not already been leaked into our
    environment - close any that are open at the start of the
    TestExtraFiles test.

    Also use the appropriate command for listing open files.

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

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

https://github.com/golang/go/commit/1677f1a1632ac2204ad6ed3e892b5beed8e1b654

元コミット内容

os/exec: TestExtraFiles - close any leaked file descriptors

このコミットは、TestExtraFiles テストにおいて、環境に漏洩している可能性のあるファイルディスクリプタを閉じ、また、オープンされているファイルをリストアップするための適切なコマンドを使用するように修正します。

変更の背景

os/exec パッケージは、外部コマンドを実行するための機能を提供します。TestExtraFiles は、子プロセスにファイルディスクリプタが正しく継承されるか、または継承されないべきファイルディスクリプタが漏洩しないかをテストするものです。

このテストが不安定になる原因として、テストが実行される環境(特にCI/CD環境や開発者のローカル環境)において、以前のテスト実行や他のプロセスからファイルディスクリプタが漏洩し、それが現在のテストの期待値に影響を与える可能性がありました。例えば、テスト開始時に予期せぬファイルディスクリプタがオープンされていると、TestExtraFiles が「ファイルディスクリプタが漏洩した」と誤って判断してしまうことがあります。

また、オープンされているファイルディスクリプタをリストアップするために lsof コマンドが使用されていましたが、これはすべてのUnix系システムで利用可能であるとは限りません。特にBSD系のシステムでは fstat のような別のコマンドが使われることが一般的です。このため、テストが特定のOS環境に依存し、移植性が低いという問題がありました。

このコミットは、これらの問題を解決し、TestExtraFiles がより信頼性が高く、様々なオペレーティングシステムで安定して動作するようにすることを目的としています。

前提知識の解説

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

ファイルディスクリプタは、Unix系オペレーティングシステムにおいて、プロセスが開いているファイルやソケット、パイプなどのI/Oリソースを識別するために使用される整数値です。プロセスがファイルを開くと、カーネルは対応するファイルディスクリプタを割り当て、プロセスはそのディスクリプタを通じてI/O操作を行います。

  • 標準入出力: 通常、ファイルディスクリプタ 0 は標準入力 (stdin)、1 は標準出力 (stdout)、2 は標準エラー出力 (stderr) に割り当てられます。
  • 継承: 子プロセスは、親プロセスからファイルディスクリプタを継承することが一般的です。しかし、セキュリティやリソース管理の観点から、特定のファイルディスクリプタを子プロセスに継承させたくない場合があります。

os/exec パッケージ

Go言語の標準ライブラリの一部で、外部コマンドを実行するための機能を提供します。exec.Command 関数を使用してコマンドを構築し、Run, Output, CombinedOutput などのメソッドで実行します。Cmd 構造体には ExtraFiles フィールドがあり、子プロセスに追加で継承させたいファイルディスクリプタを指定できます。

syscall パッケージ

Go言語の標準ライブラリの一部で、オペレーティングシステムの低レベルなシステムコールにアクセスするための機能を提供します。このコミットでは syscall.Close を使用して、特定のファイルディスクリプタを明示的に閉じています。

lsof コマンド

lsof (list open files) は、Unix系システムで、プロセスが開いているファイルやネットワーク接続を一覧表示するためのコマンドです。非常に強力で、デバッグやシステム管理によく使われます。

fstat コマンド

fstat は、BSD系のシステム(FreeBSD, NetBSD, OpenBSDなど)で、オープンされているファイルディスクリプタに関する情報を表示するために使用されるコマンドです。lsof と同様の目的で使用されますが、コマンド名と出力形式が異なります。

技術的詳細

このコミットは、src/pkg/os/exec/exec_test.go ファイルに対して2つの主要な変更を加えています。

  1. テスト開始時のファイルディスクリプタのクリーンアップ: TestExtraFiles 関数の冒頭に、os.Stderr.Fd() + 1 から 101 までのファイルディスクリプタをループで閉じようとする処理が追加されました。これは、テストが開始される前に、予期せずオープンされたままになっている可能性のあるファイルディスクリプタ(例えば、以前のテスト実行で適切に閉じられなかったもの)を強制的に閉じることで、テスト環境をクリーンな状態にするためのものです。 syscall.Close(fd) が使用されており、これは指定されたファイルディスクリプタを閉じます。エラーが発生しない場合(つまり、そのファイルディスクリプタが実際にオープンされており、閉じられた場合)、t.Logf でその旨がログに出力されます。この範囲(101まで)は、一般的なシステムでユーザープロセスが使用するファイルディスクリプタの数がこの範囲内に収まることを想定したものです。

  2. オープンファイルリストコマンドのOS依存の選択: TestHelperProcess 関数内で、オープンされているファイルディスクリプタをリストアップするために使用するコマンド (ofcmd) を、実行中のオペレーティングシステム (runtime.GOOS) に応じて動的に選択するように変更されました。

    • デフォルトでは lsof が使用されます。
    • freebsd, netbsd, openbsd の場合は fstat が使用されます。 これにより、テストが様々なUnix系システムで正しく動作するようになり、特定のコマンドの存在に依存しなくなります。この ofcmd 変数は、後続の Command(ofcmd, "-p", fmt.Sprint(os.Getpid())).CombinedOutput() の呼び出しで使用され、現在のプロセスのオープンファイルディスクリプタ情報を取得します。

これらの変更により、TestExtraFiles はより堅牢になり、テストの再現性と信頼性が向上しました。

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

--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.go
@@ -17,6 +17,7 @@ import (
  	"runtime"
  	"strconv"
  	"strings"
+	"syscall"
  	"testing"
  )

@@ -150,6 +151,15 @@ func TestExtraFiles(t *testing.T) {
  		return
  	}

+	// Ensure that file descriptors have not already been leaked into
+	// our environment.
+	for fd := os.Stderr.Fd() + 1; fd <= 101; fd++ {
+		err := syscall.Close(fd)
+		if err == nil {
+			t.Logf("Something already leaked - closed fd %d", fd)
+		}
+	}
+
  	// Force network usage, to verify the epoll (or whatever) fd
  	// doesn't leak to the child,
  	ln, err := net.Listen("tcp", "127.0.0.1:0")
@@ -202,6 +212,13 @@ func TestHelperProcess(*testing.T) {
  	}\n"
  	defer os.Exit(0)

+	// Determine which command to use to display open files.
+	ofcmd := "lsof"
+	switch runtime.GOOS {
+	case "freebsd", "netbsd", "openbsd":
+		ofcmd = "fstat"
+	}
+
  	args := os.Args
  	for len(args) > 0 {
  	 	if args[0] == "--" {
@@ -282,7 +299,7 @@ func TestHelperProcess(*testing.T) {
  				}
  				if got := f.Fd(); got != wantfd {
  					fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd)
-					out, _ := Command("lsof", "-p", fmt.Sprint(os.Getpid())).CombinedOutput()
+					out, _ := Command(ofcmd, "-p", fmt.Sprint(os.Getpid())).CombinedOutput()
  					fmt.Print(string(out))\n"
  					os.Exit(1)
  				}

コアとなるコードの解説

1. ファイルディスクリプタのクリーンアップ

	// Ensure that file descriptors have not already been leaked into
	// our environment.
	for fd := os.Stderr.Fd() + 1; fd <= 101; fd++ {
		err := syscall.Close(fd)
		if err == nil {
			t.Logf("Something already leaked - closed fd %d", fd)
		}
	}
  • os.Stderr.Fd(): 標準エラー出力のファイルディスクリプタの番号を取得します。通常は 2 です。
  • fd := os.Stderr.Fd() + 1: ループは標準エラー出力の次のファイルディスクリプタから開始します。これは、標準入出力 (0, 1, 2) は通常閉じないためです。
  • fd <= 101: ファイルディスクリプタの番号が 101 以下である限りループを続けます。これは、一般的なシステムでユーザープロセスが使用するファイルディスクリプタの数がこの範囲内に収まるという経験則に基づいています。
  • syscall.Close(fd): 指定されたファイルディスクリプタ fd を閉じようとします。
  • if err == nil: syscall.Close がエラーを返さなかった場合、それはファイルディスクリプタが正常に閉じられたことを意味します。つまり、そのファイルディスクリプタは以前にオープンされており、このテストの実行前に漏洩していたことになります。
  • t.Logf(...): テストのログに、漏洩していたファイルディスクリプタが閉じられたことを記録します。これはデバッグに役立ちます。

このコードブロックは、テストの実行環境を予測可能な状態に保ち、以前のテストや他のプロセスからの「汚染」を防ぐことで、TestExtraFiles の信頼性を高めます。

2. オープンファイルリストコマンドのOS依存の選択

	// Determine which command to use to display open files.
	ofcmd := "lsof"
	switch runtime.GOOS {
	case "freebsd", "netbsd", "openbsd":
		ofcmd = "fstat"
	}
  • ofcmd := "lsof": デフォルトで、オープンファイルをリストアップするコマンドとして lsof を設定します。これはLinuxなどの多くのUnix系システムで利用可能です。
  • switch runtime.GOOS: Goの runtime パッケージから現在のオペレーティングシステム (GOOS) の値を取得し、それに基づいて条件分岐を行います。
  • case "freebsd", "netbsd", "openbsd": もしOSがFreeBSD、NetBSD、またはOpenBSDのいずれかであれば、ofcmdfstat に変更します。これらのOSでは fstat がオープンファイル情報を取得するための標準的なコマンドです。

この変更により、テストは実行されるオペレーティングシステムに自動的に適応し、適切なコマンドを使用してオープンファイル情報を取得できるようになります。これにより、テストの移植性が向上し、異なるUnix系システム上でのテストの失敗を防ぎます。

3. コマンドの実行箇所の修正

-					out, _ := Command("lsof", "-p", fmt.Sprint(os.Getpid())).CombinedOutput()
+					out, _ := Command(ofcmd, "-p", fmt.Sprint(os.Getpid())).CombinedOutput()
  • 以前は lsof がハードコードされていましたが、上記の ofcmd 変数を使用するように変更されました。これにより、TestHelperProcess 内でオープンファイル情報を取得する際に、OSに応じた適切なコマンドが実行されるようになります。

関連リンク

参考にした情報源リンク

  • このコミット自体が主要な情報源です。
  • Go言語の os/exec パッケージのドキュメント
  • Go言語の syscall パッケージのドキュメント
  • lsof および fstat コマンドに関する一般的なUnix/Linuxドキュメント
  • ファイルディスクリプタに関する一般的なオペレーティングシステム理論