Skip to content

Commit 3e35716

Browse files
authored
wsflate: avoid double buffering when using flate.Reader (#137)
This commit makes wsflate.suffixedReader to optionally implement io.ByteReader. That is, if source object is buffered (bytes.Reader, bufio.Reader etc.) and implements io.ByteReader, but suffixedReader not, then flate.NewReader() will allocate new bufio.Reader for it. This commit fixes it and adds runtime checks on suffixedReader source to hide ReadByte() method if source is a pure io.Reader. Fixes #130
1 parent 272ac76 commit 3e35716

3 files changed

Lines changed: 80 additions & 2 deletions

File tree

wsflate/cbuf.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ type suffixedReader struct {
6161
r io.Reader
6262
pos int // position in the suffix.
6363
suffix [9]byte
64+
65+
rx struct{ io.Reader }
66+
}
67+
68+
func (r *suffixedReader) iface() io.Reader {
69+
if _, ok := r.r.(io.ByteReader); ok {
70+
// If source io.Reader implements io.ByteReader, return full set of
71+
// methods from suffixedReader struct (Read() and ReadByte()).
72+
// This actually is an optimization needed for those Decompressor
73+
// implementations (such as default flate.Reader) which do check if
74+
// given source is already "buffered" by checking if source implements
75+
// io.ByteReader. So without this checks we will always result in
76+
// double-buffering for default decompressors.
77+
return r
78+
}
79+
// Source io.Reader doesn't support io.ByteReader, so we should cut off the
80+
// ReadByte() method from suffixedReader struct. We use r.srx field to
81+
// avoid allocations.
82+
r.rx.Reader = r
83+
return &r.rx
6484
}
6585

6686
func (r *suffixedReader) Read(p []byte) (n int, err error) {
@@ -80,6 +100,27 @@ func (r *suffixedReader) Read(p []byte) (n int, err error) {
80100
return n, nil
81101
}
82102

103+
func (r *suffixedReader) ReadByte() (b byte, err error) {
104+
if r.r != nil {
105+
br, ok := r.r.(io.ByteReader)
106+
if !ok {
107+
panic("wsflate: internal error: incorrect use of suffixedReader")
108+
}
109+
b, err = br.ReadByte()
110+
if err == io.EOF {
111+
err = nil
112+
r.r = nil
113+
}
114+
return b, err
115+
}
116+
if r.pos >= len(r.suffix) {
117+
return 0, io.EOF
118+
}
119+
b = r.suffix[r.pos]
120+
r.pos += 1
121+
return b, nil
122+
}
123+
83124
func (r *suffixedReader) reset(src io.Reader) {
84125
r.r = src
85126
r.pos = 0

wsflate/reader.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ func (r *Reader) Reset(src io.Reader) {
5050
r.err = nil
5151
r.src = src
5252
r.sr.reset(src)
53+
5354
if x, ok := r.d.(ReadResetter); ok {
54-
x.Reset(&r.sr)
55+
x.Reset(r.sr.iface())
5556
} else {
56-
r.d = r.ctor(&r.sr)
57+
r.d = r.ctor(r.sr.iface())
5758
}
5859
}
5960

wsflate/reader_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,37 @@
11
package wsflate
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"testing"
8+
)
9+
10+
func TestSuffixedReaderIface(t *testing.T) {
11+
for _, test := range []struct {
12+
src io.Reader
13+
exp bool
14+
}{
15+
{
16+
src: bytes.NewReader(nil),
17+
exp: true,
18+
},
19+
{
20+
src: io.TeeReader(nil, nil),
21+
exp: false,
22+
},
23+
} {
24+
t.Run(fmt.Sprintf("%T", test.src), func(t *testing.T) {
25+
isByteReader := func(r io.Reader) bool {
26+
_, ok := r.(io.ByteReader)
27+
return ok
28+
}
29+
s := &suffixedReader{
30+
r: test.src,
31+
}
32+
if act, exp := isByteReader(s.iface()), test.exp; act != exp {
33+
t.Fatalf("unexpected io.ByteReader: %t; want %t", act, exp)
34+
}
35+
})
36+
}
37+
}

0 commit comments

Comments
 (0)