Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions reader/reusable_read_closer.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ func NewReusableReadCloser(raw interface{}) (*ReusableReadCloser, error) {
switch body := raw.(type) {

case []byte:
// if a byte array , create buffer from bytes and use it
readBuf = *bytes.NewBuffer(body)
// copy: reset writes the buffer back, which must not reach the
// caller's slice or another reader built from it
readBuf = *bytes.NewBuffer(bytes.Clone(body))

case *[]byte:
// if *[]byte, create buffer from bytes and use it
readBuf = *bytes.NewBuffer(*body)
readBuf = *bytes.NewBuffer(bytes.Clone(*body))

case string:
// if a string , create buffer from string and use it
Expand Down
40 changes: 39 additions & 1 deletion reader/reusable_read_closer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestReusableReader(t *testing.T) {
}
})

// todo: readers shouldn't be used concurrently, so here we just try to catch pontential read concurring with resets panics
// todo: readers shouldn't be used concurrently, so here we just try to catch potential read concurring with resets panics
t.Run("concurrent-reset-with-read", func(t *testing.T) {
reusableReader, err := NewReusableReadCloser(v)
require.Nil(t, err)
Expand All @@ -58,3 +58,41 @@ func TestReusableReader(t *testing.T) {
})
}
}

// Readers must own their bytes. reset writes the buffer back at EOF, so a
// caller that reuses the slice, and a second reader built from it, must still
// see the original body.
func TestReusableReaderCopiesByteSlices(t *testing.T) {
for name, build := range map[string]func([]byte) (*ReusableReadCloser, error){
"[]byte": func(b []byte) (*ReusableReadCloser, error) { return NewReusableReadCloser(b) },
"*[]byte": func(b []byte) (*ReusableReadCloser, error) { return NewReusableReadCloser(&b) },
} {
t.Run(name, func(t *testing.T) {
body := []byte("original")
reader, err := build(body)
require.NoError(t, err)
copy(body, "modified")

got, err := io.ReadAll(reader)
require.NoError(t, err)
require.Equal(t, "original", string(got))
require.Equal(t, "modified", string(body))
})

t.Run(name+" two readers", func(t *testing.T) {
body := []byte("original")
first, err := build(body)
require.NoError(t, err)
second, err := build(body)
require.NoError(t, err)
copy(body, "modified")

for _, reader := range []*ReusableReadCloser{first, second, first} {
got, err := io.ReadAll(reader)
require.NoError(t, err)
require.Equal(t, "original", string(got))
}
require.Equal(t, "modified", string(body))
})
}
}
Loading