[インデックス 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:
io
package: https://pkg.go.dev/io - Go Documentation:
io.ReaderAt
interface: https://pkg.go.dev/io#ReaderAt - Go Documentation:
io.SectionReader
type: https://pkg.go.dev/io#SectionReader - Go Documentation:
io.EOF
variable: https://pkg.go.dev/io#EOF - Go Blog: Errors are values: https://go.dev/blog/errors-are-values (Goにおけるエラーハンドリングの一般的な慣習について)