Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 16870] ファイルの概要

このコミットは、Go言語の標準ライブラリであるbufioパッケージ内のWriter.ReadFromメソッドにおけるバッファの可用性チェックに関する修正を導入しています。具体的には、ReadFromがデータを読み込む前にバッファが満杯でないかを確認し、必要に応じてフラッシュするロジックが追加されました。これにより、バッファが満杯の状態でReadFromが呼び出された際に発生しうる潜在的な問題(無限ループや不正確な動作)が解決されます。

コミット

commit 93c6d0ef8f7c081f961ea3efe10e61a5b585cfbb
Author: Andrew Gerrand <adg@golang.org>
Date:   Thu Jul 25 11:29:13 2013 +1000

    bufio: check buffer availability before reading in ReadFrom
    
    Fixes #5947.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/11801043
---
 src/pkg/bufio/bufio.go      | 17 +++++++++++------
 src/pkg/bufio/bufio_test.go | 22 ++++++++++++++++++++++\
 2 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/src/pkg/bufio/bufio.go b/src/pkg/bufio/bufio.go
index 4df5e32d9a..993c22d61c 100644
--- a/src/pkg/bufio/bufio.go
+++ b/src/pkg/bufio/bufio.go
@@ -678,23 +678,28 @@ func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
 	}
 	var m int
 	for {
+\t\tif b.Available() == 0 {\n+\t\t\tif err1 := b.flush(); err1 != nil {\n+\t\t\t\treturn n, err1\n+\t\t\t}\n+\t\t}\
 	\tm, err = r.Read(b.buf[b.n:])
 	\tif m == 0 {\n \t\t\tbreak
 	\t}\
 	\tb.n += m
 	\tn += int64(m)
-\t\tif b.Available() == 0 {\n-\t\t\tif err1 := b.flush(); err1 != nil {\n-\t\t\t\treturn n, err1\n-\t\t\t}\n-\t\t}\
 	\tif err != nil {\n \t\t\tbreak
 	\t}\
 	}
 	\tif err == io.EOF {\n-\t\terr = nil
+\t\t// If we filled the buffer exactly, flush pre-emptively.\n+\t\tif b.Available() == 0 {\n+\t\t\terr = b.flush()\n+\t\t} else {\n+\t\t\terr = nil\n+\t\t}\
 	}
 	\treturn n, err
 }
