1
0

provide default pool

This commit is contained in:
Arpad Ryszka 2026-02-22 18:45:57 +01:00
parent 83801867f4
commit 40e4c067d0
6 changed files with 253 additions and 101 deletions

View File

@ -1,40 +1,15 @@
package buffer package buffer_test
import ( import (
"testing" "code.squareroundforest.org/arpio/buffer"
"io"
"errors" "errors"
"io"
"testing"
) )
type testPool struct {
size int
failAfter []int
count int
}
var (
errTest = errors.New("test error")
errTest2 = errors.New("test error 2")
)
func (p *testPool) Get() ([]byte, error) {
defer func() {
p.count++
}()
if len(p.failAfter) > 0 && p.count == p.failAfter[0] {
p.failAfter = p.failAfter[1:]
return nil, errTest
}
return make([]byte, p.size), nil
}
func (p *testPool) Put([]byte) {}
func TestContent(t *testing.T) { func TestContent(t *testing.T) {
t.Run("eof", func(t *testing.T) { t.Run("eof", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
var n int64 var n int64
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
ni, err := w.Write([]byte("123456789")[i*3 : i*3+3]) ni, err := w.Write([]byte("123456789")[i*3 : i*3+3])
@ -47,9 +22,9 @@ func TestContent(t *testing.T) {
return n, nil return n, nil
}) })
p := &testPool{size: 2} p := &pool{allocSize: 2}
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b := make([]byte, 3) b := make([]byte, 3)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
n, err := r.Read(b) n, err := r.Read(b)
@ -69,13 +44,13 @@ func TestContent(t *testing.T) {
}) })
t.Run("eof right away", func(t *testing.T) { t.Run("eof right away", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
return 0, nil return 0, nil
}) })
p := &testPool{size: 2} p := &pool{allocSize: 2}
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b := make([]byte, 3) b := make([]byte, 3)
n, err := r.Read(b) n, err := r.Read(b)
if n != 0 || !errors.Is(err, io.EOF) { if n != 0 || !errors.Is(err, io.EOF) {
@ -84,7 +59,7 @@ func TestContent(t *testing.T) {
}) })
t.Run("writer error", func(t *testing.T) { t.Run("writer error", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
var n int64 var n int64
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
ni, err := w.Write([]byte("123456789")[i*3 : i*3+3]) ni, err := w.Write([]byte("123456789")[i*3 : i*3+3])
@ -97,9 +72,9 @@ func TestContent(t *testing.T) {
return n, errTest return n, errTest
}) })
p := &testPool{size: 2} p := &pool{allocSize: 2}
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b := make([]byte, 3) b := make([]byte, 3)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
n, err := r.Read(b) n, err := r.Read(b)
@ -119,13 +94,13 @@ func TestContent(t *testing.T) {
}) })
t.Run("writer error right away", func(t *testing.T) { t.Run("writer error right away", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
return 0, errTest return 0, errTest
}) })
p := &testPool{size: 2} p := &pool{allocSize: 2}
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b := make([]byte, 3) b := make([]byte, 3)
n, err := r.Read(b) n, err := r.Read(b)
if n != 0 || !errors.Is(err, errTest) { if n != 0 || !errors.Is(err, errTest) {
@ -134,7 +109,7 @@ func TestContent(t *testing.T) {
}) })
t.Run("abort", func(t *testing.T) { t.Run("abort", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
var n int64 var n int64
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
ni, err := w.Write([]byte("123456789")[i*3 : i*3+3]) ni, err := w.Write([]byte("123456789")[i*3 : i*3+3])
@ -147,13 +122,13 @@ func TestContent(t *testing.T) {
return n, nil return n, nil
}) })
p := &testPool{ p := &pool{
size: 2, allocSize: 2,
failAfter: []int{1}, errAfter: []int{1},
} }
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b, ok, err := r.ReadBytes([]byte("67"), 12) b, ok, err := r.ReadBytes([]byte("67"), 12)
if string(b) != "12" /* segment size og 2 by the pool */ || ok || err != nil { if string(b) != "12" /* segment size og 2 by the pool */ || ok || err != nil {
t.Fatal(string(b), ok, err) t.Fatal(string(b), ok, err)
@ -166,7 +141,7 @@ func TestContent(t *testing.T) {
}) })
t.Run("abort right away", func(t *testing.T) { t.Run("abort right away", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
var n int64 var n int64
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
ni, err := w.Write([]byte("123456789")[i*3 : i*3+3]) ni, err := w.Write([]byte("123456789")[i*3 : i*3+3])
@ -179,13 +154,13 @@ func TestContent(t *testing.T) {
return n, nil return n, nil
}) })
p := &testPool{ p := &pool{
size: 2, allocSize: 2,
failAfter: []int{0}, errAfter: []int{0},
} }
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b, ok, err := r.ReadBytes([]byte("67"), 12) b, ok, err := r.ReadBytes([]byte("67"), 12)
if len(b) != 0 || ok || !errors.Is(err, errTest) { if len(b) != 0 || ok || !errors.Is(err, errTest) {
t.Fatal(string(b), ok, err) t.Fatal(string(b), ok, err)
@ -193,20 +168,20 @@ func TestContent(t *testing.T) {
}) })
t.Run("close when implementation ignores writer errors", func(t *testing.T) { t.Run("close when implementation ignores writer errors", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
w.Write([]byte("123")) w.Write([]byte("123"))
w.Write([]byte("456")) w.Write([]byte("456"))
w.Write([]byte("123")) w.Write([]byte("123"))
return 0, nil return 0, nil
}) })
p := &testPool{ p := &pool{
size: 2, allocSize: 2,
failAfter: []int{1}, errAfter: []int{1},
} }
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b, ok, err := r.ReadBytes([]byte("67"), 12) b, ok, err := r.ReadBytes([]byte("67"), 12)
if string(b) != "12" /* segment size og 2 by the pool */ || ok || err != nil { if string(b) != "12" /* segment size og 2 by the pool */ || ok || err != nil {
t.Fatal(string(b), ok, err) t.Fatal(string(b), ok, err)
@ -219,7 +194,7 @@ func TestContent(t *testing.T) {
}) })
t.Run("zero write", func(t *testing.T) { t.Run("zero write", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
w.Write([]byte("123")) w.Write([]byte("123"))
w.Write(nil) w.Write(nil)
w.Write([]byte("456")) w.Write([]byte("456"))
@ -227,9 +202,9 @@ func TestContent(t *testing.T) {
return 0, nil return 0, nil
}) })
p := &testPool{size: 2} p := &pool{allocSize: 2}
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b := make([]byte, 3) b := make([]byte, 3)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
n, err := r.Read(b) n, err := r.Read(b)
@ -249,7 +224,7 @@ func TestContent(t *testing.T) {
}) })
t.Run("zero write right away", func(t *testing.T) { t.Run("zero write right away", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
w.Write(nil) w.Write(nil)
w.Write([]byte("123")) w.Write([]byte("123"))
w.Write([]byte("456")) w.Write([]byte("456"))
@ -257,9 +232,9 @@ func TestContent(t *testing.T) {
return 0, nil return 0, nil
}) })
p := &testPool{size: 2} p := &pool{allocSize: 2}
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b := make([]byte, 3) b := make([]byte, 3)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
n, err := r.Read(b) n, err := r.Read(b)
@ -279,7 +254,7 @@ func TestContent(t *testing.T) {
}) })
t.Run("custom error", func(t *testing.T) { t.Run("custom error", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
var n int64 var n int64
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
ni, err := w.Write([]byte("123456789")[i*3 : i*3+3]) ni, err := w.Write([]byte("123456789")[i*3 : i*3+3])
@ -292,9 +267,9 @@ func TestContent(t *testing.T) {
return n, errTest return n, errTest
}) })
p := &testPool{size: 3} p := &pool{allocSize: 3}
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b := make([]byte, 3) b := make([]byte, 3)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
n, err := r.Read(b) n, err := r.Read(b)
@ -314,20 +289,20 @@ func TestContent(t *testing.T) {
}) })
t.Run("custom error with pool error", func(t *testing.T) { t.Run("custom error with pool error", func(t *testing.T) {
c := ContentFunc(func(w io.Writer) (int64, error) { c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
w.Write([]byte("123")) w.Write([]byte("123"))
w.Write([]byte("456")) w.Write([]byte("456"))
w.Write([]byte("123")) w.Write([]byte("123"))
return 0, errTest2 return 0, errTest2
}) })
p := &testPool{ p := &pool{
size: 2, allocSize: 2,
failAfter: []int{1}, errAfter: []int{1},
} }
o := Options{Pool: p} o := buffer.Options{Pool: p}
r := BufferedContent(c, o) r := buffer.BufferedContent(c, o)
b, ok, err := r.ReadBytes([]byte("67"), 12) b, ok, err := r.ReadBytes([]byte("67"), 12)
if string(b) != "12" /* segment size og 2 by the pool */ || ok || err != nil { if string(b) != "12" /* segment size og 2 by the pool */ || ok || err != nil {
t.Fatal(string(b), ok, err) t.Fatal(string(b), ok, err)

View File

@ -30,6 +30,7 @@ var (
utf8Range = []byte("aábéícóöődúüeű") utf8Range = []byte("aábéícóöődúüeű")
utf8W2Range = []byte("áéíóöőúüű") utf8W2Range = []byte("áéíóöőúüű")
errTest = errors.New("test error") errTest = errors.New("test error")
errTest2 = errors.New("test error 2")
) )
func (g *gen) Read(p []byte) (int, error) { func (g *gen) Read(p []byte) (int, error) {

8
lib.go
View File

@ -33,8 +33,8 @@ var (
ErrContentAbort = errors.New("content pipe aborted") ErrContentAbort = errors.New("content pipe aborted")
) )
func DefaultPool() Pool { func DefaultPool(allocSize int) Pool {
return newPool() return newPool(allocSize)
} }
func NoPool(allocSize int) Pool { func NoPool(allocSize int) Pool {
@ -52,7 +52,7 @@ func BufferedReader(in io.Reader, o Options) Reader {
} }
if o.Pool == nil { if o.Pool == nil {
o.Pool = DefaultPool() o.Pool = DefaultPool(1 << 12)
} }
return Reader{reader: &reader{options: o, in: in}} return Reader{reader: &reader{options: o, in: in}}
@ -70,7 +70,7 @@ func BufferedContent(c io.WriterTo, o Options) Reader {
} }
if o.Pool == nil { if o.Pool == nil {
o.Pool = DefaultPool() o.Pool = DefaultPool(1 << 12)
} }
return Reader{reader: &reader{options: o, in: mkcontent(c)}} return Reader{reader: &reader{options: o, in: mkcontent(c)}}

152
lib_test.go Normal file
View File

@ -0,0 +1,152 @@
package buffer_test
import (
"bytes"
"code.squareroundforest.org/arpio/buffer"
"errors"
"io"
"testing"
)
func TestLib(t *testing.T) {
t.Run("default pool", func(t *testing.T) {
t.Run("buffered reader", func(t *testing.T) {
g := &gen{max: 1 << 18}
r := buffer.BufferedReader(g, buffer.Options{})
b, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(1<<18)) {
t.Fatal("output does not match", len(b))
}
})
t.Run("buffered content", func(t *testing.T) {
c := buffer.ContentFunc(func(w io.Writer) (int64, error) {
g := &gen{max: 1 << 18}
return io.Copy(w, g)
})
r := buffer.BufferedContent(c, buffer.Options{})
b, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(1<<18)) {
t.Fatal("output does not match", len(b))
}
})
})
t.Run("zero reader", func(t *testing.T) {
t.Run("buffered reader", func(t *testing.T) {
r := buffer.BufferedReader(nil, buffer.Options{})
b, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if len(b) != 0 {
t.Fatal("output does not match", len(b))
}
})
t.Run("buffered content", func(t *testing.T) {
r := buffer.BufferedContent(nil, buffer.Options{})
b, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if len(b) != 0 {
t.Fatal("output does not match", len(b))
}
})
})
t.Run("uninitialized reader", func(t *testing.T) {
t.Run("read", func(t *testing.T) {
var r buffer.Reader
p := make([]byte, 512)
n, err := r.Read(p)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if n != 0 {
t.Fatal(n)
}
})
t.Run("read bytes", func(t *testing.T) {
var r buffer.Reader
b, ok, err := r.ReadBytes([]byte("123"), 512)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if ok {
t.Fatal(ok)
}
if len(b) != 0 {
t.Fatal(len(b))
}
})
t.Run("read utf8", func(t *testing.T) {
var r buffer.Reader
runes, n, err := r.ReadUTF8(512)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if n != 0 {
t.Fatal(n)
}
if len(runes) != 0 {
t.Fatal(len(runes))
}
})
t.Run("peek", func(t *testing.T) {
var r buffer.Reader
b, err := r.Peek(512)
if !errors.Is(err, io.EOF) {
t.Fatal(err)
}
if len(b) != 0 {
t.Fatal(len(b))
}
})
t.Run("buffered", func(t *testing.T) {
var r buffer.Reader
b := r.Buffered()
if len(b) != 0 {
t.Fatal(len(b))
}
})
t.Run("write to", func(t *testing.T) {
var (
r buffer.Reader
b bytes.Buffer
)
n, err := r.WriteTo(&b)
if err != nil {
t.Fatal(err)
}
if n != 0 {
t.Fatal(n)
}
})
})
}

19
pool.go
View File

@ -1,13 +1,23 @@
package buffer package buffer
import "sync"
type noPool struct { type noPool struct {
allocSize int allocSize int
} }
type pool struct{} type pool struct {
sp *sync.Pool
}
func newPool() *pool { func newPool(allocSize int) *pool {
return &pool{} sp := &sync.Pool{
New: func() any {
return make([]byte, allocSize)
},
}
return &pool{sp: sp}
} }
func (p noPool) Get() ([]byte, error) { func (p noPool) Get() ([]byte, error) {
@ -18,8 +28,9 @@ func (noPool) Put([]byte) {
} }
func (p *pool) Get() ([]byte, error) { func (p *pool) Get() ([]byte, error) {
return nil, nil return p.sp.Get().([]byte), nil
} }
func (p *pool) Put(b []byte) { func (p *pool) Put(b []byte) {
p.sp.Put(b)
} }

View File

@ -836,3 +836,16 @@ func TestPoolUsage(t *testing.T) {
}) })
} }
} }
func TestDefaultPool(t *testing.T) {
g := &gen{max: 1 << 18}
r := buffer.BufferedReader(g, buffer.Options{})
b, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, generate(1<<18)) {
t.Fatal("output does not match", len(b))
}
}