[インデックス 17597] ファイルの概要
このコミットは、Go言語のos/execパッケージにおけるCmd構造体のStdoutPipeおよびStderrPipeメソッドのドキュメントに、重要な注意書きを追加するものです。特に、これらのパイプのライフサイクルと、WaitメソッドやRunメソッドとの相互作用に関する潜在的な落とし穴について、より明確な説明が加えられています。
コミット
commit 22e8f82e8d4b62a4c96a82d4f731606a87db8d09
Author: Russ Cox <rsc@golang.org>
Date: Fri Sep 13 15:43:54 2013 -0400
os/exec: add more caveats to StdoutPipe, StderrPipe
(StdinPipe was taken care of by CL 13329043.)
Fixes #6008.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/13606046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/22e8f82e8d4b62a4c96a82d4f731606a87db8d09
元コミット内容
os/exec: add more caveats to StdoutPipe, StderrPipe
(StdinPipe was taken care of by CL 13329043.)
Fixes #6008.
このコミットメッセージは、StdoutPipeとStderrPipeのドキュメントにさらなる注意書きを追加したことを示しています。また、StdinPipeについては、以前の変更リスト(CL 13329043)で既に対応済みであること、そしてこの変更がIssue #6008を修正するものであることが明記されています。
変更の背景
この変更の背景には、Goのos/execパッケージにおける外部コマンドの標準入出力パイプの扱いに関するユーザーの混乱や誤用があったと考えられます。特に、StdoutPipeやStderrPipeを使用して外部コマンドの出力を読み取る際に、Cmd.Wait()メソッドを呼び出すタイミングが重要であるにもかかわらず、その点がドキュメントで十分に強調されていなかったことが問題でした。
Issue #6008("os/exec: StdoutPipe and StderrPipe documentation is misleading")は、まさにこの問題点を指摘しています。ユーザーは、StdoutPipeやStderrPipeから読み取りを完了する前にWaitを呼び出すと、デッドロックや予期せぬ動作が発生する可能性があることを理解していませんでした。これは、Waitがパイプを自動的に閉じるため、読み取りが完了する前にパイプが閉じられてしまうと、読み取り操作がブロックされるか、エラーになるためです。また、Runメソッドは内部でStartとWaitを呼び出すため、StdoutPipeやStderrPipeとRunを併用することも同様の問題を引き起こします。
このコミットは、これらの潜在的な問題を回避するための重要なガイダンスをドキュメントに追加することで、ユーザーがos/execパッケージをより安全かつ効果的に使用できるようにすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語の概念とos/execパッケージの基本的な知識が必要です。
os/execパッケージ: Go言語で外部コマンドを実行するためのパッケージです。exec.Command関数でコマンドを構築し、Cmd構造体を通じてその実行を制御します。Cmd構造体: 実行する外部コマンドとその設定(引数、環境変数、標準入出力など)をカプセル化する構造体です。- 標準入出力 (Standard I/O):
- 標準入力 (Stdin): プログラムがデータを読み取るための入力ストリーム。
- 標準出力 (Stdout): プログラムが通常の結果を出力するためのストリーム。
- 標準エラー出力 (Stderr): プログラムがエラーメッセージを出力するためのストリーム。
- パイプ (Pipe): プロセス間通信の一種で、一方のプロセスの出力がもう一方のプロセスの入力に直接接続されるメカニズムです。
os/execパッケージでは、Goプログラムと外部コマンドの間でデータをやり取りするためにパイプが使用されます。 Cmd.Start(): 外部コマンドを非同期で開始します。コマンドが開始された後、すぐに制御が呼び出し元に戻ります。Cmd.Wait():Startで開始された外部コマンドの終了を待ち、その終了ステータスを返します。このメソッドは、コマンドが終了するまでブロックします。Cmd.Run():StartとWaitを連続して呼び出すコンビニエンスメソッドです。コマンドを開始し、その終了を待ちます。io.ReadCloser/io.WriteCloser: Goの標準ライブラリで定義されているインターフェースです。ReadCloserはReadとCloseメソッドを持ち、WriteCloserはWriteとCloseメソッドを持ちます。パイプはこれらのインターフェースを実装しており、データの読み書きと、使用後のリソース解放(クローズ)が可能です。- デッドロック (Deadlock): 複数のプロセスやスレッドが互いに相手が保持しているリソースの解放を待ち、結果としてどのプロセスも処理を進められなくなる状態です。このコミットの文脈では、パイプからの読み取りが完了する前に
Waitがパイプを閉じてしまい、読み取り側がブロックされることで発生します。
技術的詳細
このコミットは、os/execパッケージのCmd構造体におけるStdinPipe, StdoutPipe, StderrPipeメソッドのドキュメントを修正・拡充しています。
変更の核心:
以前のドキュメントでは、StdoutPipeとStderrPipeが「コマンドが終了すると自動的に閉じられる」とだけ述べられていました。しかし、この記述だけでは、ユーザーがこれらのパイプを扱う上で陥りやすいデッドロックのシナリオを十分に警告していませんでした。
具体的には、以下の重要な注意点が追加されました。
StdoutPipe/StderrPipeとWaitの関係:Waitはコマンドの終了を確認した後、パイプを閉じます。- このため、パイプからのすべての読み取りが完了する前に
Waitを呼び出すのは誤りです。読み取りが完了する前にパイプが閉じられると、読み取り操作がブロックされるか、エラーになります。 - これは、外部コマンドが大量の出力を生成し、Goプログラムがそれをすべて読み取る前に
Waitが呼び出された場合に特に問題となります。パイプのバッファが満杯になり、外部コマンドが書き込みをブロックし、Goプログラムが読み取りをブロックし、Waitがコマンドの終了を待つというデッドロック状態に陥る可能性があります。
StdoutPipe/StderrPipeとRunの関係:Runメソッドは内部でStartとWaitを呼び出すため、StdoutPipeやStderrPipeを使用する際には**Runを呼び出すのは誤り**です。- これは、
RunがWaitを自動的に呼び出すため、パイプからの読み取りが完了する前にWaitが実行されてしまうためです。
- 慣用的な使用法への誘導:
- ドキュメントは、これらのパイプを正しく使用するための「慣用的な使用法(idiomatic usage)」として、
StdoutPipeの例を参照するように促しています。この例では、Cmd.Start()でコマンドを開始し、別のゴルーチンでパイプからの読み取りを行い、その後にCmd.Wait()を呼び出すというパターンが示されています。これにより、読み取りとWaitが並行して実行され、デッドロックが回避されます。
- ドキュメントは、これらのパイプを正しく使用するための「慣用的な使用法(idiomatic usage)」として、
StdinPipeに関する補足:
コミットメッセージには「(StdinPipe was taken care of by CL 13329043.)」とあります。これは、StdinPipeについても同様の注意が必要であり、以前の変更リスト(CL 13329043)で既に対応済みであることを示しています。StdinPipeの場合、外部コマンドが標準入力が閉じられるまで終了しない場合、呼び出し元が明示的にパイプを閉じる必要があるという注意が追加されています。
これらの変更は、os/execパッケージのAPIが提供する柔軟性と、それに伴う複雑さをユーザーがより深く理解し、一般的な落とし穴を避けるための重要なドキュメント改善です。
コアとなるコードの変更箇所
変更はsrc/pkg/os/exec/exec.goファイルに集中しています。
--- a/src/pkg/os/exec/exec.go
+++ b/src/pkg/os/exec/exec.go
@@ -358,8 +358,10 @@ func (c *Cmd) CombinedOutput() ([]byte, error) {
// StdinPipe returns a pipe that will be connected to the command's
// standard input when the command starts.
-// If the returned WriteCloser is not closed before Wait is called,
-// Wait will close it.
+// The pipe will be closed automatically after Wait sees the command exit.
+// A caller need only call Close to force the pipe to close sooner.
+// For example, if the command being run will not exit until standard input
+// is closed, the caller must close the pipe.
func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
if c.Stdin != nil {
return nil, errors.New("exec: Stdin already set")
@@ -394,7 +396,12 @@ func (c *closeOnce) Close() error {
// StdoutPipe returns a pipe that will be connected to the command's
// standard output when the command starts.
-// The pipe will be closed automatically after Wait sees the command exit.
+//
+// Wait will close the pipe after seeing the command exit, so most callers
+// need not close the pipe themselves; however, an implication is that
+// it is incorrect to call Wait before all reads from the pipe have completed.
+// For the same reason, it is incorrect to call Run when using StdoutPipe.
+// See the example for idiomatic usage.
func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
if c.Stdout != nil {
return nil, errors.New("exec: Stdout already set")
@@ -414,7 +421,12 @@ func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
// StderrPipe returns a pipe that will be connected to the command's
// standard error when the command starts.
-// The pipe will be closed automatically after Wait sees the command exit.
+//
+// Wait will close the pipe after seeing the command exit, so most callers
+// need not close the pipe themselves; however, an implication is that
+// it is incorrect to call Wait before all reads from the pipe have completed.
+// For the same reason, it is incorrect to use Run when using StderrPipe.
+// See the StdoutPipe example for idiomatic usage.
func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
if c.Stderr != nil {
return nil, errors.New("exec: Stderr already set")
コアとなるコードの解説
このコミットは、Goのos/execパッケージ内のCmd構造体のメソッドであるStdinPipe、StdoutPipe、StderrPipeのドキュメント文字列(コメント)を修正しています。実際のコードのロジック自体には変更はありません。
-
StdinPipeの変更点:- 変更前:
// If the returned WriteCloser is not closed before Wait is called, // Wait will close it. - 変更後:
// The pipe will be closed automatically after Wait sees the command exit. // A caller need only call Close to force the pipe to close sooner. // For example, if the command being run will not exit until standard input // is closed, the caller must close the pipe. - 解説:
StdinPipeは、以前のCLで既に修正済みとされていますが、このコミットでもドキュメントが更新されています。新しいドキュメントは、Waitがパイプを自動的に閉じることを再確認しつつ、呼び出し元がパイプをより早く閉じる必要があるシナリオ(例:コマンドが入力の終了を待つ場合)を明確にしています。これにより、ユーザーはデッドロックを避けるために、必要に応じて明示的にCloseを呼び出すべきであることを理解できます。
- 変更前:
-
StdoutPipeの変更点:- 変更前:
// The pipe will be closed automatically after Wait sees the command exit. - 変更後:
// Wait will close the pipe after seeing the command exit, so most callers // need not close the pipe themselves; however, an implication is that // it is incorrect to call Wait before all reads from the pipe have completed. // For the same reason, it is incorrect to call Run when using StdoutPipe. // See the example for idiomatic usage. - 解説: ここがこのコミットの主要な変更点です。以前の記述は、パイプが自動的に閉じられるという事実のみを述べていましたが、新しい記述では、その「含意(implication)」として、パイプからのすべての読み取りが完了する前に
Waitを呼び出すのは誤りであること、そして**StdoutPipeを使用する際にRunを呼び出すのも誤りである**ことを明確に警告しています。これは、WaitやRunがパイプを閉じてしまうため、読み取りが完了する前にパイプが閉じられ、デッドロックやデータ損失につながる可能性があるためです。最後に、「慣用的な使用法」として例を参照するように促しています。
- 変更前:
-
StderrPipeの変更点:- 変更前:
// The pipe will be closed automatically after Wait sees the command exit. - 変更後:
// Wait will close the pipe after seeing the command exit, so most callers // need not close the pipe themselves; however, an implication is that // it is incorrect to call Wait before all reads from the pipe have completed. // For the same reason, it is incorrect to use Run when using StderrPipe. // See the StdoutPipe example for idiomatic usage. - 解説:
StdoutPipeと全く同じ変更がStderrPipeにも適用されています。標準エラー出力も標準出力と同様に扱われるため、同じ注意が必要です。
- 変更前:
これらのドキュメントの追加は、APIの動作に関する潜在的な誤解を解消し、ユーザーがより堅牢で正しいコードを書くための重要なガイドラインを提供します。
関連リンク
- Go Issue #6008: os/exec: StdoutPipe and StderrPipe documentation is misleading
- Go Change List 13606046: os/exec: add more caveats to StdoutPipe, StderrPipe
- Go Change List 13329043: os/exec: document StdinPipe's Close behavior (StdinPipeに関する以前の修正)
- Go
os/execパッケージのドキュメント: https://pkg.go.dev/os/exec
参考にした情報源リンク
- 上記のGitHub IssueおよびGo Change Listのページ
- Go言語の公式ドキュメント (
os/execパッケージ) - Go言語におけるパイプと外部コマンド実行に関する一般的な知識