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) } }) }) }