[インデックス 11945] ファイルの概要
このコミットは、Go言語の標準ライブラリであるbufio
パッケージ内のReader.Peek
メソッドの挙動を修正するものです。具体的には、Peek
メソッドが有効なデータを返す場合でも、不適切にエラーを返してしまうバグ(Issue 3022)を修正することを目的としています。これにより、Peek
が期待通りに動作し、呼び出し元が不要なエラー処理を行う必要がなくなります。
コミット
- コミットハッシュ:
88f8af127ab675b94e18f161c59415edd92110e9
- Author: Brad Fitzpatrick bradfitz@golang.org
- Date: Thu Feb 16 10:15:36 2012 +1100
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/88f8af127ab675b94e18f161c59415edd92110e9
元コミット内容
bufio: don't return errors from good Peeks
Fixes #3022
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5674060
変更の背景
bufio
パッケージのReader.Peek
メソッドは、内部バッファから指定されたバイト数(n
)を読み込まずに「覗き見」する機能を提供します。このメソッドは、次に読み込まれるデータの一部を事前に確認したい場合に非常に便利です。しかし、このコミットが修正するバグ(Issue 3022)により、Peek
メソッドが実際に要求されたバイト数分のデータを正常に提供できた場合でも、誤ってエラーを返してしまうという問題がありました。
具体的には、Peek
は内部バッファのサイズや、基となるリーダーからの読み込み状況に応じて、io.EOF
やErrBufferFull
といったエラーを返すことがあります。しかし、このバグが存在するバージョンでは、Peek
が要求されたn
バイト分のデータを正常に提供できたにもかかわらず、以前の読み込み操作で発生したエラー(例えば、基となるリーダーが既にEOFに達していた場合など)を不適切に伝播させてしまうことがありました。
この挙動は、Peek
の本来の意図に反しており、呼び出し元が不要なエラー処理を記述したり、Peek
の戻り値の信頼性が低下したりする原因となっていました。このコミットは、Peek
が「良い(good)」データを提供できた場合には、エラーを返さないようにすることで、この問題を解決し、Peek
メソッドの堅牢性と使いやすさを向上させています。
前提知識の解説
bufio
パッケージ
bufio
パッケージは、I/O操作をバッファリングすることで効率化するためのGo言語の標準ライブラリです。ディスクI/OやネットワークI/Oなど、低速なI/O操作を直接行う代わりに、メモリ上のバッファを介してデータを読み書きすることで、システムコールの回数を減らし、パフォーマンスを向上させます。
bufio.Reader
bufio.Reader
は、バッファリングされた読み込み操作を提供する型です。内部にバッファを持ち、基となるio.Reader
からデータを読み込み、そのバッファを介して呼び出し元にデータを提供します。これにより、一度に多くのデータを読み込んでバッファに格納し、その後の小さな読み込み要求に対してはバッファから直接データを提供することで、効率的な読み込みを実現します。
Peek
メソッド
func (b *Reader) Peek(n int) ([]byte, error)
Peek
メソッドは、bufio.Reader
の重要なメソッドの一つです。このメソッドは、次に読み込まれるであろうn
バイトのデータを、実際に読み込みポインタを進めることなく(つまり、データを消費することなく)返します。これにより、呼び出し元はストリームの先頭を「覗き見」して、次にどのようなデータが来るかを判断し、それに基づいて適切な処理を行うことができます。
Peek
は以下の2つの値を返します。
[]byte
: 覗き見したデータ。要求されたn
バイト分のデータがバッファに存在しない場合、利用可能な最大バイト数が返されます。error
: エラーが発生した場合に返されます。主なエラーとしては、以下のものがあります。io.EOF
: 基となるリーダーがファイルの終端に達した場合。bufio.ErrBufferFull
: 要求されたn
バイト分のデータをバッファに格納できない場合(バッファサイズが不足している場合)。
Go言語のエラーハンドリング
Go言語では、エラーは関数の最後の戻り値としてerror
型の値で返されます。慣習として、エラーが発生しなかった場合はnil
が返されます。呼び出し元は、返されたエラーがnil
かどうかをチェックすることで、操作が成功したか失敗したかを判断します。
技術的詳細
このコミットの主要な変更は、src/pkg/bufio/bufio.go
内のReader.Peek
メソッドのロジック修正と、その修正を検証するためのテストケースの追加です。
bufio.go
の変更点
変更前のPeek
メソッドの関連部分は以下のようになっていました。
func (b *Reader) Peek(n int) ([]byte, error) {
// ... (前略) ...
if m > n {
m = n
}
err := b.readErr() // ここで以前のエラーを取得
if m < n && err == nil {
err = ErrBufferFull
}
return b.buf[b.r : b.r+m], err
}
このコードでは、まずb.readErr()
を呼び出して、以前の読み込み操作で発生したエラー(例えば、基となるリーダーが既にEOFに達していた場合など)を取得していました。その後、m < n
(要求されたn
バイト分のデータがバッファに存在しない)かつerr == nil
(以前のエラーがない)の場合にのみErrBufferFull
を設定していました。
問題は、m < n
の条件が満たされない場合、つまりm >= n
の場合(要求されたn
バイト分のデータがバッファに存在し、Peek
が成功したと見なせる場合)でも、b.readErr()
で取得した以前のエラーがそのまま返されてしまう点にありました。これが「良いPeek」でもエラーが返される原因でした。
修正後のPeek
メソッドの関連部分は以下のようになります。
func (b *Reader) Peek(n int) ([]byte, error) {
// ... (前略) ...
if m > n {
m = n
}
var err error // エラー変数を初期化
if m < n { // 要求されたnバイト分のデータがバッファに存在しない場合のみエラーを考慮
err = b.readErr() // 以前のエラーを取得
if err == nil { // 以前のエラーがない場合、バッファ不足のエラーを設定
err = ErrBufferFull
}
}
return b.buf[b.r : b.r+m], err
}
この修正では、err
変数をnil
で初期化し、m < n
(要求されたバイト数に満たない)の場合にのみエラーを考慮するロジックに変更されています。
m < n
の場合:b.readErr()
で以前のエラーを取得します。- もし以前のエラーが
nil
であれば、ErrBufferFull
を設定します。 - 以前のエラーが存在すれば、そのエラーが返されます(例:
io.EOF
)。
m >= n
の場合:err
は初期値のnil
のままとなり、Peek
はエラーを返さずに成功します。
これにより、Peek
が要求されたバイト数分のデータを正常に提供できた場合には、以前のエラーが伝播されることなく、nil
が返されるようになります。
bufio_test.go
の変更点
このコミットでは、修正されたPeek
メソッドの挙動を検証するために、TestPeek
関数に新しいテストケースが追加されています。
追加されたテストケースは、dataAndEOFReader
というカスタムのio.Reader
実装を使用しています。このリーダーは、指定されたデータを一度だけ読み込み、その後すぐにio.EOF
を返すように設計されています。
type dataAndEOFReader string
func (r dataAndEOFReader) Read(p []byte) (int, error) {
return copy(p, r), io.EOF
}
このdataAndEOFReader
を使ってbufio.Reader
を初期化し、Peek
メソッドがどのように振る舞うかをテストしています。
// Test for issue 3022, not exposing a reader's error on a successful Peek.
buf = NewReaderSize(dataAndEOFReader("abcd"), 32)
if s, err := buf.Peek(2); string(s) != "ab" || err != nil {
t.Errorf(`Peek(2) on "abcd", EOF = %q, %v; want "ab", nil`, string(s), err)
}
if s, err := buf.Peek(4); string(s) != "abcd" || err != nil {
t.Errorf(`Peek(4) on "abcd", EOF = %q, %v; want "abcd", nil`, string(s), err)
}
// ... (後略) ...
このテストでは、"abcd"
というデータを持つdataAndEOFReader
を基にbufio.Reader
を作成し、以下のことを確認しています。
Peek(2)
を呼び出した場合、"ab"
が返され、エラーはnil
であること。Peek(4)
を呼び出した場合、"abcd"
が返され、エラーはnil
であること。
これらのテストケースは、基となるリーダーがio.EOF
を返している状況でも、Peek
が要求されたバイト数分のデータを正常に提供できた場合には、エラーを返さないという修正後の挙動を正確に検証しています。
コアとなるコードの変更箇所
src/pkg/bufio/bufio.go
のReader.Peek
メソッド内の以下の部分が変更されました。
--- a/src/pkg/bufio/bufio.go
+++ b/src/pkg/bufio/bufio.go
@@ -106,9 +106,12 @@ func (b *Reader) Peek(n int) ([]byte, error) {
if m > n {
m = n
}
- err := b.readErr()
- if m < n && err == nil {
- err = ErrBufferFull
+ var err error
+ if m < n {
+ err = b.readErr()
+ if err == nil {
+ err = ErrBufferFull
+ }
}
return b.buf[b.r : b.r+m], err
}
コアとなるコードの解説
変更の核心は、エラーの伝播ロジックの変更にあります。
変更前は、err := b.readErr()
によって、Peek
が呼び出される前に基となるリーダーで発生した可能性のあるエラー(例えば、io.EOF
)を無条件に取得していました。そして、m < n
(要求されたバイト数に満たない)かつerr == nil
の場合にのみErrBufferFull
を設定していました。このロジックでは、m >= n
(要求されたバイト数が全て提供できた)場合でも、b.readErr()
で取得したエラーがそのまま返されてしまう問題がありました。
変更後は、var err error
でerr
をnil
で初期化し、エラーを考慮する条件をif m < n
の中に限定しました。
if m < n
(要求されたバイト数に満たない場合):err = b.readErr()
: ここで初めて以前のエラーを取得します。if err == nil { err = ErrBufferFull }
: 以前のエラーがなければ、バッファが不足していることを示すErrBufferFull
を設定します。
m >= n
(要求されたバイト数が全て提供できた場合):- この
if m < n
ブロックには入らないため、err
は初期値のnil
のままとなり、Peek
はエラーを返さずに成功します。
- この
この変更により、Peek
メソッドは、実際に要求されたバイト数分のデータを正常に提供できた場合には、基となるリーダーの以前のエラー状態に関わらず、nil
を返すようになります。これにより、Peek
のセマンティクスがより直感的になり、呼び出し元はPeek
がデータを返した場合にはエラーがないことを信頼できるようになりました。
関連リンク
- Go Change-ID:
https://golang.org/cl/5674060
(このコミットに対応するGoのコードレビューページ) - Go Issue 3022:
https://golang.org/issue/3022
(このコミットが修正したバグのトラッキングページ)
参考にした情報源リンク
- コミットメッセージとコードの変更点
- Go言語の
bufio
パッケージのドキュメント (Go標準ライブラリ) - Go言語のエラーハンドリングに関する一般的な知識
io.Reader
インターフェースに関する知識io.EOF
エラーに関する知識bufio.ErrBufferFull
エラーに関する知識