package overflow_grpc_pool import ( "errors" "log" "time" "google.golang.org/grpc" ) var ( // ErrFullPool is the error when the pool is already full ErrPoolFull = errors.New("grpc pool: Pool is reached maximum capacity.") ) type pooledInstance struct { clientConn *grpc.ClientConn idleTime time.Time instance interface{} inUse bool } type Pool interface { Get() (interface{}, error) Put(i interface{}) Capacity() int Available() int Destroy() } type pool struct { options *Options instances map[interface{}]*pooledInstance instancesCh chan *pooledInstance } func New(o *Options) (Pool, error) { var err error p := &pool{ options: o.Validate(), instances: make(map[interface{}]*pooledInstance, o.MaxCapacity), instancesCh: make(chan *pooledInstance, o.MaxCapacity), } if 0 < o.MaxIdle { for i := 0; i < o.MaxIdle; i++ { err = p.createInstance() if nil != err { return nil, err } } } return p, nil } func (p *pool) createInstance() error { maxCapacity := p.options.MaxCapacity currentInstanceCount := len(p.instances) if currentInstanceCount >= maxCapacity { return ErrPoolFull } conn, i, err := p.options.Creators() if err != nil { return err } pi := &pooledInstance{ clientConn: conn, idleTime: time.Now(), instance: i, inUse: false, } p.instances[i] = pi p.instancesCh <- pi return nil } func (p *pool) destroyInstance(i interface{}) { var err error pi, ok := p.instances[i] if !ok { return } err = pi.clientConn.Close() if nil != err { log.Printf("pool: ClientConn close error: %v", err) } delete(p.instances, i) } func (p *pool) get() (*pooledInstance, error) { var pi *pooledInstance var err error avail := len(p.instancesCh) if 0 == avail { if err = p.createInstance(); nil != err { return nil, err } } for { select { case pi = <-p.instancesCh: // All good default: } idleTimeout := p.options.IdleTimeout if 1 < len(p.instancesCh) && 0 < idleTimeout && pi.idleTime.Add(idleTimeout).Before(time.Now()) { go p.destroyInstance(pi.instance) continue } break } return pi, nil } func (p *pool) Get() (interface{}, error) { var err error var pi *pooledInstance if pi, err = p.get(); nil != err { return nil, err } pi.inUse = true return pi.instance, nil } func (p *pool) Put(i interface{}) { pi, ok := p.instances[i] if !ok { return } pi.idleTime = time.Now() pi.inUse = false select { case p.instancesCh <- pi: // All good default: // channel is full return } } func (p *pool) Capacity() int { return cap(p.instancesCh) } func (p *pool) Available() int { return len(p.instancesCh) } func (p *pool) Destroy() { close(p.instancesCh) for k, _ := range p.instances { p.destroyInstance(k) } }