// pool with support for: // - finalizing items // - monitoring: events and/or stats // - adaptive shrinking algorithm // - custom shrinking algorithms package pool import ( "errors" "time" ) type Stats struct { Idle int Active int Get int Put int Alloc int Free int } type EventType int const ( None EventType = 0 GetOperation EventType = 1 << iota PutOperation AllocateOperation FreeOperation AllocateError ) type Event struct { Type EventType Stats Stats } type Algo interface { // always called // desired idle items // implementations should consider the cost of freeing the stored resources // must support being called from a goroutine other than it was created in // a single pool instance only calls it from a single goroutine at a time // items need to be allocated always by calling Get // second return argument for requested next check Target(Stats) (int, time.Duration) } type Options struct { // events can be dropped if the consumer is blocked Events chan<- Event EventMask EventType Algo Algo } type Pool[R any] struct { pool pool[R] } var ErrEmptyPool = errors.New("empty pool") // zero-config func Adaptive() Algo { return makeAdaptiveAlgo() } // enfoces a max pool size and a timeout for the items // when adding items to the pool via Put that were not fetched via Get, there can discrepancies occur in which // items get timed out, but the general pool limitations get still consistently enforced eventually func MaxTimeout(max int, to time.Duration) Algo { return makeMaxTimeout(max, to) } // like MaxTimeout but without enforcing timeouts func Max(max int) Algo { return makeMaxTimeout(max, 0) } // like MaxTimeout but without enforcing max pool size func Timeout(to time.Duration) Algo { return makeMaxTimeout(0, to) } // the user code can decide not to put back items to the pool func NoShrink() Algo { return makeMaxTimeout(0, 0) } // alloc and free need to support calls from goroutines other than they were created in // a single pool instance only calls them from a single goroutine at a time // free happens synchronously, user code may execute it in the background, in which case it is the user code's // responsibility to ensure that free is fully carried out before the application exits, if that's necessary func Make[R any](alloc func() (R, error), free func(R), o Options) Pool[R] { return Pool[R]{pool: makePool(alloc, free, o)} } func (p Pool[R]) Get() (R, error) { return p.pool.get() } func (p Pool[R]) Put(i R) { p.pool.put(i) } func (p Pool[R]) Stats() Stats { return p.pool.stats() } func (p Pool[R]) Free() { p.pool.freePool() }