diff --git a/pool.go b/pool.go index 5f57feb..7d5ee04 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 @@ -339,10 +358,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 41eac9b..eab1366 100644 --- a/pool_test.go +++ b/pool_test.go @@ -218,6 +218,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()) @@ -226,23 +228,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) { @@ -465,9 +469,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) { @@ -492,10 +497,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) {