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