1
0
pool/adaptive_test.go

333 lines
6.1 KiB
Go

package pool_test
import (
"code.squareroundforest.org/arpio/pool"
"code.squareroundforest.org/arpio/syncbus"
"code.squareroundforest.org/arpio/times"
"testing"
"time"
)
func TestAdaptive(t *testing.T) {
t.Run("basic", func(t *testing.T) {
t.Run("no concurrency", func(t *testing.T) {
testBasicSet(t, scenarioOptions{})
})
t.Run("low concurrency", func(t *testing.T) {
testBasicSet(t, scenarioOptions{concurrency: 8})
})
t.Run("high concurrency", func(t *testing.T) {
testBasicSet(t, scenarioOptions{concurrency: 256})
})
})
t.Run("cyclic", func(t *testing.T) {
t.Run("no concurrency", func(t *testing.T) {
testCyclicSet(t, scenarioOptions{})
})
t.Run("low concurrency", func(t *testing.T) {
testCyclicSet(t, scenarioOptions{concurrency: 8})
})
t.Run("high concurrency", func(t *testing.T) {
testCyclicSet(t, scenarioOptions{concurrency: 256})
})
})
t.Run("nightshift", func(t *testing.T) {
const (
initial = 15
variation = 10
variationCycles = 3
variationStepTime = 10 * time.Millisecond
dropStepTime = time.Millisecond
waitCycles = 36
waitTime = time.Second
)
bus := syncbus.New(time.Second)
clock := times.Test()
o := pool.Options{
Clock: clock,
TestBus: bus,
}
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
p := pool.Make(alloc, nil, o)
var active [][]byte
get := func() {
b, err := p.Get()
if err != nil {
t.Fatal(err)
}
active = append(active, b)
}
put := func() {
if len(active) == 0 {
t.Fatal("put called from empty active")
}
var b []byte
b, active = active[0], active[1:]
p.Put(b)
}
for i := 0; i < initial; i++ {
get()
}
for i := 0; i <= variationCycles; i++ {
for j := 0; j < variation; j++ {
put()
clock.Pass(variationStepTime)
}
for j := 0; j < variation; j++ {
get()
clock.Pass(variationStepTime)
}
}
for len(active) > 0 {
put()
clock.Pass(dropStepTime)
}
for i := 0; i < waitCycles; i++ {
if err := bus.Wait("background-job-waiting"); err != nil {
t.Fatal(err)
}
bus.ResetSignals("background-job-waiting")
clock.Pass(waitTime)
if err := bus.Wait("free-idle-done"); err != nil {
t.Fatal(err)
}
bus.ResetSignals("free-idle-done")
}
if p.Stats().Idle != 0 {
t.Fatal(p.Stats())
}
})
t.Run("external put", func(t *testing.T) {
t.Run("initial", func(t *testing.T) {
const (
initialCount = 15
steadyUseCycles = 8
)
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
p := pool.Make(alloc, nil, pool.Options{})
var active [][]byte
get := func() {
b, err := p.Get()
if err != nil {
t.Fatal(err)
}
active = append(active, b)
}
put := func() {
if len(active) == 0 {
t.Fatal("put called from empty active")
}
var b []byte
b, active = active[0], active[1:]
p.Put(b)
}
for i := 0; i < initialCount; i++ {
p.Put(make([]byte, 1<<9))
}
for i := 0; i < steadyUseCycles; i++ {
get()
put()
}
s := p.Stats()
e := pool.Stats{Idle: 0, Active: 0, Get: 8, Put: 23, Alloc: 8, Free: 23}
if s != e {
t.Fatal(s)
}
})
t.Run("expect higher load", func(t *testing.T) {
const (
initialCount = 15
steadyUseCycles = 8
adjustCount = 15
highLoadCycles = 8
)
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
p := pool.Make(alloc, nil, pool.Options{})
var active [][]byte
get := func() {
b, err := p.Get()
if err != nil {
t.Fatal(err)
}
active = append(active, b)
}
put := func() {
if len(active) == 0 {
t.Fatal("put called from empty active")
}
var b []byte
b, active = active[0], active[1:]
p.Put(b)
}
for i := 0; i < initialCount; i++ {
get()
}
for i := 0; i < steadyUseCycles; i++ {
get()
put()
}
for i := 0; i < adjustCount; i++ {
p.Put(make([]byte, 1<<9))
}
for i := 0; i < highLoadCycles; i++ {
get()
get()
put()
}
s := p.Stats()
e := pool.Stats{Idle: 1, Active: 8, Get: 39, Put: 31, Alloc: 20, Free: 11}
if s != e {
t.Fatal(s)
}
})
})
t.Run("load", func(t *testing.T) {
t.Run("prewarm", func(t *testing.T) {
const (
initialCount = 15
steadyUseCycles = 8
)
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
p := pool.Make(alloc, nil, pool.Options{})
var active [][]byte
get := func() {
b, err := p.Get()
if err != nil {
t.Fatal(err)
}
active = append(active, b)
}
put := func() {
if len(active) == 0 {
t.Fatal("put called from empty active")
}
var b []byte
b, active = active[0], active[1:]
p.Put(b)
}
l := make([][]byte, initialCount)
for i := 0; i < initialCount; i++ {
l[i] = make([]byte, 1<<9)
}
p.Load(l)
for i := 0; i < steadyUseCycles; i++ {
get()
put()
}
s := p.Stats()
e := pool.Stats{Idle: 3, Active: 0, Get: 8, Put: 8, Alloc: 0, Load: initialCount, Free: 12}
if s != e {
t.Fatal(s)
}
})
t.Run("expect higher load", func(t *testing.T) {
const (
initialCount = 15
steadyUseCycles = 8
adjustCount = 15
highLoadCycles = 8
)
alloc := func() ([]byte, error) { return make([]byte, 1<<9), nil }
p := pool.Make(alloc, nil, pool.Options{})
var active [][]byte
get := func() {
b, err := p.Get()
if err != nil {
t.Fatal(err)
}
active = append(active, b)
}
put := func() {
if len(active) == 0 {
t.Fatal("put called from empty active")
}
var b []byte
b, active = active[0], active[1:]
p.Put(b)
}
for i := 0; i < initialCount; i++ {
get()
}
for i := 0; i < steadyUseCycles; i++ {
get()
put()
}
l := make([][]byte, adjustCount)
for i := 0; i < adjustCount; i++ {
l[i] = make([]byte, 1<<9)
}
p.Load(l)
for i := 0; i < highLoadCycles; i++ {
get()
get()
put()
}
s := p.Stats()
e := pool.Stats{Idle: 8, Active: 23, Get: 39, Put: 16, Alloc: 16, Load: adjustCount, Free: 0}
if s != e {
t.Fatal(s)
}
})
})
}