From 513f9a3585bd910ec1375ac6ff761be43f396175 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Mon, 6 Apr 2026 20:41:37 +0200 Subject: [PATCH] fix reader for blocking reads --- pool_test.go | 1276 +++++++++++++++++++++++----------------------- reader.go | 98 ++-- readutf8_test.go | 27 +- 3 files changed, 693 insertions(+), 708 deletions(-) diff --git a/pool_test.go b/pool_test.go index 99143e7..4c6f682 100644 --- a/pool_test.go +++ b/pool_test.go @@ -128,200 +128,62 @@ func (p *syncedForeverPool[T]) Put(i T) { } func TestPoolUsage(t *testing.T) { - for _, cr := range []createReader{buffer.BufferedReader, testContent} { - t.Run("allocate", func(t *testing.T) { - t.Run("read", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - b := make([]byte, 256) - n, err := r.Read(b) - if err != nil { - t.Fatal(err) - } - - if n != len(b) { - t.Fatal("invalid read length") - } - - if !bytes.Equal(b, generate(len(b))) { - t.Fatal("invalid content") - } - - if p.alloc != 1 { - t.Fatal("invalid allocation count") - } - }) - - t.Run("read bytes", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 9} - o := buffer.Options{Pool: p} - r := cr(g, o) - b, ok, err := r.ReadBytes([]byte("123"), 1<<12) - if err != nil { - t.Fatal(err) - } - - if ok { - t.Fatal("unexpected delimiter") - } - - if len(b) != 0 { - t.Fatal("invalid content") - } - - if p.alloc != 8 { - t.Fatal("invalid allocation count") - } - }) - - t.Run("read utf8", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - runes, n, err := r.ReadUTF8(1 << 12) - if err != nil { - t.Fatal(err) - } - - if n != 1<<12 { - t.Fatal("unexpected delimiter") - } - - if len(runes) != 1<<12 { - t.Fatal("invalid content") - } - - if p.alloc != 3 { - t.Fatal("invalid allocation count") - } - }) - - t.Run("peek", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - b, err := r.Peek(3 * 1 << 12) - if err != nil { - t.Fatal(err) - } - - if len(b) != 3*1<<12 { - t.Fatal("invalid content") - } - - if p.alloc != 3 { - t.Fatal("invalid allocation count") - } - }) - - t.Run("buffered", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - b := r.Buffered() - if len(b) != 0 { - t.Fatal("invalid content") - } - - if p.alloc != 0 { - t.Fatal("invalid allocation count") - } - }) - - t.Run("write to", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - - var b bytes.Buffer - n, err := r.WriteTo(&b) - if err != nil { - t.Fatal(err) - } - - if n != 1<<15 { - t.Fatal("invalid write length") - } - - if !bytes.Equal(b.Bytes(), generate(1<<15)) { - t.Fatal("invalid content") - } - - if p.alloc != 1 { - t.Fatal("invalid allocation count") - } - }) - }) - - t.Run("free", func(t *testing.T) { - t.Run("read", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - b := make([]byte, 1<<9) - for { - _, err := r.Read(b) - if errors.Is(err, io.EOF) { - break - } - + for title, cr := range map[string]createReader{"reader": buffer.BufferedReader, "content": testContent} { + t.Run(title, func(t *testing.T) { + t.Run("allocate", func(t *testing.T) { + t.Run("read", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + b := make([]byte, 256) + n, err := r.Read(b) if err != nil { t.Fatal(err) } - } - if p.alloc != 1 { - t.Fatal("invalid allocation count") - } - - if p.free != 1 { - t.Fatal("invalid free count") - } - }) - - t.Run("read bytes", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - _, _, err := r.ReadBytes([]byte("123"), 1<<15+3) - if err != nil { - t.Fatal(err) - } - - _, _, err = r.ReadBytes([]byte("123"), 1<<15+3) - if !errors.Is(err, io.EOF) { - t.Fatal(err) - } - - if p.alloc != 9 { - t.Fatal("invalid allocation count", p.alloc) - } - - if p.free != 9 { - t.Fatal("invalid free count", p.free) - } - }) - - t.Run("read utf8", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - for { - runes, n, err := r.ReadUTF8(1 << 12) - if errors.Is(err, io.EOF) { - break + if n != len(b) { + t.Fatal("invalid read length") } + if !bytes.Equal(b, generate(len(b))) { + t.Fatal("invalid content") + } + + if p.alloc != 1 { + t.Fatal("invalid allocation count") + } + }) + + t.Run("read bytes", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 9} + o := buffer.Options{Pool: p} + r := cr(g, o) + b, ok, err := r.ReadBytes([]byte("123"), 1<<12) + if err != nil { + t.Fatal(err) + } + + if ok { + t.Fatal("unexpected delimiter") + } + + if len(b) != 0 { + t.Fatal("invalid content") + } + + if p.alloc != 8 { + t.Fatal("invalid allocation count") + } + }) + + t.Run("read utf8", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + runes, n, err := r.ReadUTF8(1 << 12) if err != nil { t.Fatal(err) } @@ -333,391 +195,506 @@ func TestPoolUsage(t *testing.T) { if len(runes) != 1<<12 { t.Fatal("invalid content") } - } - if p.alloc != p.free { - t.Fatal("invalid allocation count", p.alloc, p.free) - } - }) - - t.Run("peek", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - b, err := r.Peek(3 * 1 << 12) - if err != nil { - t.Fatal(err) - } - - if len(b) != 3*1<<12 { - t.Fatal("invalid content") - } - - if p.alloc != 3 { - t.Fatal("invalid allocation count") - } - - if p.free != 0 { - t.Fatal("invalid allocation count") - } - - b = make([]byte, 1<<9) - for { - _, err := r.Read(b) - if errors.Is(err, io.EOF) { - break - } - - if err != nil { - t.Fatal(err) - } - } - - if p.alloc != 3 { - t.Fatal("invalid allocation count") - } - - if p.free != 3 { - t.Fatal("invalid allocation count") - } - }) - - t.Run("buffered", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - b := r.Buffered() - if len(b) != 0 { - t.Fatal("invalid content") - } - - if p.alloc != 0 || p.free != 0 { - t.Fatal("invalid allocation count") - } - }) - - t.Run("write to", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1 << 12} - o := buffer.Options{Pool: p} - r := cr(g, o) - - var b bytes.Buffer - n, err := r.WriteTo(&b) - if err != nil { - t.Fatal(err) - } - - if n != 1<<15 { - t.Fatal("invalid write length") - } - - if !bytes.Equal(b.Bytes(), generate(1<<15)) { - t.Fatal("invalid content") - } - - if p.alloc != 1 { - t.Fatal("invalid allocation count") - } - - if p.free != 1 { - t.Fatal("invalid free count") - } - }) - }) - - t.Run("null segment", func(t *testing.T) { - t.Run("read", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{ - allocSize: 1 << 12, - zeroAfter: []int{0}, - } - - o := buffer.Options{Pool: p} - r := cr(g, o) - b := make([]byte, 256) - _, err := r.Read(b) - if !errors.Is(err, buffer.ErrZeroAllocation) { - t.Fatal("failed to fail", err) - } - }) - - t.Run("read bytes", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{ - allocSize: 1 << 9, - zeroAfter: []int{1}, - } - - o := buffer.Options{Pool: p} - r := cr(g, o) - _, _, err := r.ReadBytes([]byte("123"), 1<<12) - if err != nil { - t.Fatal(err) - } - - _, _, err = r.ReadBytes([]byte("123"), 1<<12) - if !errors.Is(err, buffer.ErrZeroAllocation) { - t.Fatal("failed to fail", err) - } - }) - - t.Run("read utf8", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{ - allocSize: 1 << 12, - zeroAfter: []int{0}, - } - - o := buffer.Options{Pool: p} - r := cr(g, o) - _, _, err := r.ReadUTF8(1 << 12) - if !errors.Is(err, buffer.ErrZeroAllocation) { - t.Fatal("failed to fail", err) - } - }) - - t.Run("peek", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{ - allocSize: 1 << 12, - zeroAfter: []int{0}, - } - - o := buffer.Options{Pool: p} - r := cr(g, o) - _, err := r.Peek(3 * 1 << 12) - if !errors.Is(err, buffer.ErrZeroAllocation) { - t.Fatal("failed to fail", err) - } - }) - - t.Run("write to", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{ - allocSize: 1 << 12, - zeroAfter: []int{0}, - } - - o := buffer.Options{Pool: p} - r := cr(g, o) - - var b bytes.Buffer - _, err := r.WriteTo(&b) - if !errors.Is(err, buffer.ErrZeroAllocation) { - t.Fatal("failed to fail", err) - } - }) - }) - - t.Run("varying segment sizes", func(t *testing.T) { - t.Run("read bytes", func(t *testing.T) { - t.Run("find", func(t *testing.T) { - g := &gen{ - max: 1 << 15, - customContentAfter: []int{1 << 11}, - customContent: map[int][]byte{1 << 11: []byte("123")}, - } - - p := &fakePool{varyingSize: []int{8, 256}} - o := buffer.Options{Pool: p} - r := cr(g, o) - b, ok, err := r.ReadBytes([]byte("123"), 1<<15) - if err != nil { - t.Fatal(err) - } - - if !ok { - t.Fatal("failed to find delimiter") - } - - if !bytes.Equal(b, append(generate(1<<11), []byte("123")...)) { - t.Fatal("invalid content") + if p.alloc != 1 { + t.Fatal("invalid allocation count", p.alloc) } }) - t.Run("find not", func(t *testing.T) { + t.Run("peek", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + b, err := r.Peek(3 * 1 << 12) + if err != nil { + t.Fatal(err) + } + + if len(b) != 3*1<<12 { + t.Fatal("invalid content") + } + + if p.alloc != 3 { + t.Fatal("invalid allocation count") + } + }) + + t.Run("buffered", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + b := r.Buffered() + if len(b) != 0 { + t.Fatal("invalid content") + } + + if p.alloc != 0 { + t.Fatal("invalid allocation count") + } + }) + + t.Run("write to", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + + var b bytes.Buffer + n, err := r.WriteTo(&b) + if err != nil { + t.Fatal(err) + } + + if n != 1<<15 { + t.Fatal("invalid write length") + } + + if !bytes.Equal(b.Bytes(), generate(1<<15)) { + t.Fatal("invalid content") + } + + if p.alloc != 1 { + t.Fatal("invalid allocation count") + } + }) + }) + + t.Run("free", func(t *testing.T) { + t.Run("read", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + b := make([]byte, 1<<9) + for { + _, err := r.Read(b) + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + t.Fatal(err) + } + } + + if p.alloc != 1 { + t.Fatal("invalid allocation count") + } + + if p.free != 1 { + t.Fatal("invalid free count") + } + }) + + t.Run("read bytes", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + _, _, err := r.ReadBytes([]byte("123"), 1<<15+3) + if err != nil { + t.Fatal(err) + } + + _, _, err = r.ReadBytes([]byte("123"), 1<<15+3) + if !errors.Is(err, io.EOF) { + t.Fatal(err) + } + + if p.alloc != 9 { + t.Fatal("invalid allocation count", p.alloc) + } + + if p.free != 9 { + t.Fatal("invalid free count", p.free) + } + }) + + t.Run("read utf8", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + for { + runes, n, err := r.ReadUTF8(1 << 12) + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + t.Fatal(err) + } + + if n != 1<<12 { + t.Fatal("unexpected delimiter") + } + + if len(runes) != 1<<12 { + t.Fatal("invalid content") + } + } + + if p.alloc != p.free { + t.Fatal("invalid allocation count", p.alloc, p.free) + } + }) + + t.Run("peek", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + b, err := r.Peek(3 * 1 << 12) + if err != nil { + t.Fatal(err) + } + + if len(b) != 3*1<<12 { + t.Fatal("invalid content") + } + + if p.alloc != 3 { + t.Fatal("invalid allocation count") + } + + if p.free != 0 { + t.Fatal("invalid allocation count") + } + + b = make([]byte, 1<<9) + for { + _, err := r.Read(b) + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + t.Fatal(err) + } + } + + if p.alloc != 3 { + t.Fatal("invalid allocation count") + } + + if p.free != 3 { + t.Fatal("invalid allocation count") + } + }) + + t.Run("buffered", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + b := r.Buffered() + if len(b) != 0 { + t.Fatal("invalid content") + } + + if p.alloc != 0 || p.free != 0 { + t.Fatal("invalid allocation count") + } + }) + + t.Run("write to", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1 << 12} + o := buffer.Options{Pool: p} + r := cr(g, o) + + var b bytes.Buffer + n, err := r.WriteTo(&b) + if err != nil { + t.Fatal(err) + } + + if n != 1<<15 { + t.Fatal("invalid write length") + } + + if !bytes.Equal(b.Bytes(), generate(1<<15)) { + t.Fatal("invalid content") + } + + if p.alloc != 1 { + t.Fatal("invalid allocation count") + } + + if p.free != 1 { + t.Fatal("invalid free count") + } + }) + }) + + t.Run("null segment", func(t *testing.T) { + t.Run("read", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{ + allocSize: 1 << 12, + zeroAfter: []int{0}, + } + + o := buffer.Options{Pool: p} + r := cr(g, o) + b := make([]byte, 256) + _, err := r.Read(b) + if !errors.Is(err, buffer.ErrZeroAllocation) { + t.Fatal("failed to fail", err) + } + }) + + t.Run("read bytes", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{ + allocSize: 1 << 9, + zeroAfter: []int{1}, + } + + o := buffer.Options{Pool: p} + r := cr(g, o) + _, _, err := r.ReadBytes([]byte("123"), 1<<12) + if err != nil { + t.Fatal(err) + } + + _, _, err = r.ReadBytes([]byte("123"), 1<<12) + if !errors.Is(err, buffer.ErrZeroAllocation) { + t.Fatal("failed to fail", err) + } + }) + + t.Run("read utf8", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{ + allocSize: 1 << 12, + zeroAfter: []int{0}, + } + + o := buffer.Options{Pool: p} + r := cr(g, o) + _, _, err := r.ReadUTF8(1 << 12) + if !errors.Is(err, buffer.ErrZeroAllocation) { + t.Fatal("failed to fail", err) + } + }) + + t.Run("peek", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{ + allocSize: 1 << 12, + zeroAfter: []int{0}, + } + + o := buffer.Options{Pool: p} + r := cr(g, o) + _, err := r.Peek(3 * 1 << 12) + if !errors.Is(err, buffer.ErrZeroAllocation) { + t.Fatal("failed to fail", err) + } + }) + + t.Run("write to", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{ + allocSize: 1 << 12, + zeroAfter: []int{0}, + } + + o := buffer.Options{Pool: p} + r := cr(g, o) + + var b bytes.Buffer + _, err := r.WriteTo(&b) + if !errors.Is(err, buffer.ErrZeroAllocation) { + t.Fatal("failed to fail", err) + } + }) + }) + + t.Run("varying segment sizes", func(t *testing.T) { + t.Run("read bytes", func(t *testing.T) { + t.Run("find", func(t *testing.T) { + g := &gen{ + max: 1 << 15, + customContentAfter: []int{1 << 11}, + customContent: map[int][]byte{1 << 11: []byte("123")}, + } + + p := &fakePool{varyingSize: []int{8, 256}} + o := buffer.Options{Pool: p} + r := cr(g, o) + b, ok, err := r.ReadBytes([]byte("123"), 1<<15) + if err != nil { + t.Fatal(err) + } + + if !ok { + t.Fatal("failed to find delimiter") + } + + if !bytes.Equal(b, append(generate(1<<11), []byte("123")...)) { + t.Fatal("invalid content") + } + }) + + t.Run("find not", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{varyingSize: []int{8, 256}} + o := buffer.Options{Pool: p} + r := cr(g, o) + b, ok, err := r.ReadBytes([]byte("123"), 1<<15) + if err != nil { + t.Fatal(err) + } + + if ok { + t.Fatal("invalid delimiter", len(b)) + } + }) + }) + + t.Run("peek", func(t *testing.T) { g := &gen{max: 1 << 15} p := &fakePool{varyingSize: []int{8, 256}} o := buffer.Options{Pool: p} r := cr(g, o) - b, ok, err := r.ReadBytes([]byte("123"), 1<<15) + b, err := r.Peek(1 << 11) if err != nil { t.Fatal(err) } - if ok { - t.Fatal("invalid delimiter") + if !bytes.Equal(b, generate(1<<11)) { + t.Fatal("invalid content") + } + }) + }) + + t.Run("one byte segments", func(t *testing.T) { + t.Run("read", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1} + o := buffer.Options{Pool: p} + r := cr(g, o) + b, err := io.ReadAll(r) + if err != nil { + t.Fatal(err) } if !bytes.Equal(b, generate(1<<15)) { t.Fatal("invalid content") } }) - }) - t.Run("peek", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{varyingSize: []int{8, 256}} - o := buffer.Options{Pool: p} - r := cr(g, o) - b, err := r.Peek(1 << 11) - if err != nil { - t.Fatal(err) - } + t.Run("read bytes", func(t *testing.T) { + t.Run("find", func(t *testing.T) { + g := &gen{ + max: 1 << 15, + customContentAfter: []int{1 << 11}, + customContent: map[int][]byte{1 << 11: []byte("123")}, + } - if !bytes.Equal(b, generate(1<<11)) { - t.Fatal("invalid content") - } - }) - }) + p := &fakePool{allocSize: 1} + o := buffer.Options{Pool: p} + r := cr(g, o) + b, ok, err := r.ReadBytes([]byte("123"), 1<<15) + if err != nil { + t.Fatal(err) + } - t.Run("one byte segments", func(t *testing.T) { - t.Run("read", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1} - o := buffer.Options{Pool: p} - r := cr(g, o) - b, err := io.ReadAll(r) - if err != nil { - t.Fatal(err) - } + if !ok { + t.Fatal("failed to find delimiter") + } - if !bytes.Equal(b, generate(1<<15)) { - t.Fatal("invalid content") - } - }) + if !bytes.Equal(b, append(generate(1<<11), []byte("123")...)) { + t.Fatal("invalid content") + } + }) - t.Run("read bytes", func(t *testing.T) { - t.Run("find", func(t *testing.T) { + t.Run("find not", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1} + o := buffer.Options{Pool: p} + r := cr(g, o) + b, ok, err := r.ReadBytes([]byte("123"), 1<<15) + if err != nil { + t.Fatal(err) + } + + if ok { + t.Fatal("invalid delimiter found") + } + + if len(b) != 0 { + t.Fatal("invalid content", len(b)) + } + }) + }) + + t.Run("read utf8", func(t *testing.T) { g := &gen{ - max: 1 << 15, - customContentAfter: []int{1 << 11}, - customContent: map[int][]byte{1 << 11: []byte("123")}, + max: 1 << 15, + rng: utf8W2Range, } p := &fakePool{allocSize: 1} o := buffer.Options{Pool: p} r := cr(g, o) - b, ok, err := r.ReadBytes([]byte("123"), 1<<15) + runes, n, err := r.ReadUTF8(1 << 14) if err != nil { t.Fatal(err) } - if !ok { - t.Fatal("failed to find delimiter") + if n != 1<<15 { + t.Fatal("invalid read length") } - if !bytes.Equal(b, append(generate(1<<11), []byte("123")...)) { + if string(runes) != string(generateFrom(utf8W2Range, 1<<15)) { t.Fatal("invalid content") } }) - t.Run("find not", func(t *testing.T) { + t.Run("peek", func(t *testing.T) { g := &gen{max: 1 << 15} p := &fakePool{allocSize: 1} o := buffer.Options{Pool: p} r := cr(g, o) - b, ok, err := r.ReadBytes([]byte("123"), 1<<15) + b, err := r.Peek(1 << 14) if err != nil { t.Fatal(err) } - if ok { - t.Fatal("invalid delimiter found") + if !bytes.Equal(b, generate(1<<14)) { + t.Fatal("invalid content") + } + }) + + t.Run("write to", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{allocSize: 1} + o := buffer.Options{Pool: p} + r := cr(g, o) + + var b bytes.Buffer + n, err := r.WriteTo(&b) + if err != nil { + t.Fatal(err) } - if len(b) != 0 { - t.Fatal("invalid content", len(b)) + if n != 1<<15 { + t.Fatal("invalid write length") + } + + if !bytes.Equal(b.Bytes(), generate(1<<15)) { + t.Fatal("invalid content") } }) }) - t.Run("read utf8", func(t *testing.T) { - g := &gen{ - max: 1 << 15, - rng: utf8W2Range, - } - - p := &fakePool{allocSize: 1} - o := buffer.Options{Pool: p} - r := cr(g, o) - runes, n, err := r.ReadUTF8(1 << 14) - if err != nil { - t.Fatal(err) - } - - if n != 1<<15 { - t.Fatal("invalid read length") - } - - if string(runes) != string(generateFrom(utf8W2Range, 1<<15)) { - t.Fatal("invalid content") - } - }) - - t.Run("peek", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1} - o := buffer.Options{Pool: p} - r := cr(g, o) - b, err := r.Peek(1 << 14) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(b, generate(1<<14)) { - t.Fatal("invalid content") - } - }) - - t.Run("write to", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{allocSize: 1} - o := buffer.Options{Pool: p} - r := cr(g, o) - - var b bytes.Buffer - n, err := r.WriteTo(&b) - if err != nil { - t.Fatal(err) - } - - if n != 1<<15 { - t.Fatal("invalid write length") - } - - if !bytes.Equal(b.Bytes(), generate(1<<15)) { - t.Fatal("invalid content") - } - }) - }) - - t.Run("pool error on allocate", func(t *testing.T) { - t.Run("read", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{ - allocSize: 1 << 11, - errAfter: []int{0}, - } - - o := buffer.Options{Pool: p} - r := cr(g, o) - b, err := io.ReadAll(r) - if !errors.Is(err, errTest) { - t.Fatal("failed to fail") - } - - if len(b) != 0 { - t.Fatal("invalid content") - } - }) - - t.Run("read bytes", func(t *testing.T) { - t.Run("immediate", func(t *testing.T) { + t.Run("pool error on allocate", func(t *testing.T) { + t.Run("read", func(t *testing.T) { g := &gen{max: 1 << 15} p := &fakePool{ allocSize: 1 << 11, @@ -726,13 +703,9 @@ func TestPoolUsage(t *testing.T) { o := buffer.Options{Pool: p} r := cr(g, o) - b, ok, err := r.ReadBytes([]byte("123"), 1<<12) + b, err := io.ReadAll(r) if !errors.Is(err, errTest) { - t.Fatal("failed to fail with the right error", err) - } - - if ok { - t.Fatal("invalid delimiter found") + t.Fatal("failed to fail") } if len(b) != 0 { @@ -740,72 +713,157 @@ func TestPoolUsage(t *testing.T) { } }) - t.Run("on grow", func(t *testing.T) { - g := &gen{max: 1 << 15} + t.Run("read bytes", func(t *testing.T) { + t.Run("immediate", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{ + allocSize: 1 << 11, + errAfter: []int{0}, + } + + o := buffer.Options{Pool: p} + r := cr(g, o) + b, ok, err := r.ReadBytes([]byte("123"), 1<<12) + if !errors.Is(err, errTest) { + t.Fatal("failed to fail with the right error", err) + } + + if ok { + t.Fatal("invalid delimiter found") + } + + if len(b) != 0 { + t.Fatal("invalid content") + } + }) + + t.Run("on grow", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{ + allocSize: 1 << 11, + errAfter: []int{1}, + } + + o := buffer.Options{Pool: p} + r := cr(g, o) + b, ok, err := r.ReadBytes([]byte("123"), 1<<15) + if err != nil { + t.Fatal(err) + } + + if ok { + t.Fatal("invalid delimiter found") + } + + if !bytes.Equal(b, generate(1<<11)) { + t.Fatal("invalid content") + } + + b, ok, err = r.ReadBytes([]byte("123"), 1<<15) + if !errors.Is(err, errTest) { + t.Fatal("failed to fail with the right error", err) + } + + if ok { + t.Fatal("invalid delimiter found") + } + + if len(b) != 0 { + t.Fatal("invalid content") + } + }) + }) + + t.Run("read utf8", func(t *testing.T) { + g := &gen{ + max: 1 << 15, + rng: utf8W2Range, + } + p := &fakePool{ allocSize: 1 << 11, - errAfter: []int{1}, + errAfter: []int{0}, } o := buffer.Options{Pool: p} r := cr(g, o) - b, ok, err := r.ReadBytes([]byte("123"), 1<<15) - if err != nil { + runes, n, err := r.ReadUTF8(1 << 14) + if !errors.Is(err, errTest) { t.Fatal(err) } - if ok { - t.Fatal("invalid delimiter found") + if n != 0 { + t.Fatal("invalid read length") } - if !bytes.Equal(b, generate(1<<11)) { - t.Fatal("invalid content") - } - - b, ok, err = r.ReadBytes([]byte("123"), 1<<15) - if !errors.Is(err, errTest) { - t.Fatal("failed to fail with the right error", err) - } - - if ok { - t.Fatal("invalid delimiter found") - } - - if len(b) != 0 { + if len(runes) != 0 { t.Fatal("invalid content") } }) - }) - t.Run("read utf8", func(t *testing.T) { - g := &gen{ - max: 1 << 15, - rng: utf8W2Range, - } + t.Run("peek", func(t *testing.T) { + t.Run("immediate", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{ + allocSize: 1 << 11, + errAfter: []int{0}, + } - p := &fakePool{ - allocSize: 1 << 11, - errAfter: []int{0}, - } + o := buffer.Options{Pool: p} + r := cr(g, o) + b, err := r.Peek(1 << 12) + if !errors.Is(err, errTest) { + t.Fatal("failed to fail with the right error", err) + } - o := buffer.Options{Pool: p} - r := cr(g, o) - runes, n, err := r.ReadUTF8(1 << 14) - if !errors.Is(err, errTest) { - t.Fatal(err) - } + if len(b) != 0 { + t.Fatal("invalid content") + } + }) - if n != 0 { - t.Fatal("invalid read length") - } + t.Run("on grow", func(t *testing.T) { + g := &gen{max: 1 << 15} + p := &fakePool{ + allocSize: 1 << 11, + errAfter: []int{1}, + } - if len(runes) != 0 { - t.Fatal("invalid content") - } - }) + o := buffer.Options{Pool: p} + r := cr(g, o) + b, err := r.Peek(1 << 15) + if err != nil { + t.Fatal(err) + } - t.Run("peek", func(t *testing.T) { - t.Run("immediate", func(t *testing.T) { + if !bytes.Equal(b, generate(1<<11)) { + t.Fatal("invalid content") + } + + b, ok, err := r.ReadBytes([]byte("123"), 1<<11) + if err != nil { + t.Fatal(err) + } + + if ok { + t.Fatal("invalid delimiter found") + } + + if !bytes.Equal(b, generate(1<<11)) { + t.Fatal("invalid content") + } + + b, err = r.Peek(1 << 15) + if !errors.Is(err, errTest) { + t.Fatal("failed to fail with the right error") + } + + if len(b) != 0 { + t.Fatal("invalid content") + } + }) + }) + + t.Run("write to", func(t *testing.T) { g := &gen{max: 1 << 15} p := &fakePool{ allocSize: 1 << 11, @@ -814,82 +872,22 @@ func TestPoolUsage(t *testing.T) { o := buffer.Options{Pool: p} r := cr(g, o) - b, err := r.Peek(1 << 12) - if !errors.Is(err, errTest) { - t.Fatal("failed to fail with the right error", err) - } - if len(b) != 0 { - t.Fatal("invalid content") - } - }) - - t.Run("on grow", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{ - allocSize: 1 << 11, - errAfter: []int{1}, - } - - o := buffer.Options{Pool: p} - r := cr(g, o) - b, err := r.Peek(1 << 15) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(b, generate(1<<11)) { - t.Fatal("invalid content") - } - - b, ok, err := r.ReadBytes([]byte("123"), 1<<11) - if err != nil { - t.Fatal(err) - } - - if ok { - t.Fatal("invalid delimiter found") - } - - if !bytes.Equal(b, generate(1<<11)) { - t.Fatal("invalid content") - } - - b, err = r.Peek(1 << 15) + var b bytes.Buffer + n, err := r.WriteTo(&b) if !errors.Is(err, errTest) { t.Fatal("failed to fail with the right error") } - if len(b) != 0 { + if n != 0 { + t.Fatal("invalid write length") + } + + if b.Len() != 0 { t.Fatal("invalid content") } }) }) - - t.Run("write to", func(t *testing.T) { - g := &gen{max: 1 << 15} - p := &fakePool{ - allocSize: 1 << 11, - errAfter: []int{0}, - } - - o := buffer.Options{Pool: p} - r := cr(g, o) - - var b bytes.Buffer - n, err := r.WriteTo(&b) - if !errors.Is(err, errTest) { - t.Fatal("failed to fail with the right error") - } - - if n != 0 { - t.Fatal("invalid write length") - } - - if b.Len() != 0 { - t.Fatal("invalid content") - } - }) }) } } diff --git a/reader.go b/reader.go index 0f346d2..4f28d9f 100644 --- a/reader.go +++ b/reader.go @@ -16,31 +16,26 @@ type reader struct { err error } -func (r *reader) fillSegment() (fn int) { - for { - if r.err != nil { - return - } - - seg := r.segments[len(r.segments)-1] - start := r.offset + r.len - r.lastSegStart - if start == len(seg) { - return - } - - rn, err := r.in.Read(seg[start:len(seg)]) - if rn == 0 && err == nil { - rn, err = r.in.Read(seg[start:len(seg)]) - } - - if rn == 0 && err == nil { - return - } - - fn += rn - r.len += rn - r.err = err +func (r *reader) fillSegment() (fn int, full bool) { + if r.err != nil { + return } + + seg := r.segments[len(r.segments)-1] + start := r.offset + r.len - r.lastSegStart + if start == len(seg) { + full = true + return + } + + fn, r.err = r.in.Read(seg[start:len(seg)]) + if fn == 0 && r.err == nil { + fn, r.err = r.in.Read(seg[start:len(seg)]) + } + + r.len += fn + full = fn == len(seg)-start + return } func (r *reader) allocate() { @@ -62,26 +57,28 @@ func (r *reader) allocate() { r.segments = append(r.segments, segment) } -func (r *reader) fill(n int) { +func (r *reader) fill(to int) int { + var n int for { if r.err != nil { - return + return n } - if r.len >= n { - return + if r.len >= to { + return n } if len(r.segments) == 0 || r.offset+r.len == r.lastSegStart+len(r.segments[len(r.segments)-1]) { r.allocate() if r.err != nil { - return + return n } } - fn := r.fillSegment() - if fn == 0 { - return + fn, full := r.fillSegment() + n += fn + if fn == 0 || !full { + return n } } } @@ -309,8 +306,10 @@ func (r *reader) read(p []byte) (int, error) { } } + var partialRead bool if r.len == 0 { - r.fillSegment() + _, full := r.fillSegment() + partialRead = !full } ni := r.copy(p) @@ -321,6 +320,9 @@ func (r *reader) read(p []byte) (int, error) { r.consume(ni) p = p[ni:] n += ni + if partialRead { + break + } } if r.err != nil && r.len == 0 { @@ -390,30 +392,16 @@ func (r *reader) readUTF8(max int) ([]rune, int, error) { break } - var ( - b []byte - nullRead bool - ) - - for { - b = r.view(n, 4) - if len(b) == 4 || utf8.FullRune(b) { - break - } - - if r.err != nil { - break - } - + var nullRead bool + b := r.view(n, 4) + if len(b) < 4 && !utf8.FullRune(b) { clen := r.len - r.fill(4 + 2*(max-len(runes))) - nullRead = r.len == clen && r.err == nil - if nullRead { - break - } + r.fill(n + 4) + b = r.view(n, 4) + nullRead = r.len == clen } - if nullRead || len(b) == 0 { + if nullRead && r.err == nil || len(b) == 0 { break } @@ -490,7 +478,7 @@ func (r *reader) writeTo(w io.Writer) (int64, error) { } } - fn := r.fillSegment() + fn, _ := r.fillSegment() if fn == 0 && r.err == nil { return n, io.ErrNoProgress } diff --git a/readutf8_test.go b/readutf8_test.go index b040f51..2089240 100644 --- a/readutf8_test.go +++ b/readutf8_test.go @@ -12,8 +12,8 @@ func TestReadUTF8(t *testing.T) { t.Run(title, func(t *testing.T) { t.Run("read all after error", func(t *testing.T) { g := &gen{ - rng: utf8Range, - max: 24, + rng: utf8W2Range, + max: 30, fastErr: true, } @@ -25,27 +25,27 @@ func TestReadUTF8(t *testing.T) { } if len(runes) != 12 { - t.Fatal("short read", len(runes)) + t.Fatal("invalid read 0", len(runes), n0) } - if string(runes) != string(generateFrom(utf8Range, n0)) { + if string(runes) != string(generateFrom(utf8W2Range, n0)) { t.Fatal("invalid content") } - runes, n1, err := r.ReadUTF8(12) + runes1, n1, err := r.ReadUTF8(12) if err != nil { t.Fatal(err) } - if len(runes) != 3 { - t.Fatal("short read", len(runes)) + if len(runes1) != 3 { + t.Fatal("invalid read 1", len(runes1), n0, n1) } - if string(runes) != string(generateFrom(utf8Range, n0+n1)[n0:]) { - t.Fatal("invalid content", string(runes), string(generateFrom(utf8Range, n0+n1)[n0:])) + if string(runes1) != string(generateFrom(utf8W2Range, n0+n1)[n0:]) { + t.Fatal("invalid content", string(runes1), string(generateFrom(utf8W2Range, n0+n1)[n0:])) } - runes, n2, err := r.ReadUTF8(12) + runes2, n2, err := r.ReadUTF8(12) if !errors.Is(err, io.EOF) { t.Fatal(err) } @@ -54,8 +54,8 @@ func TestReadUTF8(t *testing.T) { t.Fatal("unexpected consumption") } - if len(runes) != 0 { - t.Fatal("unexpected read", len(runes)) + if len(runes2) != 0 { + t.Fatal("unexpected read", len(runes2)) } }) @@ -106,7 +106,6 @@ func TestReadUTF8(t *testing.T) { } o := buffer.Options{Pool: buffer.NoPool(9)} - r := cr(g, o) runes, n, err := r.ReadUTF8(12) if err != nil { @@ -149,7 +148,7 @@ func TestReadUTF8(t *testing.T) { for i := 0; i < 3; i++ { runes, n, err = r.ReadUTF8(24) if len(runes) != 0 || n != 1 || err != nil { - t.Fatal("failed to read out broken end") + t.Fatal("failed to read out broken end", len(runes), n, err == nil) } }