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

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

このコミットは、Go言語の標準ライブラリであるos/execパッケージのテストファイルsrc/pkg/os/exec/exec_test.goに対する変更です。具体的には、テスト実行中にデバッグ情報を出力するためのlsofコマンドの呼び出し位置を移動しています。この変更の目的は、特定のテスト失敗シナリオにおいて、より有用なデバッグ情報を取得できるようにすることです。

コミット

commit fa02bac80953660924a2b00f1f9f8eea8569d717
Author: Russ Cox <rsc@golang.org>
Date:   Wed Dec 21 17:49:29 2011 -0500

    os/exec: put the print where it will help
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5501058

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

https://github.com/golang/go/commit/fa02bac80953660924a2b00f1f9f8eea8569d717

元コミット内容

diff --git a/src/pkg/os/exec/exec_test.go b/src/pkg/os/exec/exec_test.go
index d7e61e7379..1e0ea97725 100644
--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.go
@@ -262,11 +262,11 @@ 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 {
 				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)

変更の背景

この変更は、os/execパッケージのテスト、特にファイルディスクリプタの継承やリークに関連するテストのデバッグ効率を向上させるために行われました。TestHelperProcess関数は、テスト中に子プロセスを起動し、その子プロセスが特定のファイルディスクリプタの状態を正しく処理するかどうかを検証する目的で使用されます。

元のコードでは、os.Openが失敗した場合(つまり、ファイルを開くこと自体ができなかった場合)にlsofコマンドの出力が行われていました。しかし、このシナリオでは、ファイルディスクリプタのリークや不適切な継承といった問題が直接の原因ではない可能性があり、lsofの出力が必ずしも問題解決に役立つとは限りませんでした。

一方で、ファイルは開けたものの、そのファイルに割り当てられたファイルディスクリプタが期待される値と異なっていた場合(f.Fd() != wantfd)、これは親プロセスからのファイルディスクリプタの意図しない継承や、テスト環境におけるファイルディスクリプタの割り当て順序の問題など、より具体的なファイルディスクリプタ関連の問題を示唆します。このような状況でlsofの出力を得られれば、現在プロセスが保持しているすべてのファイルディスクリプタの状態を詳細に把握でき、問題の根本原因を特定するための非常に貴重な情報となります。

したがって、このコミットは、デバッグ出力のタイミングを最適化し、「本当に助けになる場所」にlsofの呼び出しを移動することで、テスト失敗時の診断能力を高めることを目的としています。

前提知識の解説

Go言語のos/execパッケージ

os/execパッケージは、外部コマンドを実行するための機能を提供します。これにより、Goプログラムからシェルコマンドや他の実行可能ファイルを起動し、その入出力を制御することができます。例えば、Command("lsof", "-p", fmt.Sprint(os.Getpid()))のように使用し、CombinedOutput()で標準出力と標準エラー出力をまとめて取得できます。

Go言語のtestingパッケージ

testingパッケージは、Go言語のテストフレームワークです。go testコマンドによって実行されるテスト関数(Testで始まる関数)を定義するために使用されます。このコミットで変更されているTestHelperProcessのような関数は、Goのテストで一般的なパターンであり、テスト対象のコードが子プロセスを起動するような場合に、その子プロセスの挙動をシミュレートしたり、特定の環境下での動作を検証したりするために使われます。

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

ファイルディスクリプタは、Unix系オペレーティングシステムにおいて、プロセスが開いているファイル、ソケット、パイプなどのI/Oリソースを識別するために使用される整数値です。プロセスがファイルを開くと、カーネルは一意のファイルディスクリプタを割り当て、それを通じてそのリソースへのアクセスを管理します。標準入力(0)、標準出力(1)、標準エラー出力(2)は、予約されたファイルディスクリプタです。

ファイルディスクリプタのリーク

ファイルディスクリプタのリークとは、プログラムがファイルディスクリプタを使い終わった後に適切に閉じないために、利用可能なファイルディスクリプタが枯渇してしまう現象を指します。これにより、新しいファイルを開いたり、ネットワーク接続を確立したりできなくなり、アプリケーションの動作に深刻な影響を与える可能性があります。

lsofコマンド

lsof (list open files) は、Unix系OSで実行中のプロセスが開いているファイルやネットワーク接続を一覧表示するためのコマンドラインユーティリティです。ファイルディスクリプタ、ファイルの種類、デバイス、サイズ、ノード、パスなど、開いているファイルに関する詳細な情報を提供します。デバッグ、特にファイルディスクリプタのリークや、予期せぬファイルアクセスを特定する際に非常に強力なツールです。 このコミットでは、lsof -p <pid>という形式で使用されており、特定のプロセスID (os.Getpid()で取得される現在のプロセスのID) が開いているファイルのみを表示します。

