diff --git a/pool.go b/pool.go index 54acf30..f8afb35 100644 --- a/pool.go +++ b/pool.go @@ -373,6 +373,33 @@ 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, + lastUsedNano: nanotime(), + } + + 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..69c3646 100644 --- a/pool_test.go +++ b/pool_test.go @@ -245,6 +245,41 @@ 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) + + 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() + + assert.EqualValues(t, 0, pool.Stat().EmptyAcquireCount(), "should have been a warm resource") +} + +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() @@ -535,6 +570,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() }) }