From d5924ef0b4545084d26bff4287976816adb9b944 Mon Sep 17 00:00:00 2001 From: Soheil Hassas Yeganeh Date: Sun, 24 Apr 2016 12:55:13 -0400 Subject: [PATCH] Fix a blocking issue in buffer reader After sniffing and buffering data, if we try to read from the socket again, bufio.Reader may block. This breaks HTTP handlers in go1.5.2+ if one tries on browsers or with curl. Go's HTTP client, however, is not broken. This issue is also there with TeeReader. Return immediately with the data in the sniffed buffer. --- buffer.go | 23 +++++++++++++++-------- cmux_test.go | 8 +++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/buffer.go b/buffer.go index 8bb66ff..a487355 100644 --- a/buffer.go +++ b/buffer.go @@ -16,23 +16,30 @@ type bufferedReader struct { bufferRead int bufferSize int sniffing bool + lastErr error } func (s *bufferedReader) Read(p []byte) (int, error) { - // Functionality of bytes.Reader. - bn := copy(p, s.buffer.Bytes()[s.bufferRead:s.bufferSize]) - s.bufferRead += bn + if s.bufferSize > s.bufferRead { + // If we have already read something from the buffer before, we return the + // same data and the last error if any. We need to immediately return, + // otherwise we may block for ever, if we try to be smart and call + // source.Read() seeking a little bit of more data. + bn := copy(p, s.buffer.Bytes()[s.bufferRead:s.bufferSize]) + s.bufferRead += bn + return bn, s.lastErr + } - p = p[bn:] - - // Funtionality of io.TeeReader. + // If there is nothing more to return in the sniffed buffer, read from the + // source. sn, sErr := s.source.Read(p) if sn > 0 && s.sniffing { + s.lastErr = sErr if wn, wErr := s.buffer.Write(p[:sn]); wErr != nil { - return bn + wn, wErr + return wn, wErr } } - return bn + sn, sErr + return sn, sErr } func (s *bufferedReader) reset(snif bool) { diff --git a/cmux_test.go b/cmux_test.go index 669b713..0039954 100644 --- a/cmux_test.go +++ b/cmux_test.go @@ -279,7 +279,13 @@ func TestHTTP2(t *testing.T) { t.Fatal(err) } var b [len(http2.ClientPreface)]byte - if _, err := muxedConn.Read(b[:]); err != io.EOF { + var n int + // We have the sniffed buffer first... + if n, err = muxedConn.Read(b[:]); err == io.EOF { + t.Fatal(err) + } + // and then we read from the source. + if _, err = muxedConn.Read(b[n:]); err != io.EOF { t.Fatal(err) } if string(b[:]) != http2.ClientPreface {