pool

package module
v1.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 22, 2025 License: MIT Imports: 5 Imported by: 0

README

go-pool

Package pool provides a concurrent, generic, variable-capacity object pool. It maintains a variable number of objects throughout the pool's lifetime, adjusting based on demand and idle timeouts. For a more static fixed-capacity alternative, consider https://github.com/michaellenaghan/go-simplepool.

  • The pool maintains between Min and Max busy and idle objects
  • Idle objects are stored in a ring buffer and ordered by their last-used times
  • Idle objects are reused on a LIFO (last in, first out) basis; in other words, the most recently used object is reused first
  • Idle objects are destroyed on a FIFO (first in, first out) basis; in other words, the least recently used object is destroyed first
  • A background goroutine checks idle objects every IdleTimeout/2 seconds
  • The background goroutine destroys idle objects that exceed the configured IdleTimeout
  • When there are no idle objects and the pool is at capacity, Get() calls wait for an object to be returned by Put()

The code is available at github.com/michaellenaghan/go-pool.

The documentation is available at pkg.go.dev/github.com/michaellenaghan/go-pool.

Installation

go get github.com/michaellenaghan/go-pool

Quick Start

package main

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/michaellenaghan/go-pool"
)

func main() {
	pool, err := pool.New(
		pool.Config[int]{
			Min:         2,
			Max:         10,
			IdleTimeout: 500 * time.Millisecond,
			NewFunc:     func() (int, error) { return 0, nil },
			CheckFunc:   func(int) error { return nil }, // this is optional, actually
			DestroyFunc: func(int) {},                   // this is optional, actually
		},
	)
	if err != nil {
		fmt.Printf("Failed to create pool: %v\n", err)
		return
	}
	defer pool.Stop()

	var wg sync.WaitGroup
	for range 100 {
		wg.Add(1)

		go func() {
			defer wg.Done()

			obj, err := pool.Get(context.Background())
			if err != nil {
				fmt.Printf("Failed to get object: %v\n", err)
				return
			}
			defer pool.Put(obj)

			time.Sleep(10 * time.Millisecond)
		}()
	}
	wg.Wait()

	stats := pool.Stats()
	fmt.Printf("CreatedTotal: %d\n", stats.CreatedTotal)     // Expect "10"
	fmt.Printf("DestroyedTotal: %d\n", stats.DestroyedTotal) // Expect "0"
	fmt.Printf("CountNow: %d\n", stats.CountNow)             // Expect "10"
	fmt.Printf("BusyNow: %d\n", stats.BusyNow)               // Expect "0"
	fmt.Printf("IdleNow: %d\n", stats.IdleNow)               // Expect "10"
	fmt.Printf("WaitingNow: %d\n", stats.WaitingNow)         // Expect "0"
	fmt.Printf("WaitingMax: %d\n", stats.WaitingMax)         // Expect "~90"
	fmt.Print("===\n")

	time.Sleep(1 * time.Second)

	stats = pool.Stats()
	fmt.Printf("CreatedTotal: %d\n", stats.CreatedTotal)     // Expect "10"
	fmt.Printf("DestroyedTotal: %d\n", stats.DestroyedTotal) // Expect "8"
	fmt.Printf("CountNow: %d\n", stats.CountNow)             // Expect "2"
	fmt.Printf("BusyNow: %d\n", stats.BusyNow)               // Expect "0"
	fmt.Printf("IdleNow: %d\n", stats.IdleNow)               // Expect "2"
	fmt.Printf("WaitingNow: %d\n", stats.WaitingNow)         // Expect "0"
	fmt.Printf("WaitingMax: %d\n", stats.WaitingMax)         // Expect "~90"
	fmt.Print("===\n")
}

License

MIT License

Documentation

Overview

Package `pool` provides a concurrent, generic, variable-capacity object pool. It maintains a variable number of objects throughout the pool's lifetime, adjusting based on demand and idle timeouts. For a more static fixed-capacity alternative, consider https://github.com/michaellenaghan/go-simplepool.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrNew               = errors.New("failed to make new pool object")
	ErrStoppingOrStopped = errors.New("pool is stopping or has stopped")
)

Functions

This section is empty.

Types

type Config

type Config[T any] struct {
	// Min is the minimum number of objects in the pool.
	// Must be >= 0.
	Min int
	// Max is the maximum number of objects in the pool.
	// Must be >= min.
	Max int
	// IdleTimeout is the maximum time an object can be idle before being
	// destroyed. If IdleTimeout is 0, objects never idle out (and min
	// should equal max). Otherwise, idle objects are checked every
	// IdleTimeout/2 seconds. If an object's idle time exceeds the idle
	// timeout, it's destroyed.
	// Must be >= 0.
	IdleTimeout time.Duration
	// NewFunc is a function that creates a new object.
	// This function is required.
	NewFunc func() (T, error)
	// CheckFunc is a function that checks an object when it's returned to
	// the pool. If CheckFunc returns an error, the object is destroyed (via
	// DestroyFunc) and not returned.
	// This function is optional.
	CheckFunc func(T) error
	// DestroyFunc is a function that destroys an object when it's no longer
	// needed.
	// This function is optional.
	DestroyFunc func(T)
}

Config configures a new pool.

func (*Config[T]) Check added in v0.2.0

func (c *Config[T]) Check() error

Check checks the configuration.

If the configuration is invalid, Check returns an error.

