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

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

このコミットは、Go言語の標準ライブラリである os/exec パッケージ内のテスト TestHelperProcess において、Plan 9 オペレーティングシステム上でのファイルディスクリプタ (FD) のチェックを無効化する変更を導入しています。Plan 9 環境では、テストが予期しないファイルディスクリプタのリークを示すため、一時的にこのチェックをスキップし、問題の根本原因の調査を継続するための措置が取られました。

コミット

commit 1f0ca748c24e84fa6849e1d9669232dd99cd61ad
Author: David du Colombier <0intro@gmail.com>
Date:   Mon Jan 13 23:03:22 2014 +0100

    os/exec: disable fd check in TestHelperProcess on Plan 9
    
    On Plan 9, we can observe the following open file descriptors:
    
      0 r  c    0 (000000000000000a   0 00)     0        0 /dev/null
      1 rw |    0 (0000000001df6742   0 00) 65536       54 #|/data1
      2 rw |    0 (0000000001df6782   0 00) 65536        0 #|/data1
      3 rw M 1956 (0000000000d66dd2   0 00)  8192       12 /tmp/333163398
      4 r  c    0 (0000000000000001   0 00)     0      528 /dev/bintime
      5 r  M 1956 (0000000000d66dd1 854 00)  8192        0 /tmp/go-build843954301/os/exec/_test/exec.test
      6 r  M 1956 (0000000000d66dd1 854 00)  8192        0 /tmp/go-build843954301/os/exec/_test/exec.test
      7 r  M 1956 (0000000000d66dd1 854 00)  8192        0 /tmp/go-build843954301/os/exec/_test/exec.test
      8 r  M 1956 (0000000000d66dd1 854 00)  8192        0 /tmp/go-build843954301/os/exec/_test/exec.test
      9 r  M 1956 (0000000000d66dd1 854 00)  8192        0 /tmp/go-build843954301/os/exec/_test/exec.test
     10 r  M 1956 (0000000000d66dd1 854 00)  8192        0 /tmp/go-build843954301/os/exec/_test/exec.test
     11 r  c    0 (000000000000000f   0 00)     0       32 /dev/random
     12 r  M 1956 (0000000000d66dd1 854 00)  8192        0 /tmp/go-build843954301/os/exec/_test/exec.test
     13 r  c    0 (000000000000000a   0 00)     0        0 /dev/null
     14 rw |    0 (0000000001df6801   0 00) 65536        0 #|/data
     15 rw |    0 (0000000001df6802   0 00) 65536     1275 #|/data1
    
    R=rsc, bradfitz, aram
    CC=golang-codereviews
    https://golang.org/cl/51420044

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

https://github.com/golang/go/commit/1f0ca748c24e84fa6849e1d9669232dd99cd61ad

元コミット内容

このコミットは、os/exec パッケージのテスト TestHelperProcess において、Plan 9 環境でファイルディスクリプタのチェックを無効化することを目的としています。コミットメッセージには、Plan 9 上で TestHelperProcess を実行した際に観測された、予期せぬ開かれたファイルディスクリプタのリストが示されています。これは、テストが期待する状態(特定のファイルディスクリプタのみが開かれている状態)と異なるため、テストが失敗する原因となっていました。

変更の背景

Go言語の os/exec パッケージは、外部コマンドの実行を扱うための機能を提供します。このパッケージのテストは、コマンドの実行がシステムリソース(特にファイルディスクリプタ)を適切に管理していることを確認するために重要です。TestHelperProcess は、子プロセスが親プロセスから不要なファイルディスクリプタを継承していないかを検証するテストの一つです。

