Use runtime.nanotime for duration tracking plus related restructuring
runtime.nanotime is significantly faster than time.Now. Use it unless build tags prevent usage of unsafe. Change LastUsedTime to LastUsedNanotime as a time.Time is no longer available. Add IdleDuration to make it more convenient to get the time a resource has been idle. Rename ReleaseIdle to ReleaseUnused to better indicate it is releasing the resource without using it rather than that it is releasing a resource acquired with AcquireAllIdle. Refactor tests of usage tracking to not be intermingled with AcquireAllIdle tests.
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
// +build purego appengine js
|
||||
|
||||
// This file contains the safe implementation of nanotime using time.Now().
|
||||
|
||||
package puddle
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func nanotime() int64 {
|
||||
return time.Now().UnixNano()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// +build !purego,!appengine,!js
|
||||
|
||||
// This file contains the implementation of nanotime using runtime.nanotime.
|
||||
|
||||
package puddle
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var _ = unsafe.Sizeof(0)
|
||||
|
||||
//go:linkname nanotime runtime.nanotime
|
||||
func nanotime() int64
|
||||
@@ -29,7 +29,7 @@ type Resource struct {
|
||||
value interface{}
|
||||
pool *Pool
|
||||
creationTime time.Time
|
||||
lastUsedTime time.Time
|
||||
lastUsedNano int64
|
||||
status byte
|
||||
}
|
||||
|
||||
@@ -46,16 +46,16 @@ func (res *Resource) Release() {
|
||||
if res.status != resourceStatusAcquired {
|
||||
panic("tried to release resource that is not acquired")
|
||||
}
|
||||
res.pool.releaseAcquiredResource(res, time.Now())
|
||||
res.pool.releaseAcquiredResource(res, nanotime())
|
||||
}
|
||||
|
||||
// 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() {
|
||||
// ReleaseUnused returns the resource to the pool without updating when it was last used used. i.e. LastUsedNanotime
|
||||
// will not change. res must not be subsequently used.
|
||||
func (res *Resource) ReleaseUnused() {
|
||||
if res.status != resourceStatusAcquired {
|
||||
panic("tried to release resource that is not acquired")
|
||||
}
|
||||
res.pool.releaseAcquiredResource(res, res.lastUsedTime)
|
||||
res.pool.releaseAcquiredResource(res, res.lastUsedNano)
|
||||
}
|
||||
|
||||
// Destroy returns the resource to the pool for destruction. res must not be
|
||||
@@ -84,13 +84,29 @@ 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 {
|
||||
// LastUsedNanotime returns when Release was last called on the resource measured in nanoseconds from an arbitrary
|
||||
// time (a monotonic time). Returns 0 is Release as never been called. This is only useful to compare with other
|
||||
// calls to LastUsedNanotime. In almost all cases, IdleDuration should be used instead.
|
||||
func (res *Resource) LastUsedNanotime() int64 {
|
||||
if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
|
||||
panic("tried to access resource that is not acquired or hijacked")
|
||||
}
|
||||
return res.lastUsedTime
|
||||
|
||||
return res.lastUsedNano
|
||||
}
|
||||
|
||||
// IdleDuration returns the duration since Release was last called on the resource. If Release has never been called
|
||||
// a zero duration will be returned. This is equivalent to subtracting LastUsedNanotime to the current nanotime.
|
||||
func (res *Resource) IdleDuration() time.Duration {
|
||||
if !(res.status == resourceStatusAcquired || res.status == resourceStatusHijacked) {
|
||||
panic("tried to access resource that is not acquired or hijacked")
|
||||
}
|
||||
|
||||
if res.lastUsedNano == 0 {
|
||||
return time.Duration(0)
|
||||
}
|
||||
|
||||
return time.Duration(nanotime() - res.lastUsedNano)
|
||||
}
|
||||
|
||||
// Pool is a concurrency-safe resource pool.
|
||||
@@ -240,7 +256,7 @@ func (p *Pool) Stat() *Stat {
|
||||
// maximum capacity it will block until a resource is available. ctx can be used
|
||||
// to cancel the Acquire.
|
||||
func (p *Pool) Acquire(ctx context.Context) (*Resource, error) {
|
||||
startTime := time.Now()
|
||||
startNano := nanotime()
|
||||
p.cond.L.Lock()
|
||||
if doneChan := ctx.Done(); doneChan != nil {
|
||||
select {
|
||||
@@ -269,7 +285,7 @@ func (p *Pool) Acquire(ctx context.Context) (*Resource, error) {
|
||||
p.emptyAcquireCount += 1
|
||||
}
|
||||
p.acquireCount += 1
|
||||
p.acquireDuration += time.Now().Sub(startTime)
|
||||
p.acquireDuration += time.Duration(nanotime() - startNano)
|
||||
p.cond.L.Unlock()
|
||||
return res, nil
|
||||
}
|
||||
@@ -278,7 +294,7 @@ func (p *Pool) Acquire(ctx context.Context) (*Resource, error) {
|
||||
|
||||
// If there is room to create a resource do so
|
||||
if len(p.allResources) < int(p.maxSize) {
|
||||
res := &Resource{pool: p, creationTime: startTime, status: resourceStatusConstructing}
|
||||
res := &Resource{pool: p, creationTime: time.Now(), status: resourceStatusConstructing}
|
||||
p.allResources = append(p.allResources, res)
|
||||
p.destructWG.Add(1)
|
||||
p.cond.L.Unlock()
|
||||
@@ -305,7 +321,7 @@ func (p *Pool) Acquire(ctx context.Context) (*Resource, error) {
|
||||
res.status = resourceStatusAcquired
|
||||
p.emptyAcquireCount += 1
|
||||
p.acquireCount += 1
|
||||
p.acquireDuration += time.Now().Sub(startTime)
|
||||
p.acquireDuration += time.Duration(nanotime() - startNano)
|
||||
p.cond.L.Unlock()
|
||||
return res, nil
|
||||
}
|
||||
@@ -358,11 +374,11 @@ func (p *Pool) AcquireAllIdle() []*Resource {
|
||||
}
|
||||
|
||||
// releaseAcquiredResource returns res to the the pool.
|
||||
func (p *Pool) releaseAcquiredResource(res *Resource, lastUsedTime time.Time) {
|
||||
func (p *Pool) releaseAcquiredResource(res *Resource, lastUsedNano int64) {
|
||||
p.cond.L.Lock()
|
||||
|
||||
if !p.closed {
|
||||
res.lastUsedTime = lastUsedTime
|
||||
res.lastUsedNano = lastUsedNano
|
||||
res.status = resourceStatusIdle
|
||||
p.idleResources = append(p.idleResources, res)
|
||||
} else {
|
||||
|
||||
+49
-16
@@ -218,8 +218,6 @@ 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())
|
||||
@@ -228,25 +226,23 @@ 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())
|
||||
assert.Equal(t, r0LastUsedTime, resources[0].LastUsedTime(), "should not have updated lastUsedTime")
|
||||
resources[0].ReleaseIdle()
|
||||
resources[1].ReleaseIdle()
|
||||
resources[2].ReleaseIdle()
|
||||
resources[3].ReleaseIdle()
|
||||
|
||||
resources[0].Release()
|
||||
resources[1].Release()
|
||||
resources[2].Release()
|
||||
resources[3].Release()
|
||||
}
|
||||
|
||||
func TestPoolCloseClosesAllIdleResources(t *testing.T) {
|
||||
@@ -469,10 +465,10 @@ func TestResourceDestroyRemovesResourceFromPool(t *testing.T) {
|
||||
assert.EqualValues(t, 0, pool.Stat().TotalResources())
|
||||
assert.EqualValues(t, 0, destructorCalls.Value())
|
||||
|
||||
// Can still call Value, CreationTime and LastUsedTime
|
||||
// Can still call Value, CreationTime and IdleDuration
|
||||
res.Value()
|
||||
res.CreationTime()
|
||||
res.LastUsedTime()
|
||||
res.IdleDuration()
|
||||
}
|
||||
|
||||
func TestResourceHijackRemovesResourceFromPoolButDoesNotDestroy(t *testing.T) {
|
||||
@@ -488,6 +484,43 @@ func TestResourceHijackRemovesResourceFromPoolButDoesNotDestroy(t *testing.T) {
|
||||
assert.EqualValues(t, 0, pool.Stat().TotalResources())
|
||||
}
|
||||
|
||||
func TestResourceLastUsageTimeTracking(t *testing.T) {
|
||||
constructor, _ := createConstructor()
|
||||
pool := puddle.NewPool(constructor, stubDestructor, 1)
|
||||
|
||||
// 0 before initial usage
|
||||
res, err := pool.Acquire(context.Background())
|
||||
require.NoError(t, err)
|
||||
t1 := res.LastUsedNanotime()
|
||||
d1 := res.IdleDuration()
|
||||
assert.EqualValues(t, 0, t1)
|
||||
assert.EqualValues(t, 0, d1)
|
||||
res.Release()
|
||||
|
||||
// Greater than zero after initial usage
|
||||
res, err = pool.Acquire(context.Background())
|
||||
require.NoError(t, err)
|
||||
t2 := res.LastUsedNanotime()
|
||||
d2 := res.IdleDuration()
|
||||
assert.True(t, t2 > 0)
|
||||
assert.True(t, d2 > 0)
|
||||
res.ReleaseUnused()
|
||||
|
||||
// ReleaseUnused does not update usage tracking
|
||||
res, err = pool.Acquire(context.Background())
|
||||
require.NoError(t, err)
|
||||
t3 := res.LastUsedNanotime()
|
||||
assert.EqualValues(t, t2, t3)
|
||||
res.Release()
|
||||
|
||||
// Release does update usage tracking
|
||||
res, err = pool.Acquire(context.Background())
|
||||
require.NoError(t, err)
|
||||
t4 := res.LastUsedNanotime()
|
||||
assert.True(t, t4 > t3)
|
||||
res.Release()
|
||||
}
|
||||
|
||||
func TestResourcePanicsOnUsageWhenNotAcquired(t *testing.T) {
|
||||
constructor, _ := createConstructor()
|
||||
pool := puddle.NewPool(constructor, stubDestructor, 10)
|
||||
@@ -497,12 +530,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 release resource that is not acquired", res.ReleaseUnused)
|
||||
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() })
|
||||
assert.PanicsWithValue(t, "tried to access resource that is not acquired or hijacked", func() { res.IdleDuration() })
|
||||
}
|
||||
|
||||
func TestPoolAcquireReturnsErrorWhenPoolIsClosed(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user