From 6a68341f62efaeb3be1dbc2b596b7eb5be389b6e Mon Sep 17 00:00:00 2001 From: Patrick Ellul Date: Sat, 25 Jan 2020 21:41:34 +1100 Subject: [PATCH] maintain lastIUsedTime so clients can use it as a factor in idle and health check logic --- pool.go | 26 ++++++++++++++++++++++++-- pool_test.go | 25 ++++++++++++++++--------- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/pool.go b/pool.go index 2bce46c..c370a9e 100644 --- a/pool.go +++ b/pool.go @@ -29,6 +29,7 @@ type Resource struct { value interface{} pool *Pool creationTime time.Time + lastUsedTime time.Time status byte } @@ -45,7 +46,16 @@ func (res *Resource) Release() { if res.status != resourceStatusAcquired { panic("tried to release resource that is not acquired") } - res.pool.releaseAcquiredResource(res) + res.pool.releaseAcquiredResource(res, true) +} + +// Release returns the resource to the pool after it was acquired via AcquireAllIdle. +// It does not updates lastUsedTime. res must not be subsequently used. +func (res *Resource) ReleaseIdle() { + if res.status != resourceStatusAcquired { + panic("tried to release resource that is not acquired") + } + res.pool.releaseAcquiredResource(res, false) } // Destroy returns the resource to the pool for destruction. res must not be @@ -74,6 +84,15 @@ func (res *Resource) CreationTime() time.Time { return res.creationTime } +// LastUsedTime returns when the resource was last used, specifically when +// it was released from a normal Acquire (not from an AcquireAllIdle) +func (res *Resource) LastUsedTime() time.Time { + if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) { + panic("tried to access resource that is not acquired or hijacked") + } + return res.lastUsedTime +} + // Pool is a concurrency-safe resource pool. type Pool struct { cond *sync.Cond @@ -340,10 +359,13 @@ func (p *Pool) AcquireAllIdle() []*Resource { } // releaseAcquiredResource returns res to the the pool. -func (p *Pool) releaseAcquiredResource(res *Resource) { +func (p *Pool) releaseAcquiredResource(res *Resource, updateLastUsed bool) { p.cond.L.Lock() if !p.closed { + if updateLastUsed { + res.lastUsedTime = time.Now() + } res.status = resourceStatusIdle p.idleResources = append(p.idleResources, res) } else { diff --git a/pool_test.go b/pool_test.go index 837e546..64c9150 100644 --- a/pool_test.go +++ b/pool_test.go @@ -217,6 +217,8 @@ func TestPoolAcquireAllIdle(t *testing.T) { resources[0], err = pool.Acquire(context.Background()) require.NoError(t, err) + assert.True(t, resources[0].LastUsedTime().IsZero(), "lastUsedTime should start as Zero") + resources[1], err = pool.Acquire(context.Background()) require.NoError(t, err) resources[2], err = pool.Acquire(context.Background()) @@ -225,23 +227,25 @@ func TestPoolAcquireAllIdle(t *testing.T) { require.NoError(t, err) assert.Len(t, pool.AcquireAllIdle(), 0) - resources[0].Release() resources[3].Release() assert.ElementsMatch(t, []*puddle.Resource{resources[0], resources[3]}, pool.AcquireAllIdle()) + r0LastUsedTime := resources[0].LastUsedTime() + assert.WithinDuration(t, time.Now(), r0LastUsedTime, time.Second, "should have updated lastUsedTime") + time.Sleep(1 * time.Millisecond) // sleep before releasing + resources[0].ReleaseIdle() + resources[3].ReleaseIdle() - resources[0].Release() - resources[3].Release() resources[1].Release() resources[2].Release() assert.ElementsMatch(t, resources, pool.AcquireAllIdle()) - - resources[0].Release() - resources[1].Release() - resources[2].Release() - resources[3].Release() + assert.Equal(t, r0LastUsedTime, resources[0].LastUsedTime(), "should not have updated lastUsedTime") + resources[0].ReleaseIdle() + resources[1].ReleaseIdle() + resources[2].ReleaseIdle() + resources[3].ReleaseIdle() } func TestPoolCloseClosesAllIdleResources(t *testing.T) { @@ -464,9 +468,10 @@ func TestResourceDestroyRemovesResourceFromPool(t *testing.T) { assert.EqualValues(t, 0, pool.Stat().TotalResources()) assert.EqualValues(t, 0, destructorCalls.Value()) - // Can still call Value and CreationTime + // Can still call Value, CreationTime and LastUsedTime res.Value() res.CreationTime() + res.LastUsedTime() } func TestResourceHijackRemovesResourceFromPoolButDoesNotDestroy(t *testing.T) { @@ -491,10 +496,12 @@ func TestResourcePanicsOnUsageWhenNotAcquired(t *testing.T) { res.Release() assert.PanicsWithValue(t, "tried to release resource that is not acquired", res.Release) + assert.PanicsWithValue(t, "tried to release resource that is not acquired", res.ReleaseIdle) assert.PanicsWithValue(t, "tried to destroy resource that is not acquired", res.Destroy) 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.LastUsedTime() }) } func TestPoolAcquireReturnsErrorWhenPoolIsClosed(t *testing.T) {