package genpool import "errors" var ( ErrPoolClosed = errors.New("pool already closed") ) type getResp[Item interface{}] struct { Item *Item err error } type poolReq[Item interface{}] struct { get chan<- getResp[Item] ret *Item } type PoolManager[Item interface{}] interface { Destroy(*Item) Create() (*Item, error) Validate(*Item) bool } type poolBackend[Item interface{}] struct { manager PoolManager[Item] pool []*Item } func (b *poolBackend[Item]) run(ch <-chan poolReq[Item]) { defer func() { for _, item := range b.pool { b.manager.Destroy(item) } }() for req := range ch { if req.get != nil { // fetch an item for { if len(b.pool) > 0 { item := b.pool[len(b.pool)-1] b.pool = b.pool[:len(b.pool)-1] if b.manager.Validate(item) { req.get <- getResp[Item]{ Item: item, err: nil, } break } else { b.manager.Destroy(item) } } else { // last item item, err := b.manager.Create() req.get <- getResp[Item]{ Item: item, err: err, } break } } } if req.ret != nil { // return an item to the pool if cap(b.pool) > len(b.pool) { b.pool = append(b.pool, req.ret) } else { b.manager.Destroy(req.ret) } } } } type Pool[Item interface{}] struct { req chan<- poolReq[Item] } func NewPool[Item interface{}](manager PoolManager[Item], capacity int) Pool[Item] { cmdChan := make(chan poolReq[Item]) backend := poolBackend[Item]{ manager: manager, pool: make([]*Item, 0, capacity), } go backend.run(cmdChan) return Pool[Item]{ req: cmdChan, } } func (p *Pool[Item]) Get() (*Item, error) { retCh := make(chan getResp[Item]) p.req <- poolReq[Item]{ get: retCh, } item, ok := <-retCh if ok { return item.Item, item.err } else { return nil, ErrPoolClosed } } func (p *Pool[Item]) Put(item *Item) { p.req <- poolReq[Item]{ ret: item, } } func (p *Pool[Item]) Close() { close(p.req) p.req = nil }