[インデックス 19498] ファイルの概要
このコミットは、Go言語の標準ライブラリ compress/gzip
パッケージにおいて、gzip.Reader
の Reset
メソッドが未初期化の Reader
インスタンスに対しても機能するように改善するものです。これにより、gzip.Reader
の再利用性が向上し、リソースの効率的な利用が可能になります。
コミット
commit 68bbf9d4642e7df8523a06b0cff37b64ea5fba57
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue Jun 3 15:40:12 2014 -0700
compress/gzip: allow Reset on Reader without NewReader
Fixes #8126.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/103020044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/68bbf9d4642e7df8523a06b0cff37b64ea5fba57
元コミット内容
compress/gzip: allow Reset on Reader without NewReader
Fixes #8126.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/103020044
変更の背景
この変更は、GoのIssue 8126「compress/gzip: Reset should work on an uninitialized Reader.」に対応するものです。
compress/gzip
パッケージの Reader
型は、GZIP圧縮されたデータを読み込むためのインターフェースを提供します。通常、Reader
インスタンスは gzip.NewReader(r io.Reader)
関数を使って作成されます。この関数は内部で Reader
のフィールドを初期化し、特にCRC32チェックサムを計算するための digest
フィールドも初期化します。
Reader
には Reset(r io.Reader)
メソッドがあり、これは既存の Reader
インスタンスを再利用して、新しい入力ストリームからGZIPデータを読み込むために設計されています。これにより、新しい Reader
を毎回アロケートするオーバーヘッドを避けることができます。
しかし、Issue 8126で報告された問題は、NewReader
を介さずに直接 var r Reader
のように宣言された未初期化の Reader
インスタンスに対して Reset
メソッドを呼び出すと、パニックが発生するというものでした。これは、Reset
メソッドが z.digest.Reset()
を呼び出す際に、z.digest
が nil
であるために発生していました。
このコミットは、この問題を解決し、Reset
メソッドが NewReader
を通さずに作成された Reader
インスタンスに対しても安全に呼び出せるようにすることで、Reader
の再利用性をさらに高めることを目的としています。
前提知識の解説
compress/gzip
パッケージ: Go言語の標準ライブラリの一部で、GZIP形式の圧縮データ(RFC 1952で定義)を読み書きするための機能を提供します。gzip.Reader
: GZIP圧縮されたデータを読み込むための型です。io.Reader
インターフェースを実装しており、Read
メソッドを通じて非圧縮データを読み出すことができます。io.Reader
インターフェース: Go言語における基本的なI/Oインターフェースの一つで、データを読み出すためのRead([]byte) (n int, err error)
メソッドを定義しています。Reset(r io.Reader) error
メソッド:gzip.Reader
のメソッドで、既存のReader
インスタンスを初期状態に戻し、新しいio.Reader
からデータを読み込めるように再設定します。これにより、オブジェクトの再利用が可能になり、ガベージコレクションの負荷を軽減できます。crc32.NewIEEE()
:hash/crc32
パッケージの関数で、IEEEポリノミアル(標準的なCRC-32)を使用して新しいCRC32ハッシュを計算するhash.Hash32
インターフェースを実装したインスタンスを返します。GZIP形式では、データの整合性チェックのためにCRC32チェックサムが使用されます。hash.Hash32
インターフェース:Write([]byte) (n int, err error)
、Sum32() uint32
、Reset()
などのメソッドを持つハッシュ計算インターフェースです。Reset()
メソッドはハッシュの状態を初期値に戻します。- オブジェクトの再利用: プログラムにおいて、頻繁に生成・破棄されるオブジェクトがある場合、それらをプールしておき、必要に応じて再利用することで、メモリのアロケーションとガベージコレクションのオーバーヘッドを削減し、パフォーマンスを向上させる手法です。
sync.Pool
などがこの目的で利用されますが、Reset
メソッドを持つ型も同様の目的で再利用されます。
技術的詳細
このコミットの核心は、gzip.Reader
の Reset
メソッドにおける z.digest
フィールドの扱いを変更した点にあります。
変更前の Reset
メソッドは、常に z.digest.Reset()
を呼び出していました。しかし、gzip.Reader
が NewReader
関数を介さずに、例えば var r Reader
のように宣言された場合、z.digest
フィールドは初期値である nil
のままです。この状態で z.digest.Reset()
を呼び出すと、nil
ポインタに対するメソッド呼び出しとなり、ランタイムパニック(panic: runtime error: invalid memory address or nil pointer dereference
)が発生していました。
このコミットでは、Reset
メソッドの冒頭に以下の条件分岐を追加することでこの問題を解決しています。
if z.digest == nil {
z.digest = crc32.NewIEEE()
} else {
z.digest.Reset()
}
この変更により、Reset
メソッドが呼び出された際に、z.digest
が nil
である(つまり、Reader
がまだ NewReader
で初期化されていない)場合は、新しく crc32.NewIEEE()
を呼び出して digest
フィールドを初期化します。z.digest
が既に初期化されている(NewReader
を介して作成された、または以前の Reset
呼び出しで初期化された)場合は、これまで通り z.digest.Reset()
を呼び出して、既存のハッシュ計算器の状態をリセットします。
これにより、Reset
メソッドは、Reader
インスタンスがどのような状態であっても安全に呼び出せるようになり、Reader
の柔軟な再利用が可能になりました。
また、この変更を検証するために、gunzip_test.go
に TestInitialReset
という新しいテストケースが追加されています。このテストは、var r Reader
で宣言された未初期化の Reader
インスタンスに対して Reset
メソッドを呼び出し、その後にGZIPデータを正しく読み込めることを確認しています。
コアとなるコードの変更箇所
変更は主に src/pkg/compress/gzip/gunzip.go
ファイルの Reader.Reset
メソッドにあります。
--- a/src/pkg/compress/gzip/gunzip.go
+++ b/src/pkg/compress/gzip/gunzip.go
@@ -94,7 +94,11 @@ func NewReader(r io.Reader) (*Reader, error) {
// This permits reusing a Reader rather than allocating a new one.
func (z *Reader) Reset(r io.Reader) error {
z.r = makeReader(r)
- z.digest.Reset()
+ if z.digest == nil {
+ z.digest = crc32.NewIEEE()
+ } else {
+ z.digest.Reset()
+ }
z.size = 0
z.err = nil
return z.readHeader(true)
また、この変更を検証するためのテストコードが src/pkg/compress/gzip/gunzip_test.go
に追加されています。
--- a/src/pkg/compress/gzip/gunzip_test.go
+++ b/src/pkg/compress/gzip/gunzip_test.go
@@ -353,3 +353,17 @@ func TestIssue6550(t *testing.T) {
// ok
}
}
+
+func TestInitialReset(t *testing.T) {
+ var r Reader
+ if err := r.Reset(bytes.NewReader(gunzipTests[1].gzip)); err != nil {
+ t.Error(err)
+ }
+ var buf bytes.Buffer
+ if _, err := io.Copy(&buf, &r); err != nil {
+ t.Error(err)
+ }
+ if s := buf.String(); s != gunzipTests[1].raw {
+ t.Errorf("got %q want %q", s, gunzipTests[1].raw)
+ }
+}
コアとなるコードの解説
gunzip.go
の Reader.Reset
メソッドにおける変更は、z.digest
フィールドが nil
である可能性を考慮に入れたものです。
if z.digest == nil
ブロック:- これは、
Reader
インスタンスがNewReader
関数を介さずに、例えばvar r Reader
のように直接宣言された場合に実行されます。 - この場合、
z.digest
はまだ初期化されていないためnil
です。 z.digest = crc32.NewIEEE()
によって、新しくCRC32ハッシュ計算器が作成され、z.digest
フィールドに割り当てられます。これにより、後続のGZIPデータ読み込みにおけるCRC32チェックサム計算の準備が整います。
- これは、
else
ブロック (z.digest.Reset()
):- これは、
Reader
インスタンスが既にNewReader
を介して初期化されているか、または以前のReset
呼び出しでz.digest
が初期化されている場合に実行されます。 - この場合、既存の
z.digest
インスタンスのReset()
メソッドが呼び出され、ハッシュ計算器の状態がリセットされます。これにより、新しい入力ストリームに対するCRC32計算が最初から行われるようになります。
- これは、
この変更により、Reset
メソッドは、Reader
インスタンスの初期化状態に関わらず、常に安全かつ正しく動作するようになりました。
gunzip_test.go
に追加された TestInitialReset
テストは、この修正が意図通りに機能することを確認します。
var r Reader
でReader
インスタンスを宣言し、明示的にNewReader
を呼び出さずに未初期化の状態で開始します。r.Reset(...)
を呼び出し、未初期化のReader
に対してReset
が成功することを確認します。io.Copy
を使ってGZIPデータを読み込み、エラーが発生しないこと、そして非圧縮データが期待通りであることを検証します。
このテストは、Reset
メソッドが nil
の digest
フィールドを適切に処理し、Reader
が正常に機能することを示す重要な役割を果たしています。
関連リンク
- Go Issue 8126: https://github.com/golang/go/issues/8126
- Go CL 103020044: https://golang.org/cl/103020044
参考にした情報源リンク
- Go Issue 8126のGitHubページ: https://github.com/golang/go/issues/8126
- Go言語の
compress/gzip
パッケージドキュメント: https://pkg.go.dev/compress/gzip - Go言語の
hash/crc32
パッケージドキュメント: https://pkg.go.dev/hash/crc32 - Go言語の
io
パッケージドキュメント: https://pkg.go.dev/io - RFC 1952 (GZIPファイル形式仕様): https://www.rfc-editor.org/rfc/rfc1952