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

[インデックス 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())

この行は、以下の処理を順に行います。

  1. os.Getpid(): 現在実行中のGoプロセスのPIDを取得します。
  2. fmt.Sprint(os.Getpid()): 取得したPIDを文字列に変換します。
  3. Command("lsof", "-p", ...): lsof -p <PID>というコマンドを構築します。これは、現在のプロセスが開いているファイルの一覧を表示するためのコマンドです。
  4. .CombinedOutput(): 構築したlsofコマンドを実行し、その標準出力と標準エラー出力を結合したバイトスライスとして取得します。
  5. fmt.Println(...): 取得したlsofコマンドの出力を標準出力にプリントします。

この変更は、os/execパッケージ自体の実行時動作を変更するものではなく、あくまでテストが失敗した際の診断情報を強化するためのものです。これにより、テストの失敗がファイルディスクリプタ関連の問題に起因する場合、開発者はlsofの出力から直接的な手がかりを得て、デバッグ時間を短縮することができます。

関連リンク

参考にした情報源リンク