vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/connpool/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  package connpool
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"vitess.io/vitess/go/netutil"
    28  	"vitess.io/vitess/go/pools"
    29  	"vitess.io/vitess/go/sync2"
    30  	"vitess.io/vitess/go/trace"
    31  	"vitess.io/vitess/go/vt/callerid"
    32  	"vitess.io/vitess/go/vt/dbconfigs"
    33  	"vitess.io/vitess/go/vt/dbconnpool"
    34  	"vitess.io/vitess/go/vt/log"
    35  	"vitess.io/vitess/go/vt/mysqlctl"
    36  	"vitess.io/vitess/go/vt/servenv"
    37  	"vitess.io/vitess/go/vt/vterrors"
    38  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    39  
    40  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    41  )
    42  
    43  // ErrConnPoolClosed is returned when the connection pool is closed.
    44  var ErrConnPoolClosed = vterrors.New(vtrpcpb.Code_INTERNAL, "internal error: unexpected: conn pool is closed")
    45  
    46  const (
    47  	getWithoutS = "GetWithoutSettings"
    48  	getWithS    = "GetWithSettings"
    49  )
    50  
    51  // Pool implements a custom connection pool for tabletserver.
    52  // It's similar to dbconnpool.ConnPool, but the connections it creates
    53  // come with built-in ability to kill in-flight queries. These connections
    54  // also trigger a CheckMySQL call if we fail to connect to MySQL.
    55  // Other than the connection type, ConnPool maintains an additional
    56  // pool of dba connections that are used to kill connections.
    57  type Pool struct {
    58  	env                tabletenv.Env
    59  	name               string
    60  	mu                 sync.Mutex
    61  	connections        pools.IResourcePool
    62  	capacity           int
    63  	prefillParallelism int
    64  	timeout            time.Duration
    65  	idleTimeout        time.Duration
    66  	maxLifetime        time.Duration
    67  	waiterCap          int64
    68  	waiterCount        sync2.AtomicInt64
    69  	waiterQueueFull    sync2.AtomicInt64
    70  	dbaPool            *dbconnpool.ConnectionPool
    71  	appDebugParams     dbconfigs.Connector
    72  	getConnTime        *servenv.TimingsWrapper
    73  }
    74  
    75  // NewPool creates a new Pool. The name is used
    76  // to publish stats only.
    77  func NewPool(env tabletenv.Env, name string, cfg tabletenv.ConnPoolConfig) *Pool {
    78  	idleTimeout := cfg.IdleTimeoutSeconds.Get()
    79  	maxLifetime := cfg.MaxLifetimeSeconds.Get()
    80  	cp := &Pool{
    81  		env:                env,
    82  		name:               name,
    83  		capacity:           cfg.Size,
    84  		prefillParallelism: cfg.PrefillParallelism,
    85  		timeout:            cfg.TimeoutSeconds.Get(),
    86  		idleTimeout:        idleTimeout,
    87  		maxLifetime:        maxLifetime,
    88  		waiterCap:          int64(cfg.MaxWaiters),
    89  		dbaPool:            dbconnpool.NewConnectionPool("", 1, idleTimeout, maxLifetime, 0),
    90  	}
    91  	if name == "" {
    92  		return cp
    93  	}
    94  	env.Exporter().NewGaugeFunc(name+"Capacity", "Tablet server conn pool capacity", cp.Capacity)
    95  	env.Exporter().NewGaugeFunc(name+"Available", "Tablet server conn pool available", cp.Available)
    96  	env.Exporter().NewGaugeFunc(name+"Active", "Tablet server conn pool active", cp.Active)
    97  	env.Exporter().NewGaugeFunc(name+"InUse", "Tablet server conn pool in use", cp.InUse)
    98  	env.Exporter().NewGaugeFunc(name+"MaxCap", "Tablet server conn pool max cap", cp.MaxCap)
    99  	env.Exporter().NewCounterFunc(name+"WaitCount", "Tablet server conn pool wait count", cp.WaitCount)
   100  	env.Exporter().NewCounterDurationFunc(name+"WaitTime", "Tablet server wait time", cp.WaitTime)
   101  	env.Exporter().NewGaugeDurationFunc(name+"IdleTimeout", "Tablet server idle timeout", cp.IdleTimeout)
   102  	env.Exporter().NewCounterFunc(name+"IdleClosed", "Tablet server conn pool idle closed", cp.IdleClosed)
   103  	env.Exporter().NewCounterFunc(name+"MaxLifetimeClosed", "Tablet server conn pool refresh closed", cp.MaxLifetimeClosed)
   104  	env.Exporter().NewCounterFunc(name+"Exhausted", "Number of times pool had zero available slots", cp.Exhausted)
   105  	env.Exporter().NewCounterFunc(name+"WaiterQueueFull", "Number of times the waiter queue was full", cp.waiterQueueFull.Get)
   106  	env.Exporter().NewCounterFunc(name+"Get", "Tablet server conn pool get count", cp.GetCount)
   107  	env.Exporter().NewCounterFunc(name+"GetSetting", "Tablet server conn pool get with setting count", cp.GetSettingCount)
   108  	env.Exporter().NewCounterFunc(name+"DiffSetting", "Number of times pool applied different setting", cp.DiffSettingCount)
   109  	env.Exporter().NewCounterFunc(name+"ResetSetting", "Number of times pool reset the setting", cp.ResetSettingCount)
   110  	cp.getConnTime = env.Exporter().NewTimings(name+"GetConnTime", "Tracks the amount of time it takes to get a connection", "Settings")
   111  
   112  	return cp
   113  }
   114  
   115  func (cp *Pool) pool() (p pools.IResourcePool) {
   116  	cp.mu.Lock()
   117  	p = cp.connections
   118  	cp.mu.Unlock()
   119  	return p
   120  }
   121  
   122  // Open must be called before starting to use the pool.
   123  func (cp *Pool) Open(appParams, dbaParams, appDebugParams dbconfigs.Connector) {
   124  	cp.mu.Lock()
   125  	defer cp.mu.Unlock()
   126  
   127  	if cp.prefillParallelism != 0 {
   128  		log.Infof("Opening pool: '%s'", cp.name)
   129  		defer log.Infof("Done opening pool: '%s'", cp.name)
   130  	}
   131  
   132  	f := func(ctx context.Context) (pools.Resource, error) {
   133  		return NewDBConn(ctx, cp, appParams)
   134  	}
   135  
   136  	var refreshCheck pools.RefreshCheck
   137  	if net.ParseIP(appParams.Host()) == nil {
   138  		refreshCheck = netutil.DNSTracker(appParams.Host())
   139  	}
   140  
   141  	cp.connections = pools.NewResourcePool(f, cp.capacity, cp.capacity, cp.idleTimeout, cp.maxLifetime, cp.getLogWaitCallback(), refreshCheck, mysqlctl.PoolDynamicHostnameResolution)
   142  	cp.appDebugParams = appDebugParams
   143  
   144  	cp.dbaPool.Open(dbaParams)
   145  }
   146  
   147  func (cp *Pool) getLogWaitCallback() func(time.Time) {
   148  	if cp.name == "" {
   149  		return func(start time.Time) {} // no op
   150  	}
   151  	return func(start time.Time) {
   152  		cp.env.Stats().WaitTimings.Record(cp.name+"ResourceWaitTime", start)
   153  	}
   154  }
   155  
   156  // Close will close the pool and wait for connections to be returned before
   157  // exiting.
   158  func (cp *Pool) Close() {
   159  	log.Infof("connpool - started execution of Close")
   160  	p := cp.pool()
   161  	log.Infof("connpool - found the pool")
   162  	if p == nil {
   163  		log.Infof("connpool - pool is empty")
   164  		return
   165  	}
   166  	// We should not hold the lock while calling Close
   167  	// because it waits for connections to be returned.
   168  	log.Infof("connpool - calling close on the pool")
   169  	p.Close()
   170  	log.Infof("connpool - acquiring lock")
   171  	cp.mu.Lock()
   172  	log.Infof("connpool - acquired lock")
   173  	cp.connections = nil
   174  	cp.mu.Unlock()
   175  	log.Infof("connpool - closing dbaPool")
   176  	cp.dbaPool.Close()
   177  	log.Infof("connpool - finished execution of Close")
   178  }
   179  
   180  // Get returns a connection.
   181  // You must call Recycle on DBConn once done.
   182  func (cp *Pool) Get(ctx context.Context, setting *pools.Setting) (*DBConn, error) {
   183  	span, ctx := trace.NewSpan(ctx, "Pool.Get")
   184  	defer span.Finish()
   185  
   186  	if cp.waiterCap > 0 {
   187  		waiterCount := cp.waiterCount.Add(1)
   188  		defer cp.waiterCount.Add(-1)
   189  		if waiterCount > cp.waiterCap {
   190  			cp.waiterQueueFull.Add(1)
   191  			return nil, vterrors.Errorf(vtrpcpb.Code_RESOURCE_EXHAUSTED, "pool %s waiter count exceeded", cp.name)
   192  		}
   193  	}
   194  
   195  	if cp.isCallerIDAppDebug(ctx) {
   196  		return NewDBConnNoPool(ctx, cp.appDebugParams, cp.dbaPool, setting)
   197  	}
   198  	p := cp.pool()
   199  	if p == nil {
   200  		return nil, ErrConnPoolClosed
   201  	}
   202  	span.Annotate("capacity", p.Capacity())
   203  	span.Annotate("in_use", p.InUse())
   204  	span.Annotate("available", p.Available())
   205  	span.Annotate("active", p.Active())
   206  
   207  	if cp.timeout != 0 {
   208  		var cancel context.CancelFunc
   209  		ctx, cancel = context.WithTimeout(ctx, cp.timeout)
   210  		defer cancel()
   211  	}
   212  
   213  	start := time.Now()
   214  	r, err := p.Get(ctx, setting)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  	if cp.getConnTime != nil {
   219  		if setting == nil {
   220  			cp.getConnTime.Record(getWithoutS, start)
   221  		} else {
   222  			cp.getConnTime.Record(getWithS, start)
   223  		}
   224  	}
   225  	return r.(*DBConn), nil
   226  }
   227  
   228  // Put puts a connection into the pool.
   229  func (cp *Pool) Put(conn *DBConn) {
   230  	p := cp.pool()
   231  	if p == nil {
   232  		panic(ErrConnPoolClosed)
   233  	}
   234  	if conn == nil {
   235  		p.Put(nil)
   236  	} else {
   237  		p.Put(conn)
   238  	}
   239  }
   240  
   241  // SetCapacity alters the size of the pool at runtime.
   242  func (cp *Pool) SetCapacity(capacity int) (err error) {
   243  	cp.mu.Lock()
   244  	defer cp.mu.Unlock()
   245  	if cp.connections != nil {
   246  		err = cp.connections.SetCapacity(capacity)
   247  		if err != nil {
   248  			return err
   249  		}
   250  	}
   251  	cp.capacity = capacity
   252  	return nil
   253  }
   254  
   255  // SetIdleTimeout sets the idleTimeout on the pool.
   256  func (cp *Pool) SetIdleTimeout(idleTimeout time.Duration) {
   257  	cp.mu.Lock()
   258  	defer cp.mu.Unlock()
   259  	if cp.connections != nil {
   260  		cp.connections.SetIdleTimeout(idleTimeout)
   261  	}
   262  	cp.dbaPool.SetIdleTimeout(idleTimeout)
   263  	cp.idleTimeout = idleTimeout
   264  }
   265  
   266  // StatsJSON returns the pool stats as a JSON object.
   267  func (cp *Pool) StatsJSON() string {
   268  	p := cp.pool()
   269  	if p == nil {
   270  		return "{}"
   271  	}
   272  	res := p.StatsJSON()
   273  	closingBraceIndex := strings.LastIndex(res, "}")
   274  	if closingBraceIndex == -1 { // unexpected...
   275  		return res
   276  	}
   277  	return fmt.Sprintf(`%s, "WaiterQueueFull": %v}`, res[:closingBraceIndex], cp.waiterQueueFull.Get())
   278  }
   279  
   280  // Capacity returns the pool capacity.
   281  func (cp *Pool) Capacity() int64 {
   282  	p := cp.pool()
   283  	if p == nil {
   284  		return 0
   285  	}
   286  	return p.Capacity()
   287  }
   288  
   289  // Available returns the number of available connections in the pool
   290  func (cp *Pool) Available() int64 {
   291  	p := cp.pool()
   292  	if p == nil {
   293  		return 0
   294  	}
   295  	return p.Available()
   296  }
   297  
   298  // Active returns the number of active connections in the pool
   299  func (cp *Pool) Active() int64 {
   300  	p := cp.pool()
   301  	if p == nil {
   302  		return 0
   303  	}
   304  	return p.Active()
   305  }
   306  
   307  // InUse returns the number of in-use connections in the pool
   308  func (cp *Pool) InUse() int64 {
   309  	p := cp.pool()
   310  	if p == nil {
   311  		return 0
   312  	}
   313  	return p.InUse()
   314  }
   315  
   316  // MaxCap returns the maximum size of the pool
   317  func (cp *Pool) MaxCap() int64 {
   318  	p := cp.pool()
   319  	if p == nil {
   320  		return 0
   321  	}
   322  	return p.MaxCap()
   323  }
   324  
   325  // WaitCount returns how many clients are waiting for a connection
   326  func (cp *Pool) WaitCount() int64 {
   327  	p := cp.pool()
   328  	if p == nil {
   329  		return 0
   330  	}
   331  	return p.WaitCount()
   332  }
   333  
   334  // WaitTime return the pool WaitTime.
   335  func (cp *Pool) WaitTime() time.Duration {
   336  	p := cp.pool()
   337  	if p == nil {
   338  		return 0
   339  	}
   340  	return p.WaitTime()
   341  }
   342  
   343  // IdleTimeout returns the idle timeout for the pool.
   344  func (cp *Pool) IdleTimeout() time.Duration {
   345  	p := cp.pool()
   346  	if p == nil {
   347  		return 0
   348  	}
   349  	return p.IdleTimeout()
   350  }
   351  
   352  // IdleClosed returns the number of closed connections for the pool.
   353  func (cp *Pool) IdleClosed() int64 {
   354  	p := cp.pool()
   355  	if p == nil {
   356  		return 0
   357  	}
   358  	return p.IdleClosed()
   359  }
   360  
   361  // MaxLifetimeClosed returns the number of connections closed to refresh timeout for the pool.
   362  func (cp *Pool) MaxLifetimeClosed() int64 {
   363  	p := cp.pool()
   364  	if p == nil {
   365  		return 0
   366  	}
   367  	return p.MaxLifetimeClosed()
   368  }
   369  
   370  // Exhausted returns the number of times available went to zero for the pool.
   371  func (cp *Pool) Exhausted() int64 {
   372  	p := cp.pool()
   373  	if p == nil {
   374  		return 0
   375  	}
   376  	return p.Exhausted()
   377  }
   378  
   379  // GetCount returns the number of times get was called
   380  func (cp *Pool) GetCount() int64 {
   381  	p := cp.pool()
   382  	if p == nil {
   383  		return 0
   384  	}
   385  	return p.GetCount()
   386  }
   387  
   388  // GetSettingCount returns the number of times getWithSettings was called
   389  func (cp *Pool) GetSettingCount() int64 {
   390  	p := cp.pool()
   391  	if p == nil {
   392  		return 0
   393  	}
   394  	return p.GetSettingCount()
   395  }
   396  
   397  // DiffSettingCount returns the number of times different settings were applied on the resource.
   398  func (cp *Pool) DiffSettingCount() int64 {
   399  	p := cp.pool()
   400  	if p == nil {
   401  		return 0
   402  	}
   403  	return p.DiffSettingCount()
   404  }
   405  
   406  // ResetSettingCount returns the number of times settings were reset on the resource.
   407  func (cp *Pool) ResetSettingCount() int64 {
   408  	p := cp.pool()
   409  	if p == nil {
   410  		return 0
   411  	}
   412  	return p.ResetSettingCount()
   413  }
   414  
   415  func (cp *Pool) isCallerIDAppDebug(ctx context.Context) bool {
   416  	params, err := cp.appDebugParams.MysqlParams()
   417  	if err != nil {
   418  		return false
   419  	}
   420  	if params == nil || params.Uname == "" {
   421  		return false
   422  	}
   423  	callerID := callerid.ImmediateCallerIDFromContext(ctx)
   424  	return callerID != nil && callerID.Username == params.Uname
   425  }