[インデックス 16200] ファイルの概要
このコミットは、Go言語の標準ライブラリio
パッケージにおけるio.Reader
インターフェースのRead
メソッドの振る舞いに関する重要な改善と明確化を導入しています。具体的には、Read
メソッドが(0, nil)
(読み込んだバイト数が0でエラーがnil)を返す場合の意味を明確にし、さらにErrNoProgress
という新しいエラー変数を追加して、io.Reader
の実装がデータやエラーを返さない「無効なRead呼び出し」を報告できるようにしています。これにより、io.Reader
の利用者がより堅牢なコードを書けるようになり、また、io.Reader
の実装者が不適切な振る舞いを避けるための指針が提供されます。
コミット
- コミットハッシュ:
5fbb54eaca57e2626e24f1982d28a7de9f91ac49
- Author: Rob Pike r@golang.org
- Date: Thu Apr 18 17:36:25 2013 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5fbb54eaca57e2626e24f1982d28a7de9f91ac49
元コミット内容
io: explain what (0,nil) means from Read
Also add a new variable ErrNoProgress that io.Readers can use to
report ineffectual Read calls.
Fixes #5310.
R=golang-dev, dsymonds, bradfitz
CC=golang-dev
https://golang.org/cl/8845043
変更の背景
Go言語のio.Reader
インターフェースは、データの読み込み操作を抽象化するための基本的な構成要素です。Read
メソッドは(n int, err error)
という形式で、読み込んだバイト数と発生したエラーを返します。しかし、Read
メソッドがn=0
かつerr=nil
を返す場合のセマンティクスは、Goの初期のドキュメントでは十分に明確ではありませんでした。
この曖昧さは、特にネットワークI/OやファイルI/Oのようなブロッキング操作において問題を引き起こす可能性がありました。Read
が(0, nil)
を返した場合、それは「現在利用可能なデータがないが、エラーも発生していない」ことを意味するのか、それとも「何らかの理由で読み込みが進まなかった」ことを意味するのかが不明瞭でした。多くのio.Reader
の実装は、データが利用可能になるまでブロックするか、エラーが発生するまでブロックすることが期待されます。しかし、一部の実装では、データがない場合に(0, nil)
をすぐに返すことがあり、これは呼び出し元が無限ループに陥る原因となる可能性がありました。
この問題は、GoのIssue #5310で議論され、Read
が(0, nil)
を返すことの推奨されない振る舞いと、それが「無効なRead呼び出し (ineffectual Read calls)」として扱われるべきであるという合意が形成されました。このコミットは、その議論の結果として、ドキュメントの明確化と、この種の状況を報告するための新しいエラーErrNoProgress
の導入を行っています。
前提知識の解説
io.Reader
インターフェース
Go言語のio
パッケージは、I/Oプリミティブを提供します。その中でもio.Reader
は最も基本的なインターフェースの一つで、データの読み込み操作を抽象化します。
type Reader interface {
Read(p []byte) (n int, err error)
}
Read(p []byte)
:p
に最大len(p)
バイトのデータを読み込みます。n int
: 実際に読み込んだバイト数です。0 <= n <= len(p)
の範囲になります。err error
: 読み込み中に発生したエラーです。
Read
メソッドの戻り値にはいくつかの重要なセマンティクスがあります。
n > 0, err == nil
: 正常にn
バイトのデータを読み込みました。エラーはありません。n > 0, err != nil
:n
バイトのデータを読み込んだ後、エラーが発生しました。この場合、エラーは部分的な読み込みの後に発生したことを示します。例えば、ネットワーク接続が切断された場合などです。n == 0, err == io.EOF
: ストリームの終端(End Of File)に達しました。これ以上読み込むデータはありません。io.EOF
は、エラーとして扱われますが、通常は正常な終了条件を示します。n == 0, err != nil
(かつerr != io.EOF
): データを読み込む前にエラーが発生しました。例えば、ファイルが見つからない、パーミッションがない、ネットワークエラーなどです。n == 0, err == nil
: これがこのコミットで明確化される点です。以前は曖昧でしたが、このコミットにより「無効なRead呼び出し」として扱われるべきであり、実装者はこのパターンを避けるべきであると明記されました。
無効なRead呼び出し (Ineffectual Read Calls)
「無効なRead呼び出し」とは、io.Reader
のRead
メソッドが、データを読み込むこともなく、またエラーも返さない状態(つまり(0, nil)
)で繰り返し呼び出される状況を指します。理想的なio.Reader
の実装は、データが利用可能になるまでブロックするか、エラーが発生するまでブロックすることが期待されます。もしRead
が(0, nil)
をすぐに返す場合、呼び出し元はデータが利用可能になるのを待つことなく、CPUサイクルを無駄に消費する無限ループに陥る可能性があります。これは、特に低レベルのI/O操作において、パフォーマンスの問題やデッドロックの原因となることがあります。
技術的詳細
このコミットは、主にsrc/pkg/io/io.go
ファイルに2つの重要な変更を加えています。
-
ErrNoProgress
エラー変数の追加:ErrNoProgress
は、io.Reader
のクライアントが、Read
呼び出しが繰り返しデータやエラーを返さない場合に報告するために使用できる新しいエラーです。これは、通常、io.Reader
の実装が壊れている兆候と見なされます。// ErrNoProgress is returned by some clients of an io.Reader when // many calls to Read have failed to return any data or error, // usually the sign of a broken io.Reader implementation. var ErrNoProgress = errors.New("multiple Read calls return no data or error")
このエラーは、
io.Reader
の実装自体が返すものではなく、io.Reader
を利用する側(例えば、io.Copy
のようなヘルパー関数や、カスタムのI/O処理ロジック)が、Read
が(0, nil)
を繰り返し返すような状況を検知した場合に、その問題を報告するために使用することを意図しています。 -
io.Reader
インターフェースのドキュメントの明確化:io.Reader
インターフェースのRead
メソッドに関するコメントに、(0, nil)
の戻り値に関する重要な指針が追加されました。// Implementations of Read are discouraged from returning a // zero byte count with a nil error, and callers should treat // that situation as a no-op.
このコメントは、
io.Reader
の実装者に対して、n=0
かつerr=nil
を返すことを避けるように強く推奨しています。これは、そのような振る舞いが「無効なRead呼び出し」につながり、呼び出し元に問題を引き起こす可能性があるためです。同時に、Read
の呼び出し元に対しては、もしそのような状況が発生した場合、それを「何もしなかった(no-op)」として扱うべきであると指示しています。つまり、(0, nil)
が返された場合は、データが読み込まれなかっただけでなく、エラーも発生しなかったため、再試行するか、別の処理を行うべきではないということを示唆しています。
これらの変更により、io.Reader
の契約がより明確になり、GoのI/Oエコシステム全体の堅牢性が向上しました。実装者はより良いio.Reader
を作成するための明確なガイドラインを得て、利用者はRead
の予期せぬ振る舞いに対処するためのより良い方法を得ました。
コアとなるコードの変更箇所
--- a/src/pkg/io/io.go
+++ b/src/pkg/io/io.go
@@ -34,6 +34,11 @@ var EOF = errors.New("EOF")
// middle of reading a fixed-size block or data structure.
var ErrUnexpectedEOF = errors.New("unexpected EOF")
+// ErrNoProgress is returned by some clients of an io.Reader when
+// many calls to Read have failed to return any data or error,
+// usually the sign of a broken io.Reader implementation.
+var ErrNoProgress = errors.New("multiple Read calls return no data or error")
+
// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p. It returns the number of bytes
@@ -55,6 +60,10 @@ var ErrUnexpectedEOF = errors.New("unexpected EOF")
// considering the error err. Doing so correctly handles I/O errors
// that happen after reading some bytes and also both of the
// allowed EOF behaviors.
+//
+// Implementations of Read are discouraged from returning a
+// zero byte count with a nil error, and callers should treat
+// that situation as a no-op.
type Reader interface {
Read(p []byte) (n int, err error)
}
コアとなるコードの解説
-
ErrNoProgress
の追加:var ErrNoProgress = errors.New("multiple Read calls return no data or error")
この行は、io
パッケージに新しい公開エラー変数ErrNoProgress
を定義しています。このエラーは、Read
メソッドが繰り返し(0, nil)
を返すような、進捗のない状況を検出したio.Reader
のクライアントによって使用されることを意図しています。これは、io.Reader
の実装が期待通りに動作していないことを示すシグナルとなります。 -
io.Reader
インターフェースのコメント更新:type Reader interface { ... }
の定義の直前に、以下のコメントが追加されました。// Implementations of Read are discouraged from returning a // zero byte count with a nil error, and callers should treat // that situation as a no-op.
このコメントは、
io.Reader
の実装者に対する明確なガイドラインを提供します。Read
メソッドがn=0
かつerr=nil
を返すことは推奨されない振る舞いであると明記されています。これは、そのような戻り値が、呼び出し元が無限ループに陥る原因となる「無効なRead呼び出し」につながるためです。同時に、Read
の呼び出し元に対しては、もしこの状況が発生した場合、それを「何もしなかった」操作として扱うべきであると指示しています。これは、データが読み込まれず、エラーも発生しなかったため、再試行しても意味がないか、あるいは別のロジックで処理すべきであることを示唆しています。
これらの変更は、GoのI/Oモデルの堅牢性と明確性を向上させ、開発者がより予測可能で効率的なI/Oコードを書くのに役立ちます。
関連リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/5fbb54eaca57e2626e24f1982d28a7de9f91ac49
- Go CL (Code Review): https://golang.org/cl/8845043
- Go Issue #5310: (検索しましたが、公式のGoリポジトリや一般的な検索では直接的な情報を見つけることができませんでした。これは非常に古いIssueであるか、または別の場所で管理されていた可能性があります。)
参考にした情報源リンク
- Go言語の公式ドキュメント (
io
パッケージ): https://pkg.go.dev/io (このコミットが適用された後のドキュメントを参照) - Go言語の
errors
パッケージ: https://pkg.go.dev/errors - Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master