[インデックス 10955] ファイルの概要
このコミットは、Go言語の標準ライブラリであるos/exec
パッケージのテストコードに対する改善です。具体的には、外部コマンドの実行に関連するテストにおいて、ファイルオープンに失敗した場合にlsof
コマンドの出力をダンプするように変更されています。これにより、テストが失敗した際に、どのファイルディスクリプタが開かれているか、または開かれていないかを診断しやすくなり、デバッグの効率が向上します。
コミット
os/exec: dump lsof on failure
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/914ab8a23f2beec9ff98d211f8aa9f6ee0ab1fa1
元コミット内容
commit 914ab8a23f2beec9ff98d211f8aa9f6ee0ab1fa1
Author: Russ Cox <rsc@golang.org>
Date: Wed Dec 21 17:17:28 2011 -0500
os/exec: dump lsof on failure
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/5504063
変更の背景
この変更の背景には、os/exec
パッケージのテスト実行中に、特定のファイル操作(os.Open
)が予期せず失敗した場合のデバッグを容易にするという目的があります。ファイルオープンが失敗する原因は多岐にわたりますが、特にファイルディスクリプタのリークや、プロセスが期待するファイルにアクセスできない状況は、lsof
コマンドの出力によって詳細な情報が得られます。
コミットメッセージにある「dump lsof on failure」という記述から、テストが失敗した際に、その時点でのプロセスが保持しているファイルディスクリプタの状態を自動的に記録することで、問題の根本原因を特定するための診断情報を増やす意図が読み取れます。これは、テストの信頼性を高め、開発者が問題を迅速に解決できるようにするための改善策です。
前提知識の解説
Go言語のos/exec
パッケージ
os/exec
パッケージは、Goプログラムから外部コマンドを実行するための機能を提供します。これにより、シェルコマンドや他の実行可能ファイルをGoアプリケーション内で起動し、その標準入力、標準出力、標準エラーを制御することができます。例えば、ls
コマンドを実行したり、別のGoプログラムを子プロセスとして起動したりする際に使用されます。
lsof
コマンド
lsof
は"list open files"の略で、Unix系オペレーティングシステムで利用できるコマンドラインユーティリティです。このコマンドは、システム上で開かれているすべてのファイル(通常のファイル、ディレクトリ、ネットワークソケット、パイプなど)と、それらを開いているプロセスに関する詳細な情報を表示します。
lsof
は、特に以下のようなデバッグシナリオで非常に役立ちます。
- ファイルディスクリプタのリークの特定: プロセスが不要になったファイルディスクリプタを閉じ忘れている場合に、
lsof
で確認できます。 - リソースの競合: どのプロセスが特定のファイルやポートを使用しているかを特定します。
- デッドロックの診断: プロセスがリソースを待機している状況を理解するのに役立ちます。
このコミットでは、lsof -p <PID>
という形式で使用されており、これは特定のプロセスID(PID)が現在開いているファイルの一覧を表示するものです。
os.Getpid()
Go言語のos
パッケージに含まれる関数で、現在のプロセスのプロセスID(PID)を返します。lsof
コマンドに渡すことで、Goプログラム自身の開いているファイルディスクリプタを調べることができます。
CombinedOutput()
os/exec.Cmd
構造体のメソッドで、外部コマンドを実行し、その標準出力と標準エラー出力の両方を結合したバイトスライスとして返します。コマンドの実行結果をまとめて取得する際に便利です。
技術的詳細
この変更は、os/exec
パッケージのテストヘルパー関数TestHelperProcess
内に組み込まれています。このヘルパー関数は、os/exec
パッケージのテストスイート内で、特定のシナリオ(例えば、ファイルディスクリプタの継承や、子プロセスでのファイル操作)をシミュレートするために使用されます。
変更の核心は、os.Open(os.Args[0])
がエラーを返した場合に、追加の診断情報を出力する点にあります。os.Args[0]
は通常、実行中のプログラム自身のパスを指します。このファイルを開く操作が失敗するということは、テスト環境や実行時のファイルシステムの状態に何らかの問題があることを示唆しています。
エラーが発生した場合、以下のGoコードが実行されます。
fmt.Println(Command("lsof", "-p", fmt.Sprint(os.Getpid())).CombinedOutput())
これは、現在のGoプロセス(テストヘルパープロセス自身)のPIDを取得し、そのPIDを引数としてlsof -p <PID>
コマンドを実行します。そして、そのlsof
コマンドの標準出力と標準エラー出力を結合した結果を標準出力にプリントします。
これにより、テストが失敗した際に、その失敗の原因がファイルディスクリプタの不足、パーミッションの問題、またはその他のファイル関連の問題である場合に、lsof
の出力がその状況を明確にするための重要な手がかりを提供します。これは、テストのデバッグプロセスを大幅に加速させるための、非常に実用的な改善策と言えます。
コアとなるコードの変更箇所
変更はsrc/pkg/os/exec/exec_test.go
ファイルに1行追加されています。
--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.go
@@ -262,6 +262,7 @@ func TestHelperProcess(*testing.T) {
f, err := os.Open(os.Args[0])
if err != nil {
fmt.Printf("error opening file with expected fd %d: %v", wantfd, err)
+ fmt.Println(Command("lsof", "-p", fmt.Sprint(os.Getpid())).CombinedOutput())
os.Exit(1)
}
if got := f.Fd(); got != wantfd {
コアとなるコードの解説
追加された行は、TestHelperProcess
関数内のif err != nil
ブロック、つまりos.Open
がエラーを返した場合の処理の中にあります。
fmt.Println(Command("lsof", "-p", fmt.Sprint(os.Getpid())).CombinedOutput())
この行は、以下の処理を順に行います。
os.Getpid()
: 現在実行中のGoプロセスのPIDを取得します。fmt.Sprint(os.Getpid())
: 取得したPIDを文字列に変換します。Command("lsof", "-p", ...)
:lsof -p <PID>
というコマンドを構築します。これは、現在のプロセスが開いているファイルの一覧を表示するためのコマンドです。.CombinedOutput()
: 構築したlsof
コマンドを実行し、その標準出力と標準エラー出力を結合したバイトスライスとして取得します。fmt.Println(...)
: 取得したlsof
コマンドの出力を標準出力にプリントします。
この変更は、os/exec
パッケージ自体の実行時動作を変更するものではなく、あくまでテストが失敗した際の診断情報を強化するためのものです。これにより、テストの失敗がファイルディスクリプタ関連の問題に起因する場合、開発者はlsof
の出力から直接的な手がかりを得て、デバッグ時間を短縮することができます。
関連リンク
- Go CL 5504063: https://golang.org/cl/5504063
参考にした情報源リンク
- Go言語
os/exec
パッケージ公式ドキュメント: https://pkg.go.dev/os/exec lsof
コマンドに関する情報 (例: Wikipedia): https://ja.wikipedia.org/wiki/Lsof- Go言語
os
パッケージ公式ドキュメント: https://pkg.go.dev/os - Go言語
fmt
パッケージ公式ドキュメント: https://pkg.go.dev/fmt