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) + } +}