Skip to content
Open
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
13 changes: 10 additions & 3 deletions nocopy_linkbuffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,11 +670,18 @@ func (b *UnsafeLinkBuffer) Bytes() []byte {
func (b *UnsafeLinkBuffer) GetBytes(p [][]byte) (vs [][]byte) {
node, flush := b.read, b.flush
if len(p) == 0 {
// Size p to hold every readable (non-empty) node, flush included, so that
// the flush node is never dropped even when every intermediate node is
// non-empty and read has already advanced past the empty head node.
n := 0
for ; node != flush; node = node.next {
n++
for node := b.read; ; node = node.next {
if node.Len() > 0 {
n++
}
if node == flush {
break
}
}
node = b.read
p = make([][]byte, n)
}
var i int
Expand Down
58 changes: 58 additions & 0 deletions nocopy_linkbuffer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,64 @@ func TestLinkBufferGetBytes(t *testing.T) {
Equal(t, actualLen, expectedLen)
}

// getBytesFolds reconstructs the bytes exposed by GetBytes into a single slice.
func getBytesFolds(vs [][]byte) []byte {
var out []byte
for _, s := range vs {
out = append(out, s...)
}
return out
}

// TestLinkBufferGetBytesExposesAllAfterHeadDrain ensures that GetBytes(nil) exposes
// every readable byte even after read has advanced past the leading empty/unmanaged
// head node. Before the fix the auto-sized slice left no slot for the flush node, so
// GetBytes(nil) dropped the flush node's bytes (returning nothing when read == flush).
func TestLinkBufferGetBytesExposesAllAfterHeadDrain(t *testing.T) {
orig := LinkBufferCap
LinkBufferCap = 64
defer func() { LinkBufferCap = orig }()

buf := NewLinkBuffer()
var want []byte
for k := 0; k < 4; k++ {
d := []byte{byte('A' + k), byte('a' + k), byte('0' + k)}
n, err := buf.WriteBinary(d)
MustNil(t, err)
Equal(t, n, len(d))
MustNil(t, buf.Flush())
want = append(want, d...)
}

// read == flush case: consume one byte so read lands on the flush node.
_, err := buf.ReadByte()
MustNil(t, err)
want = want[1:]
if got := getBytesFolds(buf.GetBytes(nil)); !bytes.Equal(got, want) {
t.Fatalf("read==flush: GetBytes(nil)=%q want %q", got, want)
}

// read != flush case: smaller nodes force several nodes; read past the head.
LinkBufferCap = 16
buf = NewLinkBuffer()
want = want[:0]
for k := 0; k < 20; k++ {
d := []byte{byte('A' + k%26), byte('a' + k%26), byte('0' + k%10)}
n, err := buf.WriteBinary(d)
MustNil(t, err)
Equal(t, n, len(d))
MustNil(t, buf.Flush())
want = append(want, d...)
}
_, err = buf.ReadByte()
MustNil(t, err)
want = want[1:]
if got := getBytesFolds(buf.GetBytes(nil)); !bytes.Equal(got, want) {
t.Fatalf("read!=flush: GetBytes(nil)=%q want %q", got, want)
}
Equal(t, len(getBytesFolds(buf.GetBytes(nil))), buf.Len())
}

// TestLinkBufferWithZero test more case with n is invalid.
func TestLinkBufferWithInvalid(t *testing.T) {
// clean & new
Expand Down