package pool_test import ( "code.squareroundforest.org/arpio/pool" "code.squareroundforest.org/arpio/times" "testing" "time" ) func TestMaxTO(t *testing.T) { maxTOBase := scenarioOptions{exclude: []string{"steady_minimal"}} t.Run("noshrink", func(t *testing.T) { base := maxTOBase base.algo = pool.NoShrink() t.Run("basic", func(t *testing.T) { t.Run("no concurrency", func(t *testing.T) { o := base testBasicSet(t, o) }) t.Run("low concurrency", func(t *testing.T) { o := base o.concurrency = 8 testBasicSet(t, o) }) t.Run("high concurrency", func(t *testing.T) { o := base o.concurrency = 256 testBasicSet(t, o) }) }) t.Run("cyclic", func(t *testing.T) { t.Run("no concurrency", func(t *testing.T) { o := base testCyclicSet(t, o) }) t.Run("low concurrency", func(t *testing.T) { o := base o.concurrency = 8 testCyclicSet(t, o) }) t.Run("high concurrency", func(t *testing.T) { o := base o.concurrency = 256 testCyclicSet(t, o) }) }) }) t.Run("max", func(t *testing.T) { base := maxTOBase base.algo = pool.Max(60) t.Run("basic", func(t *testing.T) { t.Run("no concurrency", func(t *testing.T) { o := base testBasicSet(t, o) }) t.Run("low concurrency", func(t *testing.T) { o := base o.concurrency = 8 testBasicSet(t, o) }) t.Run("high concurrency", func(t *testing.T) { o := base o.concurrency = 256 o.exclude = append( o.exclude, "steady_step_up_small", "steady_step_up_large", "slow_rise_from_zero_small", "slow_rise_from_zero_large", ) testBasicSet(t, o) }) }) t.Run("cyclic", func(t *testing.T) { t.Run("no concurrency", func(t *testing.T) { o := base testCyclicSet(t, o) }) t.Run("low concurrency", func(t *testing.T) { o := base o.concurrency = 8 testCyclicSet(t, o) }) t.Run("high concurrency", func(t *testing.T) { o := base o.concurrency = 256 testCyclicSet(t, o) }) }) }) t.Run("to", func(t *testing.T) { base := maxTOBase base.algo = pool.Timeout(300 * time.Millisecond) base.minDelay = 10 * time.Millisecond base.maxDelay = 100 * time.Millisecond t.Run("basic", func(t *testing.T) { t.Run("no concurrency", func(t *testing.T) { o := base o.exclude = append( o.exclude, "steady_step_up_large", "slow_rise_from_zero_small", "slow_rise_from_zero_large", ) testBasicSet(t, o) }) t.Run("low concurrency", func(t *testing.T) { o := base o.concurrency = 8 testBasicSet(t, o) }) t.Run("high concurrency", func(t *testing.T) { o := base o.concurrency = 256 testBasicSet(t, o) }) }) t.Run("cyclic", func(t *testing.T) { t.Run("no concurrency", func(t *testing.T) { o := base o.exclude = append(o.exclude, "sinus_large") testCyclicSet(t, o) }) t.Run("low concurrency", func(t *testing.T) { o := base o.concurrency = 8 testCyclicSet(t, o) }) t.Run("high concurrency", func(t *testing.T) { o := base o.concurrency = 256 testCyclicSet(t, o) }) }) }) t.Run("maxto", func(t *testing.T) { base := maxTOBase base.algo = pool.MaxTimeout(60, 300*time.Millisecond) base.minDelay = 10 * time.Millisecond base.maxDelay = 100 * time.Millisecond t.Run("basic", func(t *testing.T) { t.Run("no concurrency", func(t *testing.T) { o := base o.exclude = append( o.exclude, "steady_step_up_large", "slow_rise_from_zero_small", "slow_rise_from_zero_large", ) testBasicSet(t, o) }) t.Run("low concurrency", func(t *testing.T) { o := base o.concurrency = 8 testBasicSet(t, o) }) t.Run("high concurrency", func(t *testing.T) { o := base o.concurrency = 256 o.exclude = append( o.exclude, "steady_step_up_large", "steady_step_up_small", "slow_rise_from_zero_large", ) testBasicSet(t, o) }) }) t.Run("cyclic", func(t *testing.T) { t.Run("no concurrency", func(t *testing.T) { o := base o.exclude = append(o.exclude, "sinus_large") testCyclicSet(t, o) }) t.Run("low concurrency", func(t *testing.T) { o := base o.concurrency = 8 testCyclicSet(t, o) }) t.Run("high concurrency", func(t *testing.T) { o := base o.concurrency = 256 testCyclicSet(t, o) }) }) }) t.Run("external put", func(t *testing.T) { t.Run("initial", func(t *testing.T) { const ( initialCount = 15 steadyUseCycles = 8 stepDuration = 30 * time.Millisecond ) clock := times.Test() o := pool.Options{ Clock: clock, Algo: pool.MaxTimeout(15, 300*time.Millisecond), } 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 < initialCount; i++ { p.Put(make([]byte, 1<<9)) } for i := 0; i < steadyUseCycles; i++ { get() clock.Pass(stepDuration) put() clock.Pass(stepDuration) } s := p.Stats() e := pool.Stats{Idle: 1, Active: 0, Get: 8, Put: 23, Alloc: 0, Free: 14} if s != e { t.Fatal(s) } }) t.Run("expect higher load", func(t *testing.T) { const ( initialCount = 15 steadyUseCycles = 8 adjustCount = 15 highLoadCycles = 8 stepDuration = 30 * time.Millisecond ) clock := times.Test() o := pool.Options{ Clock: clock, Algo: pool.MaxTimeout(15, 300*time.Millisecond), } 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 < initialCount; i++ { get() } for i := 0; i < steadyUseCycles; i++ { put() clock.Pass(stepDuration) get() clock.Pass(stepDuration) } for i := 0; i < adjustCount; i++ { p.Put(make([]byte, 1<<9)) } for i := 0; i < highLoadCycles; i++ { get() clock.Pass(stepDuration) get() clock.Pass(stepDuration) put() clock.Pass(stepDuration) } s := p.Stats() e := pool.Stats{Idle: 1, Active: 8, Get: 39, Put: 31, Alloc: 19, Free: 10} 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 stepDuration = 30 * time.Millisecond ) clock := times.Test() o := pool.Options{ Clock: clock, Algo: pool.MaxTimeout(15, 300*time.Millisecond), } 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) } 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() clock.Pass(stepDuration) put() clock.Pass(stepDuration) } s := p.Stats() e := pool.Stats{Idle: 1, Active: 0, Get: 8, Put: 8, Alloc: 0, Load: initialCount, Free: 14} if s != e { t.Fatal(s) } }) t.Run("expect higher load", func(t *testing.T) { const ( initialCount = 15 steadyUseCycles = 8 adjustCount = 15 highLoadCycles = 8 stepDuration = 30 * time.Millisecond ) clock := times.Test() o := pool.Options{ Clock: clock, Algo: pool.MaxTimeout(15, 300*time.Millisecond), } 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 < initialCount; i++ { get() } for i := 0; i < steadyUseCycles; i++ { get() clock.Pass(stepDuration) put() clock.Pass(stepDuration) } 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() clock.Pass(stepDuration) get() clock.Pass(stepDuration) put() clock.Pass(stepDuration) } s := p.Stats() e := pool.Stats{Idle: 1, Active: 23, Get: 39, Put: 16, Alloc: 20, Load: adjustCount, Free: 11} if s != e { t.Fatal(s) } }) }) }