<pre class="notranslate">Reported at tip with 6g

changeset:   18385:db021a4c7b4a


This program:

======
package main

import (
    "html/template"
    "os"
)

func main() {
    template.Must(template.New("test").Parse(text)).Execute(os.Stdout, nil)
}

const text = `&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;script id="test" type="html/template"&gt;
      &lt;a href="<a href="https://twitter.com/share&quot;" rel="nofollow">https://twitter.com/share"</a>;&gt;&lt;/a&gt;
      &lt;a href="<a href="https://www.facebook.com/sharer/sharer.php&quot;" rel="nofollow">https://www.facebook.com/sharer/sharer.php"</a>;&gt;&lt;/a&gt;
      &lt;a href="<a href="https://plus.google.com/share&quot;" rel="nofollow">https://plus.google.com/share"</a>;&gt;&lt;/a&gt;
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
`
======

Produces this output:

======
&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;script id="test" type="html/template"&gt;
      &lt;a href="<a href="https://twitter.com/share&quot;" rel="nofollow">https://twitter.com/share"</a>;&gt;&lt;/a&gt;
      &lt;a href="<a href="https://www.facebook.com/sharer/sharer.php&quot;" rel="nofollow">https://www.facebook.com/sharer/sharer.php"</a>;&gt;&lt;/a&gt;
      &lt;a href="https:
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
======

Notice that the third URL is truncated. Deleting either of the other two from the input
restores the final one.</pre>
<p dir="auto">by <strong>ryguillian</strong>:</p>
<pre class="notranslate">I'm running Go 1.2.

My issues are about io.MultiReader: I went to go use MultiReader and its behavior
confused me.

MultiReader's documentation sez: 

“MultiReader returns a Reader that's the logical concatenation of the provided input
readers. They're read sequentially. Once all inputs are drained, Read will return EOF.”

This documentation is pretty bad. It only says what happens once “all inputs are
drained”—whatever the hell that means. 

I wish this were just a bug report about documentation, but I'm sad to say I think
MultiReader's behavior is pretty dumb.

Like, uh, is MultiReader.Read supposed to stop after the first Reader returns EOF?
Because that's what it does.

In other words, if I run `cat file1 file2 file3` I get the concatenation of file1,
file2, and file3. Even if file2 is empty! I only use this example because Go is replete
with analogies to Unix commandlineisms (io.MultiWriter mentions 'tee(1)' explicitly).
And in case differs drastically from the expected behavior. Although in a talk I saw
online Rob Pike said something about breaking analogies, so I guess it's OK. Right?

Go is obviously very opinionated software, and that's good. But if you want the user to
use io.ReadAtLeast to get the expected behavior then document it. Although I'd argue
that using io.ReadAtLeast to invoke a continuation (think about it) is a little too
subtle for me.

I'd argue for changing the behavior of this, though. It's weird because you're providing
an abstraction over Readers and then still preserving reader boundaries. I guess this
all has to work with network code where things can get unpredictable. If that's what led
to this wacky design, then maybe offer a non-network-oriented MultiReader. This seems
more like a 'ReaderPool' or something.

It just seems hard to imagine a case where you'd have intermittently available resources
somewhere and want to still read them sequentially. I suppose I could contrive an
example, but this can't be the #1 use case. I can imagine a lot more situations where
you'd want to do something like a queue and use MultiReader to read from the queue if
it's not empty and then read from an underlying Reader otherwise. "And then"
is implicit when you use a word like "logical concatenation".

So yeah, I'd call for the redesign of
io.MultiReader.readLessThanSliceLengthOrIfEOFReturnNilCallAgainIfYouWantMore.

Thanks,

Ryan

P.S. The only code in the Go 'pkg' directory that uses MultiReader is
'io/multireader_test.go'.</pre>