io: make chained multiReader Read more efficient
before this change, when io.MultiReader was called many times but contain few
underlying readers, calls to Read were unnecessarily expensive.
Fixes #13558
Change-Id: I3ec4e88c7b50c075b148331fb1b7348a5840adbe
Reviewed-on: https://go-review.googlesource.com/17873
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/src/io/multi_test.go b/src/io/multi_test.go
index 787ea34..2dce369 100644
--- a/src/io/multi_test.go
+++ b/src/io/multi_test.go
@@ -7,9 +7,11 @@
import (
"bytes"
"crypto/sha1"
+ "errors"
"fmt"
. "io"
"io/ioutil"
+ "runtime"
"strings"
"testing"
)
@@ -164,3 +166,33 @@
t.Errorf("buf.String() = %q, want %q", buf.String(), "hello world")
}
}
+
+// readerFunc is an io.Reader implemented by the underlying func.
+type readerFunc func(p []byte) (int, error)
+
+func (f readerFunc) Read(p []byte) (int, error) {
+ return f(p)
+}
+
+// Test that MultiReader properly flattens chained multiReaders when Read is called
+func TestMultiReaderFlatten(t *testing.T) {
+ pc := make([]uintptr, 1000) // 1000 should fit the full stack
+ var myDepth = runtime.Callers(0, pc)
+ var readDepth int // will contain the depth from which fakeReader.Read was called
+ var r Reader = MultiReader(readerFunc(func(p []byte) (int, error) {
+ readDepth = runtime.Callers(1, pc)
+ return 0, errors.New("irrelevant")
+ }))
+
+ // chain a bunch of multiReaders
+ for i := 0; i < 100; i++ {
+ r = MultiReader(r)
+ }
+
+ r.Read(nil) // don't care about errors, just want to check the call-depth for Read
+
+ if readDepth != myDepth+2 { // 2 should be multiReader.Read and fakeReader.Read
+ t.Errorf("multiReader did not flatten chained multiReaders: expected readDepth %d, got %d",
+ myDepth+2, readDepth)
+ }
+}