os.Openf.Fd()

  • os.Open(name string) (*File, error): 指定されたパスのファイルを読み取り専用で開きます。成功すると*os.File型のポインタとnilエラーを返します。
  • f.Fd() uintptr: *os.File型のメソッドで、開いているファイルの基になるファイルディスクリプタ(またはWindowsではハンドル)を返します。

技術的詳細

このコミットは、src/pkg/os/exec/exec_test.go内のTestHelperProcess関数の一部を変更しています。この関数は、os/execパッケージのテストスイートの一部として、子プロセスがファイルディスクリプタをどのように継承し、管理するかを検証するために設計されています。

テストのロジックは以下のようになっています。

  1. os.Args[0](現在の実行可能ファイル自身)を開こうとします。これは、テストヘルパープロセスが親プロセスから特定のファイルディスクリプタを継承しているかどうかを検証するためによく行われるパターンです。
  2. os.Openが成功した場合、開かれたファイルのファイルディスクリプタをf.Fd()で取得します。
  3. 取得したファイルディスクリプタが、テストで期待される値(wantfd)と一致するかどうかをチェックします。

変更前のコードでは、os.Openがエラーを返した場合、つまりファイルを開くこと自体ができなかった場合に、lsofコマンドが実行されていました。このシナリオでは、ファイルが開かれていないため、lsofの出力はファイルディスクリプタのリークや不適切な継承に関する直接的な情報を提供するものではありませんでした。エラーメッセージは「error opening file with expected fd %d: %v」となり、ファイルを開けなかった理由が主眼となります。

変更後のコードでは、lsofコマンドの実行が、f.Fd()で取得したファイルディスクリプタが期待されるwantfdと一致しなかった場合に移動されました。このシナリオは、ファイルは正常に開かれたものの、そのファイルに割り当てられたファイルディスクリプタが予期せぬ値であったことを意味します。これは、親プロセスから意図しないファイルディスクリプタが子プロセスに継承された、あるいはテスト環境のセットアップに問題があり、ファイルディスクリプタの割り当て順序が期待と異なった、といった状況を示唆します。このような状況でlsofを実行すると、その時点でプロセスが保持しているすべてのファイルディスクリプタとその関連情報(どのファイルに対応しているかなど)が一覧表示され、なぜf.Fd()wantfdと異なったのかを診断するための決定的な情報を提供します。エラーメッセージは「leaked parent file. fd = %d; want %d」となり、ファイルディスクリプタの不一致が問題の中心となります。

この変更は、デバッグ出力の戦略的な配置であり、テストが失敗した際に最も関連性の高い診断情報を提供することを目的としています。

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

--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.go
@@ -262,11 +262,11 @@ 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 {
 				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)

コアとなるコードの解説

このコミットの核心は、fmt.Println(Command("lsof", "-p", fmt.Sprint(os.Getpid())).CombinedOutput())という行の移動です。

  • 変更前: os.Open(os.Args[0])の呼び出しでerr != nil、つまりファイルを開くことに失敗した場合にlsofが実行されていました。この場合、ファイルディスクリプタのリークというよりは、ファイルパスの問題やパーミッションの問題など、ファイルを開くこと自体が妨げられた可能性が高いです。lsofの出力は、この時点では開いているファイルが少ないため、問題の診断に直接役立つ情報は限られていたかもしれません。

  • 変更後: f.Fd() != wantfd、つまりファイルは開けたものの、そのファイルに割り当てられたファイルディスクリプタが期待される値と異なっていた場合にlsofが実行されるようになりました。このシナリオは、ファイルディスクリプタの継承に関する問題や、テスト環境におけるファイルディスクリプタの割り当て順序の予期せぬ変化を示唆します。この時点でlsofを実行することで、プロセスが現在開いているすべてのファイルディスクリプタ(親プロセスから継承されたものも含む)の詳細なリストが得られ、なぜ期待されるファイルディスクリプタが割り当てられなかったのか、あるいは意図しないファイルディスクリプタが残っているのかを特定するための非常に具体的な情報が提供されます。

この変更は、デバッグ出力のタイミングを微調整し、テストが失敗した際に最も関連性の高いシステム状態のスナップショットを提供することで、開発者が問題の根本原因をより迅速に特定できるようにするためのものです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Unix/Linuxのファイルディスクリプタに関する一般的な知識
  • lsofコマンドの一般的な使用法とデバッグにおける役割に関する知識
  • Go言語のテストにおけるTestHelperProcessパターンの理解