vitess.io/vitess@v0.16.2/go/vt/dbconnpool/connection_pool.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  /*
    18  Package dbconnpool exposes a single DBConnection object
    19  with wrapped access to a single DB connection, and a ConnectionPool
    20  object to pool these DBConnections.
    21  */
    22  package dbconnpool
    23  
    24  import (
    25  	"errors"
    26  	"net"
    27  	"sync"
    28  	"time"
    29  
    30  	"vitess.io/vitess/go/netutil"
    31  
    32  	"context"
    33  
    34  	"vitess.io/vitess/go/pools"
    35  	"vitess.io/vitess/go/stats"
    36  	"vitess.io/vitess/go/vt/dbconfigs"
    37  )
    38  
    39  var (
    40  	// ErrConnPoolClosed is returned if the connection pool is closed.
    41  	ErrConnPoolClosed = errors.New("connection pool is closed")
    42  	// usedNames is for preventing expvar from panicking. Tests
    43  	// create pool objects multiple time. If a name was previously
    44  	// used, expvar initialization is skipped.
    45  	// TODO(sougou): Find a way to still crash if this happened
    46  	// through non-test code.
    47  	usedNames = make(map[string]bool)
    48  )
    49  
    50  // ConnectionPool re-exposes ResourcePool as a pool of
    51  // PooledDBConnection objects.
    52  type ConnectionPool struct {
    53  	mu                  sync.Mutex
    54  	connections         pools.IResourcePool
    55  	capacity            int
    56  	idleTimeout         time.Duration
    57  	maxLifetime         time.Duration
    58  	resolutionFrequency time.Duration
    59  
    60  	// info is set at Open() time
    61  	info dbconfigs.Connector
    62  	name string
    63  }
    64  
    65  // NewConnectionPool creates a new ConnectionPool. The name is used
    66  // to publish stats only.
    67  func NewConnectionPool(name string, capacity int, idleTimeout time.Duration, maxLifetime time.Duration, dnsResolutionFrequency time.Duration) *ConnectionPool {
    68  	cp := &ConnectionPool{name: name, capacity: capacity, idleTimeout: idleTimeout, maxLifetime: maxLifetime, resolutionFrequency: dnsResolutionFrequency}
    69  	if name == "" || usedNames[name] {
    70  		return cp
    71  	}
    72  	usedNames[name] = true
    73  	stats.NewGaugeFunc(name+"Capacity", "Connection pool capacity", cp.Capacity)
    74  	stats.NewGaugeFunc(name+"Available", "Connection pool available", cp.Available)
    75  	stats.NewGaugeFunc(name+"Active", "Connection pool active", cp.Active)
    76  	stats.NewGaugeFunc(name+"InUse", "Connection pool in-use", cp.InUse)
    77  	stats.NewGaugeFunc(name+"MaxCap", "Connection pool max cap", cp.MaxCap)
    78  	stats.NewCounterFunc(name+"WaitCount", "Connection pool wait count", cp.WaitCount)
    79  	stats.NewCounterDurationFunc(name+"WaitTime", "Connection pool wait time", cp.WaitTime)
    80  	stats.NewGaugeDurationFunc(name+"IdleTimeout", "Connection pool idle timeout", cp.IdleTimeout)
    81  	stats.NewGaugeFunc(name+"IdleClosed", "Connection pool idle closed", cp.IdleClosed)
    82  	stats.NewGaugeFunc(name+"MaxLifetimeClosed", "Connection pool refresh closed", cp.MaxLifetimeClosed)
    83  	stats.NewCounterFunc(name+"Exhausted", "Number of times pool had zero available slots", cp.Exhausted)
    84  	return cp
    85  }
    86  
    87  func (cp *ConnectionPool) pool() (p pools.IResourcePool) {
    88  	cp.mu.Lock()
    89  	p = cp.connections
    90  	cp.mu.Unlock()
    91  	return p
    92  }
    93  
    94  // Open must be called before starting to use the pool.
    95  //
    96  // For instance:
    97  // pool := dbconnpool.NewConnectionPool("name", 10, 30*time.Second)
    98  // pool.Open(info)
    99  // ...
   100  // conn, err := pool.Get()
   101  // ...
   102  func (cp *ConnectionPool) Open(info dbconfigs.Connector) {
   103  	var refreshCheck pools.RefreshCheck
   104  	if net.ParseIP(info.Host()) == nil {
   105  		refreshCheck = netutil.DNSTracker(info.Host())
   106  	} else {
   107  		refreshCheck = nil
   108  	}
   109  	cp.mu.Lock()
   110  	defer cp.mu.Unlock()
   111  	cp.info = info
   112  	cp.connections = pools.NewResourcePool(cp.connect, cp.capacity, cp.capacity, cp.idleTimeout, cp.maxLifetime, nil, refreshCheck, cp.resolutionFrequency)
   113  }
   114  
   115  // connect is used by the resource pool to create a new Resource.
   116  func (cp *ConnectionPool) connect(ctx context.Context) (pools.Resource, error) {
   117  	c, err := NewDBConnection(ctx, cp.info)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	return &PooledDBConnection{
   122  		DBConnection: c,
   123  		timeCreated:  time.Now(),
   124  		pool:         cp,
   125  	}, nil
   126  }
   127  
   128  // Close will close the pool and wait for connections to be returned before
   129  // exiting.
   130  func (cp *ConnectionPool) Close() {
   131  	p := cp.pool()
   132  	if p == nil {
   133  		return
   134  	}
   135  	// We should not hold the lock while calling Close
   136  	// because it waits for connections to be returned.
   137  	p.Close()
   138  	cp.mu.Lock()
   139  	cp.connections = nil
   140  	cp.mu.Unlock()
   141  }
   142  
   143  // Get returns a connection.
   144  // You must call Recycle on the PooledDBConnection once done.
   145  func (cp *ConnectionPool) Get(ctx context.Context) (*PooledDBConnection, error) {
   146  	p := cp.pool()
   147  	if p == nil {
   148  		return nil, ErrConnPoolClosed
   149  	}
   150  	r, err := p.Get(ctx, nil)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	return r.(*PooledDBConnection), nil
   156  }
   157  
   158  // Put puts a connection into the pool.
   159  func (cp *ConnectionPool) Put(conn *PooledDBConnection) {
   160  	p := cp.pool()
   161  	if p == nil {
   162  		panic(ErrConnPoolClosed)
   163  	}
   164  	if conn == nil {
   165  		// conn has a type, if we just Put(conn), we end up
   166  		// putting an interface with a nil value, that is not
   167  		// equal to a nil value. So just put a plain nil.
   168  		p.Put(nil)
   169  		return
   170  	}
   171  	p.Put(conn)
   172  }
   173  
   174  // SetCapacity alters the size of the pool at runtime.
   175  func (cp *ConnectionPool) SetCapacity(capacity int) (err error) {
   176  	cp.mu.Lock()
   177  	defer cp.mu.Unlock()
   178  	if cp.connections != nil {
   179  		err = cp.connections.SetCapacity(capacity)
   180  		if err != nil {
   181  			return err
   182  		}
   183  	}
   184  	cp.capacity = capacity
   185  	return nil
   186  }
   187  
   188  // SetIdleTimeout sets the idleTimeout on the pool.
   189  func (cp *ConnectionPool) SetIdleTimeout(idleTimeout time.Duration) {
   190  	cp.mu.Lock()
   191  	defer cp.mu.Unlock()
   192  	if cp.connections != nil {
   193  		cp.connections.SetIdleTimeout(idleTimeout)
   194  	}
   195  	cp.idleTimeout = idleTimeout
   196  }
   197  
   198  // StatsJSON returns the pool stats as a JSOn object.
   199  func (cp *ConnectionPool) StatsJSON() string {
   200  	p := cp.pool()
   201  	if p == nil {
   202  		return "{}"
   203  	}
   204  	return p.StatsJSON()
   205  }
   206  
   207  // Capacity returns the pool capacity.
   208  func (cp *ConnectionPool) Capacity() int64 {
   209  	p := cp.pool()
   210  	if p == nil {
   211  		return 0
   212  	}
   213  	return p.Capacity()
   214  }
   215  
   216  // Available returns the number of available connections in the pool
   217  func (cp *ConnectionPool) Available() int64 {
   218  	p := cp.pool()
   219  	if p == nil {
   220  		return 0
   221  	}
   222  	return p.Available()
   223  }
   224  
   225  // Active returns the number of active connections in the pool
   226  func (cp *ConnectionPool) Active() int64 {
   227  	p := cp.pool()
   228  	if p == nil {
   229  		return 0
   230  	}
   231  	return p.Active()
   232  }
   233  
   234  // InUse returns the number of in-use connections in the pool
   235  func (cp *ConnectionPool) InUse() int64 {
   236  	p := cp.pool()
   237  	if p == nil {
   238  		return 0
   239  	}
   240  	return p.InUse()
   241  }
   242  
   243  // MaxCap returns the maximum size of the pool
   244  func (cp *ConnectionPool) MaxCap() int64 {
   245  	p := cp.pool()
   246  	if p == nil {
   247  		return 0
   248  	}
   249  	return p.MaxCap()
   250  }
   251  
   252  // WaitCount returns how many clients are waiting for a connection
   253  func (cp *ConnectionPool) WaitCount() int64 {
   254  	p := cp.pool()
   255  	if p == nil {
   256  		return 0
   257  	}
   258  	return p.WaitCount()
   259  }
   260  
   261  // WaitTime return the pool WaitTime.
   262  func (cp *ConnectionPool) WaitTime() time.Duration {
   263  	p := cp.pool()
   264  	if p == nil {
   265  		return 0
   266  	}
   267  	return p.WaitTime()
   268  }
   269  
   270  // IdleTimeout returns the idle timeout for the pool.
   271  func (cp *ConnectionPool) IdleTimeout() time.Duration {
   272  	p := cp.pool()
   273  	if p == nil {
   274  		return 0
   275  	}
   276  	return p.IdleTimeout()
   277  }
   278  
   279  // IdleClosed returns the number of connections closed due to idle timeout for the pool.
   280  func (cp *ConnectionPool) IdleClosed() int64 {
   281  	p := cp.pool()
   282  	if p == nil {
   283  		return 0
   284  	}
   285  	return p.IdleClosed()
   286  }
   287  
   288  // MaxLifetimeClosed returns the number of connections closed due to refresh timeout for the pool.
   289  func (cp *ConnectionPool) MaxLifetimeClosed() int64 {
   290  	p := cp.pool()
   291  	if p == nil {
   292  		return 0
   293  	}
   294  	return p.MaxLifetimeClosed()
   295  }
   296  
   297  // Exhausted returns the number of times available went to zero for the pool.
   298  func (cp *ConnectionPool) Exhausted() int64 {
   299  	p := cp.pool()
   300  	if p == nil {
   301  		return 0
   302  	}
   303  	return p.Exhausted()
   304  }