[インデックス 14639] ファイルの概要
このコミットは、Go言語の標準ライブラリ io パッケージ内の SectionReader 型の ReadAt メソッドにおけるバグを修正するものです。具体的には、SectionReader.ReadAt が、読み取り対象のセクションの終端に到達し、かつ提供されたバッファが完全に埋まらなかった場合に、正しく io.EOF を返さないという問題に対処しています。
コミット
commit 31eedd7f3ee26a22677f057bf61e4143e9e322cc
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu Dec 13 18:36:24 2012 +0800
io: SectionReader.ReadAt should return EOF when buf is not fully read
Fixes #4392.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6858062
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/31eedd7f3ee26a22677f057bf61e4143e9e322cc
元コミット内容
io: SectionReader.ReadAt should return EOF when buf is not fully read
Fixes #4392.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/6858062
変更の背景
このコミットは、Go言語のIssue #4392 を修正するために行われました。io.SectionReader は、基となる ReaderAt から特定のセクション(範囲)のみを読み取るための構造体です。その ReadAt メソッドは、指定されたオフセットからデータを読み込み、提供されたバイトスライス(バッファ)に書き込みます。
問題は、SectionReader が読み取り対象とするセクションの終端に到達し、かつ ReadAt に渡されたバッファが完全に埋まらなかった場合に発生していました。本来、このような状況では、読み取れたバイト数と共に io.EOF (End Of File) エラーを返すのがGoの io パッケージにおける一般的な慣習です。しかし、修正前の SectionReader.ReadAt は、バッファが完全に埋まらなかった場合でも EOF を返さず、単に読み取れたバイト数と nil エラーを返していました。
これにより、SectionReader を利用する側が、データの終端に達したことを正確に検知できず、予期せぬ動作や無限ループを引き起こす可能性がありました。このコミットは、この EOF の振る舞いをGoの io パッケージの他の部分と一貫させることを目的としています。
前提知識の解説
ioパッケージ: Go言語の標準ライブラリで、I/Oプリミティブを提供します。ファイル、ネットワーク接続、メモリバッファなど、様々なソースからの読み書きを抽象化するためのインターフェースと実装が含まれています。io.ReaderAtインターフェース:type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error) }ReadAtメソッドは、pに最大len(p)バイトを読み込み、offで指定されたオフセットから開始します。読み取られたバイト数nとエラーerrを返します。ReadAtは、基となるデータソースのオフセットを変更しないという特性があります。io.SectionReader:SectionReaderは、ReaderAtインターフェースを実装する型で、基となるReaderAtから特定のセクション(開始オフセットoffから長さnまでの範囲)のみを読み取るように制限します。これは、大きなファイルやデータストリームの一部を扱う際に便利です。NewSectionReader(r ReaderAt, off int64, n int64)関数で作成されます。io.EOF:io.EOFは、入力の終端に到達したことを示すエラー変数です。ReadやReadAtのような読み取り操作が、それ以上読み取るデータがない場合に、読み取れたバイト数と共にio.EOFを返すのがGoの慣習です。これは、エラーとして扱われますが、通常は正常な終了条件として解釈されます。
技術的詳細
修正前の SectionReader.ReadAt のロジックでは、読み取り要求されたバイト数(len(p))が、SectionReader が扱うセクションの残りのバイト数(max)よりも大きい場合、p のサイズを max に切り詰めてから基となる ReaderAt の ReadAt を呼び出していました。
// 修正前 (簡略化)
func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) {
off += s.base
if max := s.limit - off; int64(len(p)) > max {
p = p[0:max] // ここでバッファが切り詰められる
}
return s.r.ReadAt(p, off) // 基のReaderAtのReadAtを呼び出す
}
このコードの問題点は、s.r.ReadAt(p, off) が nil エラーを返した場合でも、p が max に切り詰められた結果、元の p のサイズよりも少ないバイトしか読み取れなかった場合に、io.EOF を返すべきであるにもかかわらず、それを返していなかったことです。
Goの io パッケージの慣習では、Read や ReadAt は、要求されたバイト数よりも少ないバイトしか読み取れなかった場合、かつそれ以上読み取るデータがない場合に io.EOF を返します。SectionReader の場合、そのセクションの終端に到達したことが「それ以上読み取るデータがない」状態に相当します。
このコミットは、この慣習に従うように SectionReader.ReadAt の動作を変更しました。
コアとなるコードの変更箇所
src/pkg/io/io.go の SectionReader.ReadAt メソッドに以下の変更が加えられました。
--- a/src/pkg/io/io.go
+++ b/src/pkg/io/io.go
@@ -468,6 +468,11 @@ func (s *SectionReader) ReadAt(p []byte, off int64) (n int, err error) {
off += s.base
if max := s.limit - off; int64(len(p)) > max {
p = p[0:max]
+ n, err = s.r.ReadAt(p, off)
+ if err == nil {
+ err = EOF
+ }
+ return n, err
}
return s.r.ReadAt(p, off)
}
コアとなるコードの解説
変更は SectionReader.ReadAt メソッド内の if ブロック内で行われています。
if max := s.limit - off; int64(len(p)) > max {
p = p[0:max]
+ n, err = s.r.ReadAt(p, off) // 基のReaderAtからデータを読み込む
+ if err == nil { // 基のReaderAtがエラーを返さなかった場合
+ err = EOF // SectionReaderとしてはEOFを返す
+ }
+ return n, err // 読み取れたバイト数とエラーを返す
}
この変更のポイントは以下の通りです。
- バッファの切り詰め:
if int64(len(p)) > maxの条件は、要求された読み取りサイズlen(p)が、SectionReaderが扱うセクションの残りのバイト数maxを超えていることを意味します。この場合、p = p[0:max]によって、実際に読み取れる最大バイト数に合わせてバッファpが切り詰められます。 - 基の
ReadAtの呼び出し: 切り詰められたpと計算されたオフセットoffを使って、基となるs.r(元のReaderAt) のReadAtメソッドが呼び出されます。この呼び出しの結果、実際に読み取れたバイト数nとエラーerrが得られます。 EOFの判定と返却:if err == nilの条件は、基のReaderAtが読み取り中にエラーを発生させなかったことを意味します。- しかし、この
ifブロックに入っているということは、SectionReaderが扱うセクションの終端に到達したため、要求されたバッファを完全に埋めることができなかった状況です。 - したがって、基の
ReaderAtがエラーを返さなかったとしても、SectionReaderの観点からはデータの終端に達したと判断し、err = EOFを設定します。
- 結果の返却: 最後に、実際に読み取れたバイト数
nと、適切に設定されたエラーerr(nilまたはEOF) が返されます。
この修正により、SectionReader.ReadAt は、セクションの終端に到達し、かつバッファが完全に埋まらなかった場合に、Goの io パッケージの慣習に従って io.EOF を返すようになり、より予測可能で堅牢な動作を実現しました。
また、src/pkg/io/io_test.go には、この修正を検証するための新しいテストケース TestSectionReader_ReadAt が追加されています。このテストは、様々なシナリオ(空のデータ、セクションの終端での読み取り、バッファが完全に埋まらないケースなど)で SectionReader.ReadAt が期待通りのバイト数とエラー(特に EOF)を返すことを確認しています。
関連リンク
- Go CL 6858062: https://golang.org/cl/6858062
- Go Issue 4392: https://github.com/golang/go/issues/4392
参考にした情報源リンク
- Go Documentation:
iopackage: https://pkg.go.dev/io - Go Documentation:
io.ReaderAtinterface: https://pkg.go.dev/io#ReaderAt - Go Documentation:
io.SectionReadertype: https://pkg.go.dev/io#SectionReader - Go Documentation:
io.EOFvariable: https://pkg.go.dev/io#EOF - Go Blog: Errors are values: https://go.dev/blog/errors-are-values (Goにおけるエラーハンドリングの一般的な慣習について)