From 4014e4825a5e9f561c2e7b610be4d2a623fbb791 Mon Sep 17 00:00:00 2001 From: Patrick Ellul Date: Mon, 3 Feb 2020 11:14:49 +1100 Subject: [PATCH 1/3] CreateResource constructs a new resource without acquiring it. --- pool.go | 26 ++++++++++++++++++++++++++ pool_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/pool.go b/pool.go index 54acf30..1a3fb30 100644 --- a/pool.go +++ b/pool.go @@ -373,6 +373,32 @@ func (p *Pool) AcquireAllIdle() []*Resource { return resources } +// CreateResource constructs a new resource without acquiring it. +// It goes straight in the IdlePool. It does not check against maxSize. +// It can be useful to maintain warm resources under little load. +func (p *Pool) CreateResource(ctx context.Context) error { + + value, err := p.constructResourceValue(ctx) + if err != nil { + return err + } + + res := &Resource{ + pool: p, + creationTime: time.Now(), + status: resourceStatusIdle, + value: value, + } + + p.cond.L.Lock() + p.allResources = append(p.allResources, res) + p.idleResources = append(p.idleResources, res) + p.destructWG.Add(1) + p.cond.L.Unlock() + + return nil +} + // releaseAcquiredResource returns res to the the pool. func (p *Pool) releaseAcquiredResource(res *Resource, lastUsedNano int64) { p.cond.L.Lock() diff --git a/pool_test.go b/pool_test.go index c822a67..1df1486 100644 --- a/pool_test.go +++ b/pool_test.go @@ -245,6 +245,44 @@ func TestPoolAcquireAllIdle(t *testing.T) { resources[3].Release() } +func TestPoolCreateResource(t *testing.T) { + constructor, counter := createConstructor() + pool := puddle.NewPool(constructor, stubDestructor, 10) + defer pool.Close() + + var err error + + err = pool.CreateResource(context.Background()) + require.NoError(t, err) + + allIdle := pool.AcquireAllIdle() + assert.Equal(t, counter.Value(), allIdle[0].Value()) + allIdle[0].ReleaseUnused() + + stats := pool.Stat() + assert.EqualValues(t, 1, stats.IdleResources()) + + res, err := pool.Acquire(context.Background()) + require.NoError(t, err) + assert.Equal(t, 1, res.Value()) + assert.WithinDuration(t, time.Now(), res.CreationTime(), time.Second) + res.Release() + + stats = pool.Stat() + assert.EqualValues(t, 0, stats.EmptyAcquireCount()) +} + +func TestPoolCreateResourceReturnsErrorFromFailedResourceCreate(t *testing.T) { + errCreateFailed := errors.New("create failed") + constructor := func(ctx context.Context) (interface{}, error) { + return nil, errCreateFailed + } + pool := puddle.NewPool(constructor, stubDestructor, 10) + + err := pool.CreateResource(context.Background()) + assert.Equal(t, errCreateFailed, err) +} + func TestPoolCloseClosesAllIdleResources(t *testing.T) { constructor, _ := createConstructor() From 426c57eed9332433098b88eabc8c1b1437115281 Mon Sep 17 00:00:00 2001 From: Patrick Ellul Date: Mon, 3 Feb 2020 11:15:21 +1100 Subject: [PATCH 2/3] test for LastUsedNano panic (to bring coverage back to 100%) --- pool_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pool_test.go b/pool_test.go index 1df1486..1e2fe32 100644 --- a/pool_test.go +++ b/pool_test.go @@ -573,6 +573,7 @@ func TestResourcePanicsOnUsageWhenNotAcquired(t *testing.T) { assert.PanicsWithValue(t, "tried to hijack resource that is not acquired", res.Hijack) assert.PanicsWithValue(t, "tried to access resource that is not acquired or hijacked", func() { res.Value() }) assert.PanicsWithValue(t, "tried to access resource that is not acquired or hijacked", func() { res.CreationTime() }) + assert.PanicsWithValue(t, "tried to access resource that is not acquired or hijacked", func() { res.LastUsedNanotime() }) assert.PanicsWithValue(t, "tried to access resource that is not acquired or hijacked", func() { res.IdleDuration() }) } From c42fada37edf3427e49b3a0a0f7c77acd876ebfa Mon Sep 17 00:00:00 2001 From: Patrick Ellul Date: Mon, 3 Feb 2020 15:56:07 +1100 Subject: [PATCH 3/3] CreateResource sets LastUsedNanotime so that Idle Time calculations are still valid --- pool.go | 1 + pool_test.go | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pool.go b/pool.go index 1a3fb30..f8afb35 100644 --- a/pool.go +++ b/pool.go @@ -388,6 +388,7 @@ func (p *Pool) CreateResource(ctx context.Context) error { creationTime: time.Now(), status: resourceStatusIdle, value: value, + lastUsedNano: nanotime(), } p.cond.L.Lock() diff --git a/pool_test.go b/pool_test.go index 1e2fe32..69c3646 100644 --- a/pool_test.go +++ b/pool_test.go @@ -255,21 +255,18 @@ func TestPoolCreateResource(t *testing.T) { err = pool.CreateResource(context.Background()) require.NoError(t, err) - allIdle := pool.AcquireAllIdle() - assert.Equal(t, counter.Value(), allIdle[0].Value()) - allIdle[0].ReleaseUnused() - stats := pool.Stat() assert.EqualValues(t, 1, stats.IdleResources()) res, err := pool.Acquire(context.Background()) require.NoError(t, err) + assert.Equal(t, counter.Value(), res.Value()) + assert.True(t, res.LastUsedNanotime() > 0, "should set LastUsedNanotime so that idle calculations can still work") assert.Equal(t, 1, res.Value()) assert.WithinDuration(t, time.Now(), res.CreationTime(), time.Second) res.Release() - stats = pool.Stat() - assert.EqualValues(t, 0, stats.EmptyAcquireCount()) + assert.EqualValues(t, 0, pool.Stat().EmptyAcquireCount(), "should have been a warm resource") } func TestPoolCreateResourceReturnsErrorFromFailedResourceCreate(t *testing.T) {