diff --git a/src/pkg/bufio/bufio_test.go b/src/pkg/bufio/bufio_test.go
index 93f1b3fe08..68a138e5c1 100644
--- a/src/pkg/bufio/bufio_test.go
+++ b/src/pkg/bufio/bufio_test.go
@@ -847,6 +847,10 @@ func TestWriterReadFrom(t *testing.T) {
 	\t\t\tt.Errorf("ws[%d],rs[%d]: w.ReadFrom(r) = %d, %v, want %d, nil", wi, ri, n, err, len(input))\n \t\t\t\tcontinue
 	\t\t}\n+\t\t\tif err := w.Flush(); err != nil {\n+\t\t\t\tt.Errorf("Flush returned %v", err)\n+\t\t\t\tcontinue\n+\t\t\t}\
 	\t\tif got, want := b.String(), string(input); got != want {\n \t\t\t\tt.Errorf("ws[%d], rs[%d]:\\ngot  %q\\nwant %q\\n", wi, ri, got, want)\n \t\t\t}\
 @@ -1003,6 +1007,24 @@ func TestReaderClearError(t *testing.T) {
 	}\n }\n \n+// Test for golang.org/issue/5947\n+func TestWriterReadFromWhileFull(t *testing.T) {\n+\tbuf := new(bytes.Buffer)\n+\tw := NewWriterSize(buf, 10)\n+\n+\t// Fill buffer exactly.\n+\tn, err := w.Write([]byte("0123456789"))\n+\tif n != 10 || err != nil {\n+\t\tt.Fatalf("Write returned (%v, %v), want (10, nil)", n, err)\n+\t}\n+\n+\t// Use ReadFrom to read in some data.\n+\tn2, err := w.ReadFrom(strings.NewReader("abcdef"))\n+\tif n2 != 6 || err != nil {\n+\t\tt.Fatalf("ReadFrom returned (%v, %v), want (6, nil)", n, err)\n+\t}\n+}\n+\n // An onlyReader only implements io.Reader, no matter what other methods the underlying implementation may have.\n type onlyReader struct {\n \tr io.Reader\n```

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/93c6d0ef8f7c081f961ea3efe10e61a5b585cfbb](https://github.com/golang/go/commit/93c6d0ef8f7c081f961ea3efe10e61a5b585cfbb)

## 元コミット内容

`bufio: check buffer availability before reading in ReadFrom`

このコミットは、`bufio`パッケージの`ReadFrom`メソッドにおいて、読み込みを行う前にバッファの空き容量を確認するように変更します。これにより、Issue #5947を修正します。

## 変更の背景

`bufio.Writer`の`ReadFrom`メソッドは、`io.Reader`からデータを読み込み、自身の内部バッファに格納し、最終的にそのバッファの内容を基となる`io.Writer`に書き出す役割を担っています。このコミットが導入される前は、`ReadFrom`メソッドの内部ループにおいて、まず`io.Reader`からデータを読み込もうとし、その後にバッファが満杯になったかどうかをチェックし、必要であればフラッシュ(基となる`io.Writer`への書き出し)を行っていました。

この既存のロジックには、以下のような潜在的な問題がありました。

1.  **バッファが既に満杯の場合の非効率性または問題:** `ReadFrom`が呼び出された時点で`bufio.Writer`の内部バッファが既に満杯である場合、`r.Read(b.buf[b.n:])`の呼び出しは、読み込むべきスペースがないため、`m=0`(読み込んだバイト数0)を返す可能性があります。このとき、`err`が`nil`であると、ループは継続されますが、バッファはフラッシュされないため、無限ループに陥るか、あるいはデータが適切に処理されない状態になる可能性がありました。特に、`io.Reader`が一時的にデータを持たないがEOFではない場合(例: ネットワークI/Oでデータがまだ到着していない場合など)に、この問題が顕在化しやすくなります。
2.  **予期せぬ動作:** `ReadFrom`の目的は、ソースからデータを効率的に読み込み、バッファリングして書き出すことです。バッファが満杯であるにもかかわらず、読み込みを試みるのは非効率であり、上記のような問題を引き起こす原因となります。

このコミットは、このような問題を解決し、`ReadFrom`メソッドの堅牢性と正確性を向上させることを目的としています。

## 前提知識の解説

このコミットを理解するためには、以下のGo言語の概念と`bufio`パッケージの基本的な知識が必要です。

1.  **`io.Reader`と`io.Writer`インターフェース:**
    *   `io.Reader`は、`Read(p []byte) (n int, err error)`メソッドを持つインターフェースです。これは、データをバイトスライス`p`に読み込み、読み込んだバイト数`n`とエラー`err`を返します。
    *   `io.Writer`は、`Write(p []byte) (n int, err error)`メソッドを持つインターフェースです。これは、バイトスライス`p`のデータを書き込み、書き込んだバイト数`n`とエラー`err`を返します。
    *   これらのインターフェースは、Go言語におけるI/O操作の基本的な抽象化を提供し、様々なデータソース(ファイル、ネットワーク接続、メモリなど)やデータシンク(ファイル、ネットワーク接続、メモリなど)に対して統一的なI/O操作を可能にします。

2.  **`bufio`パッケージ:**
    *   `bufio`パッケージは、I/O操作をバッファリングすることで、効率を向上させるための機能を提供します。ディスクI/OやネットワークI/Oのような低速な操作では、一度に少量のデータを何度も読み書きするよりも、ある程度のデータをまとめて読み書きする方が効率的です。
    *   **`bufio.Reader`:** `io.Reader`をラップし、内部バッファを使用して読み込みをバッファリングします。
    *   **`bufio.Writer`:** `io.Writer`をラップし、内部バッファを使用して書き込みをバッファリングします。データは、バッファが満杯になったとき、明示的に`Flush`が呼び出されたとき、または`Close`されたときに、基となる`io.Writer`に書き込まれます。
    *   **`Writer.ReadFrom(r io.Reader) (n int64, err error)`メソッド:** このメソッドは、`bufio.Writer`が提供する便利な機能で、指定された`io.Reader`からすべてのデータを読み込み、それを自身の内部バッファを介して基となる`io.Writer`に効率的に転送します。これは、`io.Copy`の`Writer`側の最適化版と考えることができます。

3.  **バッファリングの仕組み:**
    *   `bufio.Writer`は、内部にバイトスライス(`b.buf`)をバッファとして持っています。`b.n`は、現在バッファに格納されているデータのバイト数を示します。
    *   `b.Available()`メソッドは、バッファに残っている空き容量(バイト数)を返します。`len(b.buf) - b.n`で計算されます。
    *   `b.flush()`メソッドは、バッファに格納されているすべてのデータを基となる`io.Writer`に書き出し、バッファをクリアします。

これらの概念を理解することで、コミットが`bufio.Writer.ReadFrom`の動作をどのように改善しているかを深く把握することができます。

## 技術的詳細

このコミットの技術的な変更は、主に`src/pkg/bufio/bufio.go`の`Writer.ReadFrom`メソッドと、それに対応するテストケース`src/pkg/bufio/bufio_test.go`に集中しています。

### `src/pkg/bufio/bufio.go` の変更点

`Writer.ReadFrom`メソッドの主要な変更は、データの読み込みループ内でのバッファのフラッシュロジックの移動です。

**変更前:**

```go
	for {
		m, err = r.Read(b.buf[b.n:])
		if m == 0 {
			break
		}
		b.n += m
		n += int64(m)
		if b.Available() == 0 { // 読み込み後にバッファが満杯かチェック
			if err1 := b.flush(); err1 != nil {
				return n, err1
			}
		}
		if err != nil {
			break
		}
	}
	if err == io.EOF {
		err = nil
	}

変更後:

	for {
		if b.Available() == 0 { // 読み込み前にバッファが満杯かチェック
			if err1 := b.flush(); err1 != nil {
				return n, err1
			}
		}
		m, err = r.Read(b.buf[b.n:])
		if m == 0 {
			break
		}
		b.n += m
		n += int64(m)
		// 以前のフラッシュロジックは削除された
		if err != nil {
			break
		}
	}
	if err == io.EOF {
		// If we filled the buffer exactly, flush pre-emptively.
		if b.Available() == 0 { // EOFでバッファが満杯の場合、先行してフラッシュ
			err = b.flush()
		} else {
			err = nil
		}
	}

変更の意図:

  • ループ内のフラッシュロジックの移動: 最も重要な変更は、b.Available() == 0のチェックとb.flush()の呼び出しが、r.Readの呼び出しのに移動したことです。これにより、r.Readが呼び出される際には、常にバッファに空きスペースがあることが保証されます。これにより、バッファが満杯の状態でr.Readが呼び出され、m=0を返して無限ループに陥る可能性がなくなります。
  • EOF処理の改善: io.EOFが検出された際の処理も変更されました。以前は単にerr = nilとしていましたが、変更後は、もしバッファがちょうど満杯になった状態でio.EOFが検出された場合(つまり、b.Available() == 0)、そのバッファを先行してフラッシュするようになりました。これは、ReadFromが完了する前に、読み込んだすべてのデータが基となるio.Writerに書き出されることを保証するためのものです。それ以外の場合は、以前と同様にerr = nilとします。

src/pkg/bufio/bufio_test.go の変更点

テストファイルには、既存のテストの修正と新しいテストケースの追加が行われました。

  1. TestWriterReadFromの修正:

    • 既存のTestWriterReadFromテストケースに、w.ReadFrom(r)の呼び出し後にw.Flush()の呼び出しが追加されました。これは、ReadFromが完了した後にバッファに残っているデータが確実に基となるbytes.Bufferに書き出されるようにするためです。これにより、テストがReadFromの正確な動作を検証できるようになります。
  2. TestWriterReadFromWhileFullの追加:

    • golang.org/issue/5947を具体的にテストするための新しいテスト関数TestWriterReadFromWhileFullが追加されました。
    • このテストは、まずbufio.Writerのバッファを正確に満杯にします(例: サイズ10のバッファに10バイト書き込む)。
    • 次に、バッファが満杯の状態でw.ReadFromを呼び出し、さらにデータを読み込もうとします。
    • このテストの目的は、バッファが満杯の状態からReadFromが呼び出されたときに、正しくバッファがフラッシュされ、新しいデータが読み込まれることを検証することです。これにより、以前のバグが修正されたことを確認します。

これらの変更により、bufio.Writer.ReadFromメソッドはより堅牢になり、予期せぬ動作を防ぐことができるようになりました。

コアとなるコードの変更箇所

src/pkg/bufio/bufio.go

--- a/src/pkg/bufio/bufio.go
+++ b/src/pkg/bufio/bufio.go
@@ -678,23 +678,28 @@ func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
 	}
 	var m int
 	for {
+\t\tif b.Available() == 0 {\n+\t\t\tif err1 := b.flush(); err1 != nil {\n+\t\t\t\treturn n, err1\n+\t\t\t}\n+\t\t}\
 	\tm, err = r.Read(b.buf[b.n:])
 	\tif m == 0 {\n \t\t\tbreak
 	\t}\
 	\tb.n += m
 	\tn += int64(m)
-\t\tif b.Available() == 0 {\n-\t\t\tif err1 := b.flush(); err1 != nil {\n-\t\t\t\treturn n, err1\n-\t\t\t}\n-\t\t}\
 	\tif err != nil {\n \t\t\tbreak
 	\t}\
 	}
 	\tif err == io.EOF {\n-\t\terr = nil
+\t\t// If we filled the buffer exactly, flush pre-emptively.\n+\t\tif b.Available() == 0 {\n+\t\t\terr = b.flush()\n+\t\t} else {\n+\t\t\terr = nil\n+\t\t}\
 	}
 	\treturn n, err
 }

src/pkg/bufio/bufio_test.go

--- a/src/pkg/bufio/bufio_test.go
+++ b/src/pkg/bufio/bufio_test.go
@@ -847,6 +847,10 @@ func TestWriterReadFrom(t *testing.T) {
 	\t\t\tt.Errorf("ws[%d],rs[%d]: w.ReadFrom(r) = %d, %v, want %d, nil", wi, ri, n, err, len(input))\n \t\t\t\tcontinue
 	\t\t}\n+\t\t\tif err := w.Flush(); err != nil {\n+\t\t\t\tt.Errorf("Flush returned %v", err)\n+\t\t\t\tcontinue\n+\t\t\t}\
 	\t\tif got, want := b.String(), string(input); got != want {\n \t\t\t\tt.Errorf("ws[%d], rs[%d]:\\ngot  %q\\nwant %q\\n", wi, ri, got, want)\n \t\t\t}\
 @@ -1003,6 +1007,24 @@ func TestReaderClearError(t *testing.T) {
 	}\n }\n \n+// Test for golang.org/issue/5947\n+func TestWriterReadFromWhileFull(t *testing.T) {\n+\tbuf := new(bytes.Buffer)\n+\tw := NewWriterSize(buf, 10)\n+\n+\t// Fill buffer exactly.\n+\tn, err := w.Write([]byte("0123456789"))\n+\tif n != 10 || err != nil {\n+\t\tt.Fatalf("Write returned (%v, %v), want (10, nil)", n, err)\n+\t}\n+\n+\t// Use ReadFrom to read in some data.\n+\tn2, err := w.ReadFrom(strings.NewReader("abcdef"))\n+\tif n2 != 6 || err != nil {\n+\t\tt.Fatalf("ReadFrom returned (%v, %v), want (6, nil)", n, err)\n+\t}\n+}\n+\n // An onlyReader only implements io.Reader, no matter what other methods the underlying implementation may have.\n type onlyReader struct {\n \tr io.Reader\n```

## コアとなるコードの解説

### `bufio.go` の変更点

`Writer.ReadFrom`メソッドのループ内で、`r.Read`を呼び出す前に`b.Available() == 0`(バッファに空きがないか)をチェックし、もし空きがなければ`b.flush()`を呼び出してバッファの内容を基となる`io.Writer`に書き出しています。これにより、`r.Read`が常に空きのあるバッファにデータを読み込もうとすることが保証され、バッファが満杯の状態で`r.Read`が`m=0`を返すことによる無限ループや不正確な動作が回避されます。

また、ループの終了条件である`err == io.EOF`の処理も変更されました。以前は単に`err = nil`としていましたが、変更後は、`io.EOF`が検出された時点でバッファがちょうど満杯であった場合(`b.Available() == 0`)、`b.flush()`を呼び出してバッファの内容を先行して書き出すようになりました。これにより、`ReadFrom`が正常に終了する際に、読み込んだすべてのデータが確実にフラッシュされることが保証されます。

### `bufio_test.go` の変更点

1.  **`TestWriterReadFrom`の修正:**
    `w.ReadFrom(r)`の呼び出し後に`w.Flush()`が追加されました。これは、`ReadFrom`が内部バッファにデータを保持したまま終了する可能性があるため、テストの検証のために明示的にバッファをフラッシュして、基となる`bytes.Buffer`にデータが書き込まれるようにするためです。これにより、`b.String()`で取得される内容が期待通りになることを保証します。

2.  **`TestWriterReadFromWhileFull`の追加:**
    この新しいテストは、`bufio.Writer`のバッファが満杯の状態で`ReadFrom`が呼び出された場合の挙動を検証します。
    *   まず、`NewWriterSize(buf, 10)`でサイズ10のバッファを持つ`Writer`を作成します。
    *   `w.Write([]byte("0123456789"))`でバッファを正確に満杯にします。この時点では、データはまだ基となる`bytes.Buffer`には書き出されていません。
    *   `w.ReadFrom(strings.NewReader("abcdef"))`を呼び出します。この呼び出しにより、`ReadFrom`メソッド内の新しいロジックが発動し、`r.Read`を呼び出す前にバッファが満杯であることを検知して`flush()`が実行されます。これにより、`"0123456789"`が`buf`に書き出され、バッファがクリアされます。その後、`"abcdef"`がバッファに読み込まれます。
    *   テストは、`ReadFrom`が正しく6バイトを読み込み、エラーがないことを確認します。このテストは、バッファが満杯の状況での`ReadFrom`の堅牢な動作を保証します。

これらのコード変更により、`bufio.Writer.ReadFrom`はより予測可能で堅牢な動作をするようになり、特定のコーナーケースでのバグが修正されました。

## 関連リンク

*   **Go Gerrit Code Review:** [https://golang.org/cl/11801043](https://golang.org/cl/11801043)
*   **GitHub Commit:** [https://github.com/golang/go/commit/93c6d0ef8f7c081f961ea3efe10e61a5b585cfbb](https://github.com/golang/go/commit/93c6d0ef8f7c081f961ea3efe10e61a5b585cfbb)

## 参考にした情報源リンク

*   Go言語の`io`パッケージドキュメント: [https://pkg.go.dev/io](https://pkg.go.dev/io)
*   Go言語の`bufio`パッケージドキュメント: [https://pkg.go.dev/bufio](https://pkg.go.dev/bufio)
*   Go言語のIssueトラッカー (一般的な情報源として): [https://github.com/golang/go/issues](https://github.com/golang/go/issues)