type Pool

type Pool[T any] struct {
	// contains filtered or unexported fields
}

Pool is a generic object pool that manages a collection of objects of type T.

Example (ConcurrentGetAndPut)
package main

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/michaellenaghan/go-pool"
)

func main() {
	pool, err := pool.New(
		pool.Config[int]{
			Min:         2,
			Max:         10,
			IdleTimeout: 500 * time.Millisecond,
			NewFunc:     func() (int, error) { return 0, nil },
			CheckFunc:   func(int) error { return nil }, // optional
			DestroyFunc: func(int) {},                   // optional
		},
	)
	if err != nil {
		fmt.Printf("Failed to create pool: %v\n", err)
		return
	}
	defer pool.Stop()

	wg := sync.WaitGroup{}
	for range 100 {
		wg.Add(1)

		go func() {
			defer wg.Done()

			obj, err := pool.Get(context.Background())
			if err != nil {
				fmt.Printf("Failed to get object: %v\n", err)
				return
			}
			defer pool.Put(obj)

			time.Sleep(10 * time.Millisecond)
		}()
	}
	wg.Wait()

	stats := pool.Stats()
	fmt.Printf("CreatedTotal: %d\n", stats.CreatedTotal)
	fmt.Printf("DestroyedTotal: %d\n", stats.DestroyedTotal)
	fmt.Printf("CountNow: %d\n", stats.CountNow)
	fmt.Printf("BusyNow: %d\n", stats.BusyNow)
	fmt.Printf("IdleNow: %d\n", stats.IdleNow)
	fmt.Printf("WaitingNow: %d\n", stats.WaitingNow)
	fmt.Printf("WaitingMax: %d\n", stats.WaitingMax)
	fmt.Print("===\n")

	time.Sleep(1 * time.Second)

	stats = pool.Stats()
	fmt.Printf("CreatedTotal: %d\n", stats.CreatedTotal)
	fmt.Printf("DestroyedTotal: %d\n", stats.DestroyedTotal)
	fmt.Printf("CountNow: %d\n", stats.CountNow)
	fmt.Printf("BusyNow: %d\n", stats.BusyNow)
	fmt.Printf("IdleNow: %d\n", stats.IdleNow)
	fmt.Printf("WaitingNow: %d\n", stats.WaitingNow)
	fmt.Printf("WaitingMax: %d\n", stats.WaitingMax)
	fmt.Print("===\n")

}
Output:
CreatedTotal: 10
DestroyedTotal: 0
CountNow: 10
BusyNow: 0
IdleNow: 10
WaitingNow: 0
WaitingMax: 90
===
CreatedTotal: 10
DestroyedTotal: 8
CountNow: 2
BusyNow: 0
IdleNow: 2
WaitingNow: 0
WaitingMax: 90
===

func New

func New[T any](config Config[T]) (*Pool[T], error)

New creates a new object pool.

New checks the provided config by calling config.Check(). If there's an error, New returns it.

Otherwise, New immediately creates the minimum number of pool objects. If there's an error creating one of those objects, New destroys the objects it created and returns the error.

Finally, New starts a goroutine that periodically checks for and removes idle objects from the pool. The checks are performed every idleTimeout/2 seconds. If an object's idle time exceeds the config's idle timeout, it's destroyed. Objects are checked and destroyed in FIFO order. In other words, the least recently used object is checked and destroyed first.

func (*Pool[T]) Get

func (p *Pool[T]) Get(ctx context.Context) (T, error)

Get returns an object from the pool, and an error.

(If the error is not nil the object will be the zero value of the type T.)

If the provided context was cancelled, Get returns an error.

Otherwise, if the pool is stopping or stopped, Get returns an error.

Otherwise, if there are idle objects, Get returns the most recently used idle object (LIFO).

Otherwise, if there's capacity, Get creates and returns a new object.

Otherwise, Get waits for an object to be returned to the pool by Put.

Get stops waiting when the provided context is cancelled or when Stop is called.

func (*Pool[T]) Put

func (p *Pool[T]) Put(object T)

Put returns an object to the pool.

If CheckFunc was provided in the pool configuration, it's called before anything else happens. If CheckFunc returns an error, Put destroys the object rather than returning it to the pool.

Otherwise, if the pool is stopping or stopped, Put destroys the object rather than returning it to the pool.

Otherwise, the object is added to the idle pool.

func (*Pool[T]) Stats

func (p *Pool[T]) Stats() Stats

Stats returns statistics about the pool.

func (*Pool[T]) Stop

func (p *Pool[T]) Stop()

Stop stops the pool.

If the pool is already stopping, or has already stopped, Stop does nothing.

Otherwise, Stop destroys all idle objects and then waits for all busy objects to be destroyed before returning.

type Stats

type Stats struct {
	// Number of created objects
	CreatedTotal uint
	// Number of destroyed objects
	DestroyedTotal uint

	// Number of objects right now (count == busy + idle)
	CountNow int
	// Number of busy objects right now (busy == count - idle)
	BusyNow int
	// Number of idle objects right now (idle == count - busy)
	IdleNow int

	// Number of objects being waited for right now
	// (i.e. current number of "Get"ers waiting for a "Put"er)
	WaitingNow int
	// Number of objects being waited for maximum
	// (i.e. maximum number of "Get"ers waiting for a "Put"er)
	WaitingMax int
}

Stats represents statistical information about the pool's performance.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL