diff --git a/pool.go b/pool.go index 06315c3..3b19a5b 100644 --- a/pool.go +++ b/pool.go @@ -7,10 +7,10 @@ import ( ) const ( - resourceStatusCreating = 0 - resourceStatusIdle = iota - resourceStatusAcquired = iota - resourceStatusHijacked = iota + resourceStatusConstructing = 0 + resourceStatusIdle = iota + resourceStatusAcquired = iota + resourceStatusHijacked = iota ) // ErrClosedPool occurs on an attempt to get a connection from a closed pool. @@ -96,20 +96,59 @@ func (p *Pool) Close() { p.destructWG.Wait() } -// Size returns the current size of the pool. -func (p *Pool) Size() int { - p.cond.L.Lock() - n := len(p.allResources) - p.cond.L.Unlock() - return n +type Stat struct { + constructing int + acquired int + idle int + maxSize int } -// MaxSize returns the current maximum size of the pool. -func (p *Pool) MaxSize() int { +// Size returns the total number of resources in the pool. +func (s *Stat) Size() int { + return s.constructing + s.acquired + s.idle +} + +// Constructing returns the number of resources with construction in progress in +// the pool. +func (s *Stat) Constructing() int { + return s.constructing +} + +// Acquired returns the number of acquired resources in the pool. +func (s *Stat) Acquired() int { + return s.acquired +} + +// Idle returns the number of idle resources in the pool. +func (s *Stat) Idle() int { + return s.idle +} + +// MaxSize returns the maximum size of the pool +func (s *Stat) MaxSize() int { + return s.maxSize +} + +// Stat returns the current pool statistics. +func (p *Pool) Stat() *Stat { p.cond.L.Lock() - n := p.maxSize + s := &Stat{ + maxSize: p.maxSize, + } + + for _, res := range p.allResources { + switch res.status { + case resourceStatusConstructing: + s.constructing += 1 + case resourceStatusIdle: + s.idle += 1 + case resourceStatusAcquired: + s.acquired += 1 + } + } + p.cond.L.Unlock() - return n + return s } // Acquire gets a resource from the pool. If no resources are available and the pool @@ -142,7 +181,7 @@ func (p *Pool) Acquire(ctx context.Context) (*Resource, error) { // If there is room to create a resource do so if len(p.allResources) < p.maxSize { - res := &Resource{pool: p, status: resourceStatusCreating} + res := &Resource{pool: p, status: resourceStatusConstructing} p.allResources = append(p.allResources, res) p.cond.L.Unlock() diff --git a/pool_test.go b/pool_test.go index 3d035e5..e48213e 100644 --- a/pool_test.go +++ b/pool_test.go @@ -113,7 +113,7 @@ func TestPoolAcquireDoesNotCreatesResourceWhenItWouldExceedMaxSize(t *testing.T) wg.Wait() assert.Equal(t, 1, createCounter.Value()) - assert.Equal(t, 1, pool.Size()) + assert.Equal(t, 1, pool.Stat().Size()) } func TestPoolAcquireWithCancellableContext(t *testing.T) { @@ -140,7 +140,7 @@ func TestPoolAcquireWithCancellableContext(t *testing.T) { wg.Wait() assert.Equal(t, 1, createCounter.Value()) - assert.Equal(t, 1, pool.Size()) + assert.Equal(t, 1, pool.Stat().Size()) } func TestPoolAcquireReturnsErrorFromFailedResourceCreate(t *testing.T) { @@ -261,6 +261,55 @@ func TestPoolCloseBlocksUntilAllResourcesReleasedAndClosed(t *testing.T) { assert.Equal(t, len(resources), closeCalls.Value()) } +func TestPoolStat(t *testing.T) { + startWaitChan := make(chan struct{}) + waitingChan := make(chan struct{}) + endWaitChan := make(chan struct{}) + + var createCalls Counter + createFunc := func(ctx context.Context) (interface{}, error) { + select { + case <-startWaitChan: + close(waitingChan) + <-endWaitChan + default: + } + + return createCalls.Next(), nil + } + pool := puddle.NewPool(createFunc, stubCloseRes, 10) + defer pool.Close() + + resAcquired, err := pool.Acquire(context.Background()) + require.Nil(t, err) + + close(startWaitChan) + go func() { + res, err := pool.Acquire(context.Background()) + require.Nil(t, err) + res.Release() + }() + <-waitingChan + stat := pool.Stat() + + assert.Equal(t, 2, stat.Size()) + assert.Equal(t, 1, stat.Constructing()) + assert.Equal(t, 1, stat.Acquired()) + assert.Equal(t, 0, stat.Idle()) + assert.Equal(t, 10, stat.MaxSize()) + + resAcquired.Release() + + stat = pool.Stat() + assert.Equal(t, 2, stat.Size()) + assert.Equal(t, 1, stat.Constructing()) + assert.Equal(t, 0, stat.Acquired()) + assert.Equal(t, 1, stat.Idle()) + assert.Equal(t, 10, stat.MaxSize()) + + close(endWaitChan) +} + func TestResourceDestroyRemovesResourceFromPool(t *testing.T) { createFunc, _ := createCreateResourceFunc() var closeCalls Counter @@ -276,7 +325,7 @@ func TestResourceDestroyRemovesResourceFromPool(t *testing.T) { res.Hijack() - assert.Equal(t, 0, pool.Size()) + assert.Equal(t, 0, pool.Stat().Size()) assert.Equal(t, 0, closeCalls.Value()) } @@ -288,9 +337,9 @@ func TestResourceHijackRemovesResourceFromPoolButDoesNotDestroy(t *testing.T) { require.NoError(t, err) assert.Equal(t, 1, res.Value()) - assert.Equal(t, 1, pool.Size()) + assert.Equal(t, 1, pool.Stat().Size()) res.Destroy() - assert.Equal(t, 0, pool.Size()) + assert.Equal(t, 0, pool.Stat().Size()) } func TestPoolAcquireReturnsErrorWhenPoolIsClosed(t *testing.T) {