github.com/XiaoMi/Gaea@v1.2.5/backend/connection_pool.go (about)

     1  // Copyright 2019 The Gaea Authors. 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  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package backend
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/XiaoMi/Gaea/mysql"
    24  	"github.com/XiaoMi/Gaea/util"
    25  )
    26  
    27  const (
    28  	getConnTimeout = 2 * time.Second
    29  	PING_PEROID    = 5 * time.Second
    30  )
    31  
    32  var (
    33  	// ErrConnectionPoolClosed means pool closed error
    34  	ErrConnectionPoolClosed = errors.New("connection pool is closed")
    35  	// DefaultCapacity default capacity of connection pool
    36  	DefaultCapacity = 64
    37  )
    38  
    39  // connectionPoolImpl means connection pool with specific addr
    40  type connectionPoolImpl struct {
    41  	mu          sync.RWMutex
    42  	connections *util.ResourcePool
    43  
    44  	addr     string
    45  	user     string
    46  	password string
    47  	db       string
    48  
    49  	charset     string
    50  	collationID mysql.CollationID
    51  
    52  	capacity    int // capacity of pool
    53  	maxCapacity int // max capacity of pool
    54  	idleTimeout time.Duration
    55  }
    56  
    57  // NewConnectionPool create connection pool
    58  func NewConnectionPool(addr, user, password, db string, capacity, maxCapacity int, idleTimeout time.Duration, charset string, collationID mysql.CollationID) ConnectionPool {
    59  	cp := &connectionPoolImpl{addr: addr, user: user, password: password, db: db, capacity: capacity, maxCapacity: maxCapacity, idleTimeout: idleTimeout, charset: charset, collationID: collationID}
    60  	return cp
    61  }
    62  
    63  func (cp *connectionPoolImpl) pool() (p *util.ResourcePool) {
    64  	cp.mu.Lock()
    65  	p = cp.connections
    66  	cp.mu.Unlock()
    67  	return p
    68  }
    69  
    70  // Open open connection pool without error, should be called before use the pool
    71  func (cp *connectionPoolImpl) Open() {
    72  	if cp.capacity == 0 {
    73  		cp.capacity = DefaultCapacity
    74  	}
    75  
    76  	if cp.maxCapacity == 0 {
    77  		cp.maxCapacity = cp.capacity
    78  	}
    79  	cp.mu.Lock()
    80  	defer cp.mu.Unlock()
    81  	cp.connections = util.NewResourcePool(cp.connect, cp.capacity, cp.maxCapacity, cp.idleTimeout)
    82  	return
    83  }
    84  
    85  // connect is used by the resource pool to create new resource.It's factory method
    86  func (cp *connectionPoolImpl) connect() (util.Resource, error) {
    87  	c, err := NewDirectConnection(cp.addr, cp.user, cp.password, cp.db, cp.charset, cp.collationID)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  	return &pooledConnectImpl{directConnection: c, pool: cp}, nil
    92  }
    93  
    94  // Addr return addr of connection pool
    95  func (cp *connectionPoolImpl) Addr() string {
    96  	return cp.addr
    97  }
    98  
    99  // Close close connection pool
   100  func (cp *connectionPoolImpl) Close() {
   101  	p := cp.pool()
   102  	if p == nil {
   103  		return
   104  	}
   105  	p.Close()
   106  	cp.mu.Lock()
   107  	cp.connections = nil
   108  	cp.mu.Unlock()
   109  	return
   110  }
   111  
   112  // tryReuse reset params of connection before reuse
   113  func (cp *connectionPoolImpl) tryReuse(pc *pooledConnectImpl) error {
   114  	return pc.directConnection.ResetConnection()
   115  }
   116  
   117  // Get return a connection, you should call PooledConnect's Recycle once done
   118  func (cp *connectionPoolImpl) Get(ctx context.Context) (pc PooledConnect, err error) {
   119  	p := cp.pool()
   120  	if p == nil {
   121  		return nil, ErrConnectionPoolClosed
   122  	}
   123  
   124  	getCtx, cancel := context.WithTimeout(ctx, getConnTimeout)
   125  	defer cancel()
   126  	r, err := p.Get(getCtx)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	pc = r.(*pooledConnectImpl)
   132  	
   133  	//do ping when over the ping time. if error happen, create new one
   134  	if !pc.GetReturnTime().IsZero() && time.Until(pc.GetReturnTime().Add(PING_PEROID)) < 0 { 
   135  		if err = pc.Ping(); err != nil {
   136  			err = pc.Reconnect()
   137  		}
   138  	}
   139  	return pc, err
   140  }
   141  
   142  // Put recycle a connection into the pool
   143  func (cp *connectionPoolImpl) Put(pc PooledConnect) {
   144  	p := cp.pool()
   145  	if p == nil {
   146  		panic(ErrConnectionPoolClosed)
   147  	}
   148  
   149  	if pc == nil {
   150  		p.Put(nil)
   151  	} else if err := cp.tryReuse(pc.(*pooledConnectImpl)); err != nil {
   152  		pc.Close()
   153  		p.Put(nil)
   154  	} else {
   155  		p.Put(pc)
   156  	}
   157  }
   158  
   159  // SetCapacity alert the size of the pool at runtime
   160  func (cp *connectionPoolImpl) SetCapacity(capacity int) (err error) {
   161  	cp.mu.Lock()
   162  	defer cp.mu.Unlock()
   163  	if cp.connections != nil {
   164  		err = cp.connections.SetCapacity(capacity)
   165  		if err != nil {
   166  			return err
   167  		}
   168  	}
   169  	cp.capacity = capacity
   170  	return nil
   171  }
   172  
   173  // SetIdleTimeout set the idleTimeout of the pool
   174  func (cp *connectionPoolImpl) SetIdleTimeout(idleTimeout time.Duration) {
   175  	cp.mu.Lock()
   176  	defer cp.mu.Unlock()
   177  	if cp.connections != nil {
   178  		cp.connections.SetIdleTimeout(idleTimeout)
   179  	}
   180  	cp.idleTimeout = idleTimeout
   181  }
   182  
   183  // StatsJSON return the pool stats as JSON object.
   184  func (cp *connectionPoolImpl) StatsJSON() string {
   185  	p := cp.pool()
   186  	if p == nil {
   187  		return "{}"
   188  	}
   189  	return p.StatsJSON()
   190  }
   191  
   192  // Capacity return the pool capacity
   193  func (cp *connectionPoolImpl) Capacity() int64 {
   194  	p := cp.pool()
   195  	if p == nil {
   196  		return 0
   197  	}
   198  	return p.Capacity()
   199  }
   200  
   201  // Available returns the number of available connections in the pool
   202  func (cp *connectionPoolImpl) Available() int64 {
   203  	p := cp.pool()
   204  	if p == nil {
   205  		return 0
   206  	}
   207  	return p.Available()
   208  }
   209  
   210  // Active returns the number of active connections in the pool
   211  func (cp *connectionPoolImpl) Active() int64 {
   212  	p := cp.pool()
   213  	if p == nil {
   214  		return 0
   215  	}
   216  	return p.Active()
   217  }
   218  
   219  // InUse returns the number of in-use connections in the pool
   220  func (cp *connectionPoolImpl) InUse() int64 {
   221  	p := cp.pool()
   222  	if p == nil {
   223  		return 0
   224  	}
   225  	return p.InUse()
   226  }
   227  
   228  // MaxCap returns the maximum size of the pool
   229  func (cp *connectionPoolImpl) MaxCap() int64 {
   230  	p := cp.pool()
   231  	if p == nil {
   232  		return 0
   233  	}
   234  	return p.MaxCap()
   235  }
   236  
   237  // WaitCount returns how many clients are waitting for a connection
   238  func (cp *connectionPoolImpl) WaitCount() int64 {
   239  	p := cp.pool()
   240  	if p == nil {
   241  		return 0
   242  	}
   243  	return p.WaitCount()
   244  }
   245  
   246  // WaitTime returns the time wait for a connection
   247  func (cp *connectionPoolImpl) WaitTime() time.Duration {
   248  	p := cp.pool()
   249  	if p == nil {
   250  		return 0
   251  	}
   252  	return p.WaitTime()
   253  }
   254  
   255  // IdleTimeout returns the idle timeout for the pool
   256  func (cp *connectionPoolImpl) IdleTimeout() time.Duration {
   257  	p := cp.pool()
   258  	if p == nil {
   259  		return 0
   260  	}
   261  	return p.IdleTimeout()
   262  }
   263  
   264  // IdleClosed return the number of closed connections for the pool
   265  func (cp *connectionPoolImpl) IdleClosed() int64 {
   266  	p := cp.pool()
   267  	if p == nil {
   268  		return 0
   269  	}
   270  	return p.IdleClosed()
   271  }