[インデックス 10961] ファイルの概要
このコミットは、Go言語のos/exec
パッケージにおけるExtraFiles
テストの挙動に関するものです。特に、macOS (Darwin) 環境で子プロセスが予期せぬファイルディスクリプタを継承するバグ(Go issue 2603)が存在するため、この環境での関連テストを一時的に無効化し、同時にこのバグに関するコメントをコードに追加しています。また、テストの堅牢性を高めるために、TLSルート証明書のロードがファイルディスクリプタのリークを引き起こさないことを確認するコードが追加されています。
コミット
commit 90d56e072f8125be85b77f085e3f576d6533c29d
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Dec 21 17:08:16 2011 -0800
exec: disable the ExtraFiles test on darwin
Still a mystery. New issue 2603 filed.
R=golang-dev, dsymonds, iant
CC=golang-dev
https://golang.org/cl/5503063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/90d56e072f8125be85b77f085e3f576d6533c29d
元コミット内容
exec: disable the ExtraFiles test on darwin
このコミットは、Go言語のos/exec
パッケージにおいて、macOS (Darwin) 環境でのExtraFiles
テストを無効化するものです。これは、未解決のバグ(Go issue 2603)に関連しており、子プロセスが予期せぬファイルディスクリプタを継承する問題が原因でテストが不安定になっていたためです。
変更の背景
この変更の背景には、Go言語のos/exec
パッケージが提供するプロセス実行機能において、macOS (Darwin) 環境で特定のバグが確認されたことがあります。具体的には、Cmd
構造体のExtraFiles
フィールドを使用して子プロセスに追加のファイルディスクリプタを継承させる際、OS X 10.6で子プロセスが意図しないファイルディスクリプタを継承してしまうという問題が発生していました。
この問題は、os/exec
パッケージのテストスイートに含まれるTestExtraFiles
テストが、Darwin環境で不安定に失敗する原因となっていました。テストが不安定であると、開発者はコードの変更が本当にバグを修正したのか、あるいは新たなバグを導入したのかを判断することが困難になります。
コミットメッセージにある「Still a mystery. New issue 2603 filed.」という記述は、この問題の原因が特定できておらず、新たなGo issue 2603として報告されたことを示しています。このコミットは、根本原因が解決されるまでの間、テストのCI/CDパイプラインを安定させるための一時的な措置として、Darwin環境での問題のあるテスト部分をスキップすることを目的としています。
また、このコミットでは、TLSルート証明書のロードがCgo(GoからC言語のコードを呼び出すメカニズム)を介して行われる際に、ファイルディスクリプタのリークが発生しないことを確認するためのテストコードも追加されています。これは、ExtraFiles
のバグとは直接関係ありませんが、ファイルディスクリプタのリークという同様の懸念事項に対処するための、テストの堅牢性向上の一環として行われました。
前提知識の解説
Go言語のos/exec
パッケージ
os/exec
パッケージは、外部コマンドを実行するための機能を提供します。このパッケージを使用することで、Goプログラムからシェルコマンドや他の実行可能ファイルを起動し、その入出力を制御することができます。
exec.Command
: 実行するコマンドと引数を指定してCmd
構造体を作成します。Cmd.Run()
: コマンドを実行し、完了を待ちます。Cmd.Start()
: コマンドを非同期で実行します。Cmd.Wait()
:Start()
で起動したコマンドの完了を待ちます。Cmd.Stdin
,Cmd.Stdout
,Cmd.Stderr
: 子プロセスの標準入力、標準出力、標準エラーを制御するためのフィールドです。Cmd.ExtraFiles
: 子プロセスに継承させる追加のファイルディスクリプタを指定するためのフィールドです。これは、標準入出力以外のファイルやソケットなどを子プロセスと共有したい場合に利用されます。
ファイルディスクリプタ (File Descriptor, FD)
ファイルディスクリプタは、Unix系オペレーティングシステムにおいて、開いているファイルやソケット、パイプなどのI/Oリソースを識別するためにカーネルがプロセスに割り当てる非負の整数です。プロセスがファイルを開いたり、ソケットを作成したりすると、カーネルは対応するファイルディスクリプタを返します。子プロセスは通常、親プロセスからファイルディスクリプタを継承します。
httptest.NewTLSServer
とTLSルート証明書のロード
net/http/httptest
パッケージ: HTTPサーバーのテストを容易にするためのユーティリティを提供します。httptest.NewTLSServer
: テスト目的でTLS (Transport Layer Security) を使用するHTTPサーバーを作成します。このサーバーは自己署名証明書を使用するため、クライアントが接続するにはその証明書を信頼する必要があります。- TLSルート証明書のロード: TLS通信を行う際、クライアントはサーバーから提示された証明書が信頼できる認証局 (CA) によって署名されていることを検証します。この検証のために、システムにインストールされている信頼されたルート証明書がロードされます。Go言語では、このルート証明書のロード処理がCgo(GoとC言語の相互運用機能)を介して行われることがあり、その際にファイルディスクリプタのリークが発生する可能性が懸念されることがあります。
runtime.GOOS
runtime
パッケージは、Goプログラムが実行されている環境に関する情報を提供します。runtime.GOOS
は、プログラムがコンパイルまたは実行されているオペレーティングシステムの名前(例: "linux"
, "windows"
, "darwin"
)を文字列で返します。これにより、特定のOSに依存するコードを条件付きで実行することができます。
lsof
コマンド
lsof
(list open files) は、Unix系オペレーティングシステムで実行中のプロセスが開いているファイルやネットワーク接続を一覧表示するためのコマンドラインユーティリティです。デバッグ目的で、どのプロセスがどのファイルディスクリプタを使用しているかを特定するのに非常に役立ちます。
技術的詳細
このコミットは、主に以下の3つの技術的変更を含んでいます。
-
os/exec.go
におけるExtraFiles
フィールドのコメント更新:Cmd
構造体のExtraFiles
フィールドのコメントに、macOS (OS X 10.6) で子プロセスが追加のファイルディスクリプタを継承するバグ(Go issue 2603)に関する注意書きが追加されました。これは、この機能を使用する開発者に対して、既知の問題を明確に伝えるためのものです。--- a/src/pkg/os/exec/exec.go +++ b/src/pkg/os/exec/exec.go @@ -67,6 +67,9 @@ type Cmd struct { // ExtraFiles specifies additional open files to be inherited by the // new process. It does not include standard input, standard output, or // standard error. If non-nil, entry i becomes file descriptor 3+i. +// +// BUG: on OS X 10.6, child processes may sometimes inherit extra fds. +// http://golang.org/issue/2603 ExtraFiles []*os.File // SysProcAttr holds optional, operating system-specific attributes.
-
os/exec/exec_test.go
におけるTLSサーバー関連のテストコード追加:TestExtraFiles
関数内に、httptest.NewTLSServer
とhttp.Get
を使用してTLSルート証明書のロードを強制するコードが追加されました。これは、Cgoを介したTLS証明書のロード処理がファイルディスクリプタのリークを引き起こさないことを確認するためのものです。このテストは、ExtraFiles
のバグとは直接関係ありませんが、ファイルディスクリプタの健全性を確認するという点で関連性があります。--- a/src/pkg/os/exec/exec_test.go +++ b/src/pkg/os/exec/exec_test.go @@ -156,6 +158,14 @@ func TestExtraFiles(t *testing.T) { } defer ln.Close() +// Force TLS root certs to be loaded (which might involve +// cgo), to make sure none of that potential C code leaks fds. +ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello")) +})) +defer ts.Close() +http.Get(ts.URL) // ignore result; just calling to force root cert loading + tf, err := ioutil.TempFile("", "") if err != nil { t.Fatalf("TempFile: %v", err)
-
os/exec/exec_test.go
におけるDarwin環境でのFDリークチェックのスキップ:TestHelperProcess
関数内で、runtime.GOOS
が"darwin"
(macOS)である場合に、追加のファイルディスクリプタリークチェックをスキップする条件分岐が追加されました。これは、Go issue 2603で報告されているバグにより、Darwin環境ではこのチェックが不安定に失敗するため、テストの安定性を確保するための一時的な措置です。他のOSでは引き続きこのチェックが実行されます。--- a/src/pkg/os/exec/exec_test.go +++ b/src/pkg/os/exec/exec_test.go @@ -256,23 +266,31 @@ func TestHelperProcess(*testing.T) { fmt.Printf("ReadAll from fd 3: %v", err) os.Exit(1) } - // Now verify that there are no other open fds. - var files []*os.File - for wantfd := os.Stderr.Fd() + 2; wantfd <= 100; wantfd++ { - f, err := os.Open(os.Args[0]) - if err != nil { - fmt.Printf("error opening file with expected fd %d: %v", wantfd, err) - os.Exit(1) - } - if got := f.Fd(); got != wantfd { - fmt.Printf("leaked parent file. fd = %d; want %d", got, wantfd) - fmt.Println(Command("lsof", "-p", fmt.Sprint(os.Getpid())).CombinedOutput()) - os.Exit(1) - } - files = append(files, f) - } - for _, f := range files { - f.Close() + switch runtime.GOOS { + case "darwin": + // TODO(bradfitz): broken? Sometimes. + // http://golang.org/issue/2603 + // Skip this additional part of the test for now. + default: + // Now verify that there are no other open fds. + var files []*os.File + for wantfd := os.Stderr.Fd() + 2; wantfd <= 100; wantfd++ { + f, err := os.Open(os.Args[0]) + if err != nil { + fmt.Printf("error opening file with expected fd %d: %v", wantfd, err) + os.Exit(1) + } + 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() + fmt.Print(string(out)) + os.Exit(1) + } + files = append(files, f) + } + for _, f := range files { + f.Close() + } } os.Stderr.Write(bs) case "exit":
コアとなるコードの変更箇所
src/pkg/os/exec/exec.go
Cmd
構造体のExtraFiles
フィールドのコメントに、OS X 10.6でのバグに関する記述が追加されました。
type Cmd struct {
// ...
// ExtraFiles specifies additional open files to be inherited by the
// new process. It does not include standard input, standard output, or
// standard error. If non-nil, entry i becomes file descriptor 3+i.
//
// BUG: on OS X 10.6, child processes may sometimes inherit extra fds.
// http://golang.org/issue/2603
ExtraFiles []*os.File
// ...
}
src/pkg/os/exec/exec_test.go
TestExtraFiles
関数内
TLSルート証明書のロードを強制するためのコードが追加されました。
func TestExtraFiles(t *testing.T) {
// ...
defer ln.Close()
// Force TLS root certs to be loaded (which might involve
// cgo), to make sure none of that potential C code leaks fds.
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
}))
defer ts.Close()
http.Get(ts.URL) // ignore result; just calling to force root cert loading
tf, err := ioutil.TempFile("", "")
// ...
}
TestHelperProcess
関数内
runtime.GOOS
が"darwin"
の場合に、ファイルディスクリプタのリークチェックをスキップする条件分岐が追加されました。
func TestHelperProcess(t *testing.T) {
// ...
switch runtime.GOOS {
case "darwin":
// TODO(bradfitz): broken? Sometimes.
// http://golang.org/issue/2603
// Skip this additional part of the test for now.
default:
// Now verify that there are no other open fds.
var files []*os.File
for wantfd := os.Stderr.Fd() + 2; wantfd <= 100; wantfd++ {
f, err := os.Open(os.Args[0])
if err != nil {
fmt.Printf("error opening file with expected fd %d: %v", wantfd, err)
os.Exit(1)
}
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()
fmt.Print(string(out))
os.Exit(1)
}
files = append(files, f)
}
for _, f := range files {
f.Close()
}
}
// ...
}
コアとなるコードの解説
src/pkg/os/exec/exec.go
の変更
ExtraFiles
フィールドのコメントに追加されたBUG
の記述は、Go言語のドキュメントやコードを読む開発者に対して、macOS環境での既知の制限やバグを明示的に伝えます。これにより、開発者はこの機能を使用する際に、特定のOSで予期せぬ挙動が発生する可能性があることを認識し、それに応じた対策を講じることができます。http://golang.org/issue/2603
へのリンクは、このバグに関する詳細な情報や進捗を確認するための参照を提供します。
src/pkg/os/exec/exec_test.go
の変更
TestExtraFiles
関数へのTLSサーバー関連コードの追加
このコードブロックは、httptest.NewTLSServer
を使用して一時的なTLSサーバーを起動し、そのURLに対してhttp.Get
リクエストを送信しています。この操作の目的は、TLSルート証明書がロードされるプロセスを強制的に実行することです。Go言語のTLS実装は、システムにインストールされているルート証明書をロードするためにCgo(GoとC言語の相互運用機能)を使用する場合があります。このCgo呼び出しの際に、ファイルディスクリプタのリークが発生しないことを確認することが、このテストの意図です。// ignore result; just calling to force root cert loading
というコメントがその目的を明確に示しています。これは、ExtraFiles
のテストとは直接関係ありませんが、ファイルディスクリプタの健全性という点で、os/exec
パッケージのテスト全体としての堅牢性を高めるための追加のチェックです。
TestHelperProcess
関数におけるDarwin環境でのFDリークチェックのスキップ
TestHelperProcess
関数は、os/exec
パッケージのテストヘルパーとして機能し、子プロセスが正しくファイルディスクリプタを継承し、余分なFDをリークしないことを検証します。しかし、runtime.GOOS
による条件分岐が導入され、"darwin"
(macOS)の場合には、この追加のFDリークチェックがスキップされるようになりました。
元のコードでは、os.Stderr.Fd() + 2
から100
までのファイルディスクリプタを順に開き、そのFDが期待される値(wantfd
)と一致するかどうかを確認していました。もし一致しない場合(つまり、子プロセスが親プロセスから予期せぬFDを継承している場合)、それはリークと判断され、テストは失敗します。さらに、lsof
コマンドを実行して、現在のプロセスが開いているファイルディスクリプタのリストを出力し、デバッグ情報を提供していました。
しかし、Go issue 2603で報告されているように、macOS環境ではこのFDリークチェックが不安定に失敗することがありました。これは、OS X 10.6の特定の挙動に起因するもので、Go言語のコードだけでは制御が難しい問題でした。そのため、このコミットでは、根本原因が解決されるまでの間、Darwin環境でのテストの安定性を確保するために、この部分のチェックを一時的に無効化する判断がなされました。// TODO(bradfitz): broken? Sometimes.
というコメントは、この問題が未解決であり、将来的に再検討されるべき課題であることを示唆しています。
この変更により、Darwin環境でのCI/CDパイプラインが安定し、他のOSでのFDリークチェックは引き続き有効であるため、全体的な品質は維持されます。
関連リンク
- Go issue 2603: https://golang.org/issue/2603
- Go CL 5503063: https://golang.org/cl/5503063
参考にした情報源リンク
- Go言語
os/exec
パッケージドキュメント: https://pkg.go.dev/os/exec - Go言語
net/http/httptest
パッケージドキュメント: https://pkg.go.dev/net/http/httptest - Go言語
runtime
パッケージドキュメント: https://pkg.go.dev/runtime - ファイルディスクリプタ (Wikipedia): https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%87%E3%82%A3%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%82%BF
- lsof (Wikipedia): https://ja.wikipedia.org/wiki/Lsof