github.com/godaddy-x/freego@v1.0.156/rpcx/pool/pool.go (about)

     1  // Copyright 2019 shimingyah. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // ee the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package pool
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"google.golang.org/grpc"
    22  	"log"
    23  	"math"
    24  	"sync"
    25  	"sync/atomic"
    26  	"time"
    27  )
    28  
    29  type ConnConfig struct {
    30  	Opts    []grpc.DialOption
    31  	Timeout int
    32  	Address string
    33  }
    34  
    35  // ErrClosed is the error resulting if the pool is closed via pool.Close().
    36  var ErrClosed = errors.New("pool is closed")
    37  
    38  // Pool interface describes a pool implementation.
    39  // An ideal pool is threadsafe and easy to use.
    40  type Pool interface {
    41  	// Get returns a new connection from the pool. Closing the connections puts
    42  	// it back to the Pool. Closing it when the pool is destroyed or full will
    43  	// be counted as an error. we guarantee the conn.Value() isn't nil when conn isn't nil.
    44  	Get() (Conn, error)
    45  
    46  	// Close closes the pool and all its connections. After Close() the pool is
    47  	// no longer usable. You can't make concurrent calls Close and Get method.
    48  	// It will be cause panic.
    49  	Close() error
    50  
    51  	// Status returns the current status of the pool.
    52  	Status() string
    53  }
    54  
    55  type pool struct {
    56  	// atomic, used to get connection random
    57  	index uint32
    58  
    59  	// atomic, the current physical connection of pool
    60  	current int32
    61  
    62  	// atomic, the using logic connection of pool
    63  	// logic connection = physical connection * MaxConcurrentStreams
    64  	ref int32
    65  
    66  	// pool options
    67  	opt Options
    68  
    69  	// all of created physical connections
    70  	conns []*conn
    71  
    72  	// the server address is to create connection.
    73  	address string
    74  
    75  	// closed set true when Close is called.
    76  	closed int32
    77  
    78  	// control the atomic var current's concurrent read write.
    79  	sync.RWMutex
    80  
    81  	// connection config
    82  	connConfig ConnConfig
    83  }
    84  
    85  // New return a connection pool.
    86  func New(address string, option Options) (Pool, error) {
    87  	if address == "" {
    88  		return nil, errors.New("invalid address settings")
    89  	}
    90  	if option.Dial == nil {
    91  		return nil, errors.New("invalid dial settings")
    92  	}
    93  	if option.MaxIdle <= 0 || option.MaxActive <= 0 || option.MaxIdle > option.MaxActive {
    94  		return nil, errors.New("invalid maximum settings")
    95  	}
    96  	if option.MaxConcurrentStreams <= 0 {
    97  		return nil, errors.New("invalid maximun settings")
    98  	}
    99  
   100  	p := &pool{
   101  		index:   0,
   102  		current: int32(option.MaxIdle),
   103  		ref:     0,
   104  		opt:     option,
   105  		conns:   make([]*conn, option.MaxActive),
   106  		address: address,
   107  		closed:  0,
   108  	}
   109  
   110  	for i := 0; i < p.opt.MaxIdle; i++ {
   111  		c, err := p.opt.Dial(address)
   112  		if err != nil {
   113  			p.Close()
   114  			return nil, fmt.Errorf("dial is not able to fill the pool: %s", err)
   115  		}
   116  		p.conns[i] = p.wrapConn(c, false)
   117  	}
   118  	log.Printf("new pool success: %v\n", p.Status())
   119  
   120  	return p, nil
   121  }
   122  
   123  func (p *pool) NewClientConn() (*grpc.ClientConn, error) {
   124  	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(p.connConfig.Timeout)*time.Second)
   125  	defer cancel()
   126  	conn, err := grpc.DialContext(ctx, p.connConfig.Address, p.connConfig.Opts...)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	return conn, nil
   131  }
   132  
   133  // New return a connection pool.
   134  func NewPool(option Options, config ConnConfig) (Pool, error) {
   135  	if config.Address == "" {
   136  		return nil, errors.New("invalid address settings")
   137  	}
   138  	if config.Timeout == 0 {
   139  		return nil, errors.New("invalid timeout settings")
   140  	}
   141  	if config.Opts == nil || len(config.Opts) == 0 {
   142  		return nil, errors.New("invalid opts settings")
   143  	}
   144  	if option.MaxIdle <= 0 || option.MaxActive <= 0 || option.MaxIdle > option.MaxActive {
   145  		return nil, errors.New("invalid maximum settings")
   146  	}
   147  	if option.MaxConcurrentStreams <= 0 {
   148  		return nil, errors.New("invalid maximun settings")
   149  	}
   150  
   151  	p := &pool{
   152  		index:      0,
   153  		current:    int32(option.MaxIdle),
   154  		ref:        0,
   155  		opt:        option,
   156  		conns:      make([]*conn, option.MaxActive),
   157  		address:    config.Address,
   158  		closed:     0,
   159  		connConfig: config,
   160  	}
   161  
   162  	for i := 0; i < p.opt.MaxIdle; i++ {
   163  		c, err := p.NewClientConn()
   164  		if err != nil {
   165  			p.Close()
   166  			return nil, fmt.Errorf("dial is not able to fill the pool: %s", err)
   167  		}
   168  		p.conns[i] = p.wrapConn(c, false)
   169  	}
   170  	log.Printf("new grpc client pool success: %v\n", p.Status())
   171  
   172  	return p, nil
   173  }
   174  
   175  func (p *pool) incrRef() int32 {
   176  	newRef := atomic.AddInt32(&p.ref, 1)
   177  	if newRef == math.MaxInt32 {
   178  		panic(fmt.Sprintf("overflow ref: %d", newRef))
   179  	}
   180  	return newRef
   181  }
   182  
   183  func (p *pool) decrRef() {
   184  	newRef := atomic.AddInt32(&p.ref, -1)
   185  	if newRef < 0 && atomic.LoadInt32(&p.closed) == 0 {
   186  		panic(fmt.Sprintf("negative ref: %d", newRef))
   187  	}
   188  	if newRef == 0 && atomic.LoadInt32(&p.current) > int32(p.opt.MaxIdle) {
   189  		p.Lock()
   190  		if atomic.LoadInt32(&p.ref) == 0 {
   191  			log.Printf("shrink pool: %d ---> %d, decrement: %d, maxActive: %d\n",
   192  				p.current, p.opt.MaxIdle, p.current-int32(p.opt.MaxIdle), p.opt.MaxActive)
   193  			atomic.StoreInt32(&p.current, int32(p.opt.MaxIdle))
   194  			p.deleteFrom(p.opt.MaxIdle)
   195  		}
   196  		p.Unlock()
   197  	}
   198  }
   199  
   200  func (p *pool) reset(index int) {
   201  	conn := p.conns[index]
   202  	if conn == nil {
   203  		return
   204  	}
   205  	conn.reset()
   206  	p.conns[index] = nil
   207  }
   208  
   209  func (p *pool) deleteFrom(begin int) {
   210  	for i := begin; i < p.opt.MaxActive; i++ {
   211  		p.reset(i)
   212  	}
   213  }
   214  
   215  // Get see Pool interface.
   216  func (p *pool) Get() (Conn, error) {
   217  	// the first selected from the created connections
   218  	nextRef := p.incrRef()
   219  	p.RLock()
   220  	current := atomic.LoadInt32(&p.current)
   221  	p.RUnlock()
   222  	if current == 0 {
   223  		return nil, ErrClosed
   224  	}
   225  	if nextRef <= current*int32(p.opt.MaxConcurrentStreams) {
   226  		next := atomic.AddUint32(&p.index, 1) % uint32(current)
   227  		return p.conns[next], nil
   228  	}
   229  
   230  	// the number connection of pool is reach to max active
   231  	if current == int32(p.opt.MaxActive) {
   232  		// the second if reuse is true, select from pool's connections
   233  		if p.opt.Reuse {
   234  			next := atomic.AddUint32(&p.index, 1) % uint32(current)
   235  			return p.conns[next], nil
   236  		}
   237  		// the third create one-time connection
   238  		//c, err := p.opt.Dial(p.address)
   239  		c, err := p.NewClientConn()
   240  		return p.wrapConn(c, true), err
   241  	}
   242  
   243  	// the fourth create new connections given back to pool
   244  	p.Lock()
   245  	current = atomic.LoadInt32(&p.current)
   246  	if current < int32(p.opt.MaxActive) && nextRef > current*int32(p.opt.MaxConcurrentStreams) {
   247  		// 2 times the incremental or the remain incremental
   248  		increment := current
   249  		if current+increment > int32(p.opt.MaxActive) {
   250  			increment = int32(p.opt.MaxActive) - current
   251  		}
   252  		var i int32
   253  		var err error
   254  		for i = 0; i < increment; i++ {
   255  			//c, er := p.opt.Dial(p.address)
   256  			c, er := p.NewClientConn()
   257  			if er != nil {
   258  				err = er
   259  				break
   260  			}
   261  			p.reset(int(current + i))
   262  			p.conns[current+i] = p.wrapConn(c, false)
   263  		}
   264  		current += i
   265  		log.Printf("grow pool: %d ---> %d, increment: %d, maxActive: %d\n",
   266  			p.current, current, increment, p.opt.MaxActive)
   267  		atomic.StoreInt32(&p.current, current)
   268  		if err != nil {
   269  			p.Unlock()
   270  			return nil, err
   271  		}
   272  	}
   273  	p.Unlock()
   274  	next := atomic.AddUint32(&p.index, 1) % uint32(current)
   275  	return p.conns[next], nil
   276  }
   277  
   278  // Close see Pool interface.
   279  func (p *pool) Close() error {
   280  	atomic.StoreInt32(&p.closed, 1)
   281  	atomic.StoreUint32(&p.index, 0)
   282  	atomic.StoreInt32(&p.current, 0)
   283  	atomic.StoreInt32(&p.ref, 0)
   284  	p.deleteFrom(0)
   285  	log.Printf("close pool success: %v\n", p.Status())
   286  	return nil
   287  }
   288  
   289  // Status see Pool interface.
   290  func (p *pool) Status() string {
   291  	return fmt.Sprintf("address:%s, index:%d, current:%d, ref:%d. option:%v",
   292  		p.address, p.index, p.current, p.ref, p.opt)
   293  }