しかし、Plan 9 オペレーティングシステム上では、このテストが予期せぬファイルディスクリプタのリークを報告していました。コミットメッセージに示されているように、テスト実行時に多数のファイルディスクリプタが開かれている状態が観測され、これがテストの失敗につながっていました。この問題は、GoのIssue 7118 (http://golang.org/issue/7118) として報告されており、根本原因の特定と解決にはさらなる調査が必要でした。

このコミットは、根本原因が特定されるまでの間、Plan 9 上での TestHelperProcess の失敗を回避し、CI/CDパイプラインのブロックを防ぐための一時的な措置として導入されました。これにより、他のOSでのテストの健全性を維持しつつ、Plan 9 特有の問題の調査を継続することが可能になります。

前提知識の解説

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

ファイルディスクリプタは、Unix系オペレーティングシステムにおいて、プロセスが開いているファイルやその他のI/Oリソース(ソケット、パイプなど)を参照するために使用される抽象的な識別子です。各プロセスは、0、1、2といった非負の整数値で表されるファイルディスクリプタのテーブルを持っています。

  • 0: 標準入力 (stdin)
  • 1: 標準出力 (stdout)
  • 2: 標準エラー出力 (stderr) これらは通常、プロセスが起動する際に自動的に開かれます。

プログラムがファイルを開いたり、ネットワーク接続を確立したりすると、新しいファイルディスクリプタが割り当てられます。プロセスが終了する際には、通常、開いているすべてのファイルディスクリプタが自動的に閉じられます。しかし、プログラムのバグや特定のシステム動作により、不要なファイルディスクリプタが閉じられずに残ってしまうことがあり、これを「ファイルディスクリプタのリーク」と呼びます。リークが発生すると、システムリソースの枯渇やパフォーマンスの低下につながる可能性があります。

os/exec パッケージと子プロセス

Go言語の os/exec パッケージは、Goプログラムから外部コマンドを実行するための機能を提供します。exec.Command を使用してコマンドを構築し、RunOutputCombinedOutput などのメソッドで実行します。 子プロセスは、親プロセスからファイルディスクリプタを継承することが一般的です。しかし、セキュリティやリソース管理の観点から、子プロセスには必要最小限のファイルディスクリプタのみを継承させることが望ましいとされます。os/exec パッケージは、この継承を制御するためのメカニズムを提供しており、テストではこの制御が正しく機能しているかを検証します。

Plan 9 オペレーティングシステム

Plan 9 from Bell Labsは、Unixの設計思想をさらに推し進めた分散オペレーティングシステムです。Unixが「すべてをファイルとして扱う」という哲学を持つのに対し、Plan 9は「すべてをファイルシステムとして扱う」という哲学を持っています。これは、デバイス、プロセス、ネットワークリソースなど、システム内のあらゆるものがファイルシステム階層内のファイルとして表現され、標準的なファイル操作(読み書き、オープン、クローズなど)を通じてアクセスできることを意味します。

Plan 9のファイルディスクリプタの管理やプロセスの状態表現は、一般的なUnix系システムとは異なる場合があります。例えば、プロセスに関する情報は /proc ファイルシステムを通じて提供されますが、その構造やアクセス方法はLinuxなどのシステムとは異なります。この違いが、GoのテストがPlan 9上で予期せぬファイルディスクリプタを検出する原因となる可能性があります。

Go Issue 3955と7118

  • Issue 3955 (http://golang.org/issue/3955): このIssueは、os/exec のテストにおける /dev/urandom のファイルディスクリプタのクローンに関する問題に言及しています。一部のシステムでは、/dev/urandom を開くと、そのファイルディスクリプタが予期せずクローンされ、子プロセスに継承されることがありました。これは、テストが期待するファイルディスクリプタの数と実際の数が異なる原因となっていました。

  • Issue 7118 (http://golang.org/issue/7118): このIssueは、まさにこのコミットが対処しようとしているPlan 9上でのファイルディスクリプタリークの問題を追跡するために作成されました。コミットメッセージの TODO コメントにも明記されており、Plan 9環境で TestHelperProcess が失敗する根本原因を特定し、解決するための調査が継続中であることを示しています。

技術的詳細

TestHelperProcess は、Goのテストフレームワークの一部として、os/exec パッケージが外部コマンドを起動した際に、その子プロセスが親プロセスから不要なファイルディスクリプタを継承していないことを検証します。このテストは、子プロセスが起動した直後に、開かれているファイルディスクリプタのリストを取得し、その数が期待される数(通常は標準入出力の3つ)と一致するかどうかを確認します。

ファイルディスクリプタのリストを取得する方法は、オペレーティングシステムによって異なります。

  • BSD系OS (DragonFly, FreeBSD, NetBSD, OpenBSD): fstat コマンドを使用します。fstat -p <pid> のように、特定のプロセスのファイルディスクリプタ情報を取得します。
  • Plan 9: このコミット以前は、Plan 9でも fstat を使用しようとしていた可能性がありますが、Plan 9には fstat コマンドは存在しません。代わりに、Plan 9では /proc/<pid>/fd のようなパスを通じてプロセスのファイルディスクリプタ情報をファイルシステムとして公開しています。このため、/bin/cat /proc/<pid>/fd のようにして情報を取得するのが一般的です。

このコミットの主要な変更点は、Plan 9 環境でのファイルディスクリプタのチェック方法と、そのチェックを一時的に無効化するロジックの導入です。

  1. ファイルディスクリプタ情報取得コマンドの変更: Plan 9 では fstat コマンドが存在しないため、TestHelperProcess がファイルディスクリプタ情報を取得するために使用するコマンドを /bin/cat に変更しました。これにより、Plan 9 の /proc ファイルシステムを通じてファイルディスクリプタの情報を読み取ることが可能になります。

  2. Plan 9 での FD チェックの無効化: TestHelperProcess 内で、runtime.GOOS が "plan9" の場合、ファイルディスクリプタのリークチェックをスキップする TODO コメントが追加されました。これは、Plan 9 上で予期せぬファイルディスクリプタのリークが発生していることが確認されており、その根本原因が特定されるまで、テストの失敗を防ぐための一時的な措置です。コミットメッセージに示されているように、多数のファイルディスクリプタがテスト実行時に開かれていることが観測されており、これらがテストの期待値と一致しないため、テストが失敗していました。

  3. FD 情報表示コマンドの引数変更: ファイルディスクリプタのリークが検出された際に、詳細な情報を出力するために実行されるコマンドの引数が、Plan 9 向けに調整されました。Plan 9 では /proc/<pid>/fd の形式でファイルディスクリプタのパスが指定されるため、exec.Command に渡す引数もそれに合わせて変更されています。

この変更は、Plan 9 という特定のOS環境におけるGoのテストの安定性を向上させるためのものであり、Goのクロスプラットフォーム対応の複雑さの一端を示しています。

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

変更は src/pkg/os/exec/exec_test.go ファイルに集中しています。

--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.go
@@ -480,6 +480,8 @@ func TestHelperProcess(*testing.T) {
 	switch runtime.GOOS {
 	case "dragonfly", "freebsd", "netbsd", "openbsd":
 		ofcmd = "fstat"
+	case "plan9":
+		ofcmd = "/bin/cat"
 	}
 
 	args := os.Args
@@ -570,6 +572,10 @@ func TestHelperProcess(*testing.T) {
 			// the cloned file descriptors that result from opening
 			// /dev/urandom.
 			// http://golang.org/issue/3955
+		case "plan9":
+			// TODO(0intro): Determine why Plan 9 is leaking
+			// file descriptors.
+			// http://golang.org/issue/7118
 		case "solaris":
 			// TODO(aram): This fails on Solaris because libc opens
 			// its own files, as it sees fit. Darwin does the same,
@@ -585,7 +591,14 @@ func TestHelperProcess(*testing.T) {
 				if got := f.Fd(); got != wantfd {
 					fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd)
 					out, _ := exec.Command(ofcmd, "-p", fmt.Sprint(os.Getpid())).CombinedOutput()
+					var args []string
+					switch runtime.GOOS {
+					case "plan9":
+						args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())}
+					default:
+						args = []string{"-p", fmt.Sprint(os.Getpid())}
+					}
+					out, _ := exec.Command(ofcmd, args...).CombinedOutput()
 					fmt.Print(string(out))
 					os.Exit(1)
 				}

コアとなるコードの解説

  1. ofcmd の設定 (L480-482): ofcmd 変数は、開いているファイルディスクリプタの情報を取得するために使用されるコマンドを格納します。

    • 既存の dragonfly, freebsd, netbsd, openbsd のケースでは fstat が設定されています。
    • 新たに case "plan9": が追加され、ofcmd = "/bin/cat" が設定されました。これは、Plan 9 では fstat の代わりに /bin/cat を使用して /proc ファイルシステムからFD情報を読み取るためです。
  2. Plan 9 での FD チェックのスキップ (L570-575): TestHelperProcess 内のファイルディスクリプタのリークチェックを行う switch runtime.GOOS ブロックに、case "plan9": が追加されました。

    • このブロック内には TODO コメントがあり、「Plan 9 がファイルディスクリプタをリークしている理由を特定する」という課題 (http://golang.org/issue/7118) が示されています。
    • この case ブロックの追加により、Plan 9 環境では、この特定のファイルディスクリプタリークチェックが実質的にスキップされることになります。これは、テストが失敗するのを防ぐための一時的な回避策です。
  3. FD 情報表示コマンドの引数変更 (L585-594): ファイルディスクリプタのリークが検出された際に、デバッグ情報として開いているFDのリストを出力するために exec.Command が使用されます。このコマンドに渡す引数が runtime.GOOS に応じて分岐されるようになりました。

    • 既存のコードでは、ofcmd (例: fstat) に対して -p とプロセスIDを引数として渡していました。
    • 新たに switch runtime.GOOS が導入され、case "plan9": の場合、args[]string{fmt.Sprintf("/proc/%d/fd", os.Getpid())} となります。これは、Plan 9 の /proc ファイルシステムにおけるFD情報のパスを指定しています。
    • default: ケースでは、以前と同様に -p とプロセスIDを引数として渡します。 この変更により、Plan 9 環境で正しいコマンドと引数を使用してFD情報を取得し、デバッグ出力に含めることができるようになりました。

これらの変更は、Plan 9 という特定のOS環境におけるGoのテストの互換性と安定性を向上させるためのものであり、Goのクロスプラットフォーム対応の複雑さの一端を示しています。

関連リンク

参考にした情報源リンク

  • Plan 9 from Bell Labs: https://9p.io/plan9/
  • Unix系OSにおけるファイルディスクリプタの概念
  • Go言語 os/exec パッケージのドキュメント