From 8101c0dff4beedfa171d281d65a7d5209b2952fc Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Thu, 6 Jul 2023 21:18:31 -0500 Subject: [PATCH] CreateResource cannot overflow pool --- pool.go | 38 ++++++++++++++++++++++---------------- pool_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/pool.go b/pool.go index f9190d6..c8edc0f 100644 --- a/pool.go +++ b/pool.go @@ -588,42 +588,48 @@ func (p *Pool[T]) AcquireAllIdle() []*Resource[T] { return idle } -// 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. +// CreateResource constructs a new resource without acquiring it. It goes straight in the IdlePool. If the pool is full +// it returns an error. It can be useful to maintain warm resources under little load. func (p *Pool[T]) CreateResource(ctx context.Context) error { + if !p.acquireSem.TryAcquire(1) { + return ErrNotAvailable + } + p.mux.Lock() if p.closed { + p.acquireSem.Release(1) p.mux.Unlock() return ErrClosedPool } - p.destructWG.Add(1) + + if len(p.allResources) >= int(p.maxSize) { + p.acquireSem.Release(1) + p.mux.Unlock() + return ErrNotAvailable + } + + res := p.createNewResource() p.mux.Unlock() value, err := p.constructor(ctx) + p.mux.Lock() + defer p.mux.Unlock() + defer p.acquireSem.Release(1) if err != nil { + p.allResources.remove(res) p.destructWG.Done() return err } - res := &Resource[T]{ - pool: p, - creationTime: time.Now(), - status: resourceStatusIdle, - value: value, - lastUsedNano: nanotime(), - poolResetCount: p.resetCount, - } - - p.mux.Lock() - defer p.mux.Unlock() + res.value = value + res.status = resourceStatusIdle // If closed while constructing resource then destroy it and return an error if p.closed { go p.destructResourceValue(res.value) return ErrClosedPool } - p.allResources.append(res) + p.idleResources.Push(res) return nil diff --git a/pool_test.go b/pool_test.go index ac0d15b..27f9079 100644 --- a/pool_test.go +++ b/pool_test.go @@ -448,6 +448,28 @@ func TestPoolCreateResourceReturnsErrorWhenClosedWhileCreatingResource(t *testin assert.Equal(t, puddle.ErrClosedPool, err) } +func TestPoolCreateResourceReturnsErrorWhenPoolFull(t *testing.T) { + constructor, _ := createConstructor() + pool, err := puddle.NewPool(&puddle.Config[int]{Constructor: constructor, Destructor: stubDestructor, MaxSize: 2}) + require.NoError(t, err) + defer pool.Close() + + err = pool.CreateResource(context.Background()) + require.NoError(t, err) + + stats := pool.Stat() + assert.EqualValues(t, 1, stats.IdleResources()) + + err = pool.CreateResource(context.Background()) + require.NoError(t, err) + + stats = pool.Stat() + assert.EqualValues(t, 2, stats.IdleResources()) + + err = pool.CreateResource(context.Background()) + require.Error(t, err) +} + func TestPoolCloseClosesAllIdleResources(t *testing.T) { constructor, _ := createConstructor() @@ -990,6 +1012,13 @@ func TestStress(t *testing.T) { stat := pool.Stat() assert.NotNil(t, stat) }, + // CreateResource + func() { + err := pool.CreateResource(context.Background()) + if err != nil && !errors.Is(err, puddle.ErrClosedPool) && !errors.Is(err, puddle.ErrNotAvailable) { + t.Error(err) + } + }, } workerCount := int(poolSize) * 2