2026-03-03 19:50:09 +01:00
|
|
|
package pool_test
|
|
|
|
|
|
2026-03-05 12:34:35 +01:00
|
|
|
import (
|
|
|
|
|
"code.squareroundforest.org/arpio/pool"
|
|
|
|
|
"code.squareroundforest.org/arpio/syncbus"
|
|
|
|
|
"code.squareroundforest.org/arpio/times"
|
|
|
|
|
"errors"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var errTest = errors.New("test error")
|
2026-03-03 19:50:09 +01:00
|
|
|
|
|
|
|
|
func TestPool(t *testing.T) {
|
2026-03-05 12:34:35 +01:00
|
|
|
t.Run("initial stats", func(t *testing.T) {
|
|
|
|
|
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
|
|
|
|
|
p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("get when empty", func(t *testing.T) {
|
|
|
|
|
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
|
|
|
|
|
p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
b, err := p.Get()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(b) != 1<<9 {
|
|
|
|
|
t.Fatal(len(b))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Alloc: 1, Get: 1, Active: 1}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("get pooled own", func(t *testing.T) {
|
|
|
|
|
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
|
|
|
|
|
p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
b, err := p.Get()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(b) != 1<<9 {
|
|
|
|
|
t.Fatal(len(b))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.Put(b)
|
|
|
|
|
b, err = p.Get()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(b) != 1<<9 {
|
|
|
|
|
t.Fatal(len(b))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Alloc: 1, Get: 2, Put: 1, Active: 1}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("get pooled foreign", func(t *testing.T) {
|
|
|
|
|
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
|
|
|
|
|
p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
p.Put(make([]byte, 1<<6))
|
|
|
|
|
b, err := p.Get()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(b) != 1<<6 {
|
|
|
|
|
t.Fatal(len(b))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Get: 1, Put: 1, Active: 1}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("get own alloc not available", func(t *testing.T) {
|
|
|
|
|
p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
_, err := p.Get()
|
|
|
|
|
if !errors.Is(err, pool.ErrEmpty) {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Alloc: 1, Get: 1}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("get foreign no alloc", func(t *testing.T) {
|
|
|
|
|
p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
p.Put(make([]byte, 1<<6))
|
|
|
|
|
b, err := p.Get()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(b) != 1<<6 {
|
|
|
|
|
t.Fatal(len(b))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Get: 1, Put: 1, Active: 1}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("allocation error", func(t *testing.T) {
|
|
|
|
|
alloc := func() ([]byte, error) { return nil, errTest }
|
|
|
|
|
p := pool.Make(alloc, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
_, err := p.Get()
|
|
|
|
|
if !errors.Is(err, errTest) {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("put foreign not empty", func(t *testing.T) {
|
|
|
|
|
p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
p.Put(make([]byte, 1<<6))
|
|
|
|
|
p.Put(make([]byte, 1<<6))
|
|
|
|
|
b, err := p.Get()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(b) != 1<<6 {
|
|
|
|
|
t.Fatal(len(b))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Get: 1, Put: 2, Active: 1, Idle: 1}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-14 19:03:18 +01:00
|
|
|
t.Run("load items", func(t *testing.T) {
|
|
|
|
|
p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
l := make([][]byte, 9)
|
|
|
|
|
for i := 0; i < len(l); i++ {
|
|
|
|
|
l[i] = make([]byte, 1<<9)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p.Load(l)
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
if s.Idle != 9 {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-03-05 12:34:35 +01:00
|
|
|
t.Run("release on put no free", func(t *testing.T) {
|
|
|
|
|
p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.Max(2)})
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Put: 3, Idle: 2, Free: 1}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("release on put with free", func(t *testing.T) {
|
|
|
|
|
var freeCount int
|
|
|
|
|
f := func([]byte) { freeCount++ }
|
|
|
|
|
p := pool.Make(nil, f, pool.Options{Algo: pool.Max(2)})
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
if freeCount != 1 {
|
|
|
|
|
t.Fatal(freeCount)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Put: 3, Idle: 2, Free: 1}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("release all no free", func(t *testing.T) {
|
|
|
|
|
p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Free()
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Put: 3, Idle: 0, Free: 3}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("release all with free", func(t *testing.T) {
|
|
|
|
|
var freeCount int
|
|
|
|
|
f := func([]byte) { freeCount++ }
|
|
|
|
|
p := pool.Make[[]byte](nil, f, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
|
|
|
|
p.Free()
|
|
|
|
|
if freeCount != 3 {
|
|
|
|
|
t.Fatal(freeCount)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Put: 3, Idle: 0, Free: 3}
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("release all when empty", func(t *testing.T) {
|
|
|
|
|
p := pool.Make[[]byte](nil, nil, pool.Options{Algo: pool.NoShrink()})
|
|
|
|
|
p.Free()
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
|
|
|
|
|
var e pool.Stats
|
|
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("release on timeout no free", func(t *testing.T) {
|
|
|
|
|
c := times.Test()
|
|
|
|
|
b := syncbus.New(time.Second)
|
|
|
|
|
o := pool.Options{
|
|
|
|
|
Algo: pool.Timeout(3 * time.Millisecond),
|
|
|
|
|
Clock: c,
|
|
|
|
|
TestBus: b,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p := pool.Make[[]byte](nil, nil, o)
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
2026-03-14 19:03:18 +01:00
|
|
|
if err := b.Wait("background-job-waiting"); err != nil {
|
2026-03-05 12:34:35 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Pass(2 * time.Millisecond)
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
2026-03-14 19:03:18 +01:00
|
|
|
c.Pass(2 * time.Millisecond)
|
|
|
|
|
if err := b.Wait("free-idle-done"); err != nil {
|
2026-03-05 12:34:35 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Put: 2, Idle: 1, Free: 1}
|
2026-03-14 19:03:18 +01:00
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
2026-03-05 12:34:35 +01:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("release on timeout with free", func(t *testing.T) {
|
|
|
|
|
var freeCount int
|
|
|
|
|
f := func([]byte) { freeCount++ }
|
|
|
|
|
c := times.Test()
|
|
|
|
|
b := syncbus.New(time.Second)
|
|
|
|
|
o := pool.Options{
|
|
|
|
|
Algo: pool.Timeout(3 * time.Millisecond),
|
|
|
|
|
Clock: c,
|
|
|
|
|
TestBus: b,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p := pool.Make[[]byte](nil, f, o)
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
2026-03-14 19:03:18 +01:00
|
|
|
if err := b.Wait("background-job-waiting"); err != nil {
|
2026-03-05 12:34:35 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.Pass(2 * time.Millisecond)
|
|
|
|
|
p.Put(make([]byte, 1<<9))
|
2026-03-14 19:03:18 +01:00
|
|
|
c.Pass(2 * time.Millisecond)
|
|
|
|
|
if err := b.Wait("free-idle-done"); err != nil {
|
2026-03-05 12:34:35 +01:00
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
|
|
|
|
e := pool.Stats{Put: 2, Idle: 1, Free: 1}
|
2026-03-14 19:03:18 +01:00
|
|
|
if s != e || freeCount != 1 {
|
|
|
|
|
t.Fatal(s, freeCount)
|
2026-03-05 12:34:35 +01:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("use default algo", func(t *testing.T) {
|
|
|
|
|
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
|
|
|
|
|
p := pool.Make(alloc, nil, pool.Options{})
|
|
|
|
|
|
|
|
|
|
var bs [][]byte
|
|
|
|
|
for i := 0; i < 9; i++ {
|
|
|
|
|
b, err := p.Get()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bs = append(bs, b)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, b := range bs[:2*len(bs)/3] {
|
|
|
|
|
p.Put(b)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := p.Stats()
|
2026-03-14 19:03:18 +01:00
|
|
|
e := pool.Stats{Alloc: 9, Get: 9, Put: 6, Active: 3, Idle: 6, Free: 0}
|
2026-03-05 12:34:35 +01:00
|
|
|
if s != e {
|
|
|
|
|
t.Fatal(s)
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-03-03 19:50:09 +01:00
|
|
|
}
|