vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/stateful_connection_pool.go (about)

     1  /*
     2  Copyright 2020 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 tabletserver
    18  
    19  import (
    20  	"context"
    21  	"time"
    22  
    23  	"vitess.io/vitess/go/pools"
    24  	"vitess.io/vitess/go/sync2"
    25  	"vitess.io/vitess/go/vt/dbconfigs"
    26  	"vitess.io/vitess/go/vt/log"
    27  	"vitess.io/vitess/go/vt/vttablet/tabletserver/connpool"
    28  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv"
    29  	"vitess.io/vitess/go/vt/vttablet/tabletserver/tx"
    30  
    31  	querypb "vitess.io/vitess/go/vt/proto/query"
    32  )
    33  
    34  const (
    35  	scpClosed = int64(iota)
    36  	scpOpen
    37  	scpKillingNonTx
    38  	scpKillingAll
    39  )
    40  
    41  // StatefulConnectionPool keeps track of currently and future active connections
    42  // it's used whenever the session has some state that requires a dedicated connection
    43  type StatefulConnectionPool struct {
    44  	env tabletenv.Env
    45  
    46  	state sync2.AtomicInt64
    47  
    48  	// conns is the 'regular' pool. By default, connections
    49  	// are pulled from here for starting transactions.
    50  	conns *connpool.Pool
    51  
    52  	// foundRowsPool is the alternate pool that creates
    53  	// connections with CLIENT_FOUND_ROWS flag set. A separate
    54  	// pool is needed because this option can only be set at
    55  	// connection time.
    56  	foundRowsPool *connpool.Pool
    57  	active        *pools.Numbered
    58  	lastID        sync2.AtomicInt64
    59  }
    60  
    61  // NewStatefulConnPool creates an ActivePool
    62  func NewStatefulConnPool(env tabletenv.Env) *StatefulConnectionPool {
    63  	config := env.Config()
    64  
    65  	return &StatefulConnectionPool{
    66  		env:           env,
    67  		conns:         connpool.NewPool(env, "TransactionPool", config.TxPool),
    68  		foundRowsPool: connpool.NewPool(env, "FoundRowsPool", config.TxPool),
    69  		active:        pools.NewNumbered(),
    70  		lastID:        sync2.NewAtomicInt64(time.Now().UnixNano()),
    71  	}
    72  }
    73  
    74  // Open makes the TxPool operational. This also starts the transaction killer
    75  // that will kill long-running transactions.
    76  func (sf *StatefulConnectionPool) Open(appParams, dbaParams, appDebugParams dbconfigs.Connector) {
    77  	log.Infof("Starting transaction id: %d", sf.lastID)
    78  	sf.conns.Open(appParams, dbaParams, appDebugParams)
    79  	foundRowsParam, _ := appParams.MysqlParams()
    80  	foundRowsParam.EnableClientFoundRows()
    81  	appParams = dbconfigs.New(foundRowsParam)
    82  	sf.foundRowsPool.Open(appParams, dbaParams, appDebugParams)
    83  	sf.state.Set(scpOpen)
    84  }
    85  
    86  // Close closes the TxPool. A closed pool can be reopened.
    87  func (sf *StatefulConnectionPool) Close() {
    88  	for _, v := range sf.active.GetByFilter("for closing", func(_ any) bool { return true }) {
    89  		conn := v.(*StatefulConnection)
    90  		thing := "connection"
    91  		if conn.IsInTransaction() {
    92  			thing = "transaction"
    93  		}
    94  		log.Warningf("killing %s for shutdown: %s", thing, conn.String(sf.env.Config().SanitizeLogMessages))
    95  		sf.env.Stats().InternalErrors.Add("StrayTransactions", 1)
    96  		conn.Close()
    97  		conn.Releasef("pool closed")
    98  	}
    99  	sf.conns.Close()
   100  	sf.foundRowsPool.Close()
   101  	sf.state.Set(scpClosed)
   102  }
   103  
   104  // ShutdownNonTx enters the state where all non-transactional connections are killed.
   105  // InUse connections will be killed as they are returned.
   106  func (sf *StatefulConnectionPool) ShutdownNonTx() {
   107  	sf.state.Set(scpKillingNonTx)
   108  	conns := mapToTxConn(sf.active.GetByFilter("kill non-tx", func(sc any) bool {
   109  		return !sc.(*StatefulConnection).IsInTransaction()
   110  	}))
   111  	for _, sc := range conns {
   112  		sc.Releasef("kill non-tx")
   113  	}
   114  }
   115  
   116  // ShutdownAll enters the state where all connections are to be killed.
   117  // It returns all connections that are not in use. They must be rolled back
   118  // by the caller (TxPool). InUse connections will be killed as they are returned.
   119  func (sf *StatefulConnectionPool) ShutdownAll() []*StatefulConnection {
   120  	sf.state.Set(scpKillingAll)
   121  	return mapToTxConn(sf.active.GetByFilter("kill non-tx", func(sc any) bool {
   122  		return true
   123  	}))
   124  }
   125  
   126  // AdjustLastID adjusts the last transaction id to be at least
   127  // as large as the input value. This will ensure that there are
   128  // no dtid collisions with future transactions.
   129  func (sf *StatefulConnectionPool) AdjustLastID(id int64) {
   130  	if current := sf.lastID.Get(); current < id {
   131  		log.Infof("Adjusting transaction id to: %d", id)
   132  		sf.lastID.Set(id)
   133  	}
   134  }
   135  
   136  // GetElapsedTimeout returns sessions older than the timeout stored on the
   137  // connection. Does not return any connections that are in use.
   138  // TODO(sougou): deprecate.
   139  func (sf *StatefulConnectionPool) GetElapsedTimeout(purpose string) []*StatefulConnection {
   140  	return mapToTxConn(sf.active.GetByFilter(purpose, func(val any) bool {
   141  		sc := val.(*StatefulConnection)
   142  		return sc.ElapsedTimeout()
   143  	}))
   144  }
   145  
   146  func mapToTxConn(vals []any) []*StatefulConnection {
   147  	result := make([]*StatefulConnection, len(vals))
   148  	for i, el := range vals {
   149  		result[i] = el.(*StatefulConnection)
   150  	}
   151  	return result
   152  }
   153  
   154  // WaitForEmpty returns as soon as the pool becomes empty
   155  func (sf *StatefulConnectionPool) WaitForEmpty() {
   156  	sf.active.WaitForEmpty()
   157  }
   158  
   159  // GetAndLock locks the connection for use. It accepts a purpose as a string.
   160  // If it cannot be found, it returns a "not found" error. If in use,
   161  // it returns a "in use: purpose" error.
   162  func (sf *StatefulConnectionPool) GetAndLock(id int64, reason string) (*StatefulConnection, error) {
   163  	conn, err := sf.active.Get(id, reason)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	return conn.(*StatefulConnection), nil
   168  }
   169  
   170  // NewConn creates a new StatefulConnection. It will be created from either the normal pool or
   171  // the found_rows pool, depending on the options provided
   172  func (sf *StatefulConnectionPool) NewConn(ctx context.Context, options *querypb.ExecuteOptions, setting *pools.Setting) (*StatefulConnection, error) {
   173  	var conn *connpool.DBConn
   174  	var err error
   175  
   176  	if options.GetClientFoundRows() {
   177  		conn, err = sf.foundRowsPool.Get(ctx, setting)
   178  	} else {
   179  		conn, err = sf.conns.Get(ctx, setting)
   180  	}
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	connID := sf.lastID.Add(1)
   186  	sfConn := &StatefulConnection{
   187  		dbConn:         conn,
   188  		ConnID:         connID,
   189  		pool:           sf,
   190  		env:            sf.env,
   191  		enforceTimeout: options.GetWorkload() != querypb.ExecuteOptions_DBA,
   192  	}
   193  	// This will set both the timeout and initialize the expiryTime.
   194  	sfConn.SetTimeout(sf.env.Config().TxTimeoutForWorkload(options.GetWorkload()))
   195  
   196  	err = sf.active.Register(sfConn.ConnID, sfConn)
   197  	if err != nil {
   198  		sfConn.Release(tx.ConnInitFail)
   199  		return nil, err
   200  	}
   201  
   202  	return sf.GetAndLock(sfConn.ConnID, "new connection")
   203  }
   204  
   205  // ForAllTxProperties executes a function an every connection that has a not-nil TxProperties
   206  func (sf *StatefulConnectionPool) ForAllTxProperties(f func(*tx.Properties)) {
   207  	for _, connection := range mapToTxConn(sf.active.GetAll()) {
   208  		props := connection.txProps
   209  		if props != nil {
   210  			f(props)
   211  		}
   212  	}
   213  }
   214  
   215  // Unregister forgets the specified connection.  If the connection is not present, it's ignored.
   216  func (sf *StatefulConnectionPool) unregister(id tx.ConnID, reason string) {
   217  	sf.active.Unregister(id, reason)
   218  }
   219  
   220  // markAsNotInUse marks the connection as not in use at the moment
   221  func (sf *StatefulConnectionPool) markAsNotInUse(sc *StatefulConnection, updateTime bool) {
   222  	switch sf.state.Get() {
   223  	case scpKillingNonTx:
   224  		if !sc.IsInTransaction() {
   225  			sc.Releasef("kill non-tx")
   226  			return
   227  		}
   228  	case scpKillingAll:
   229  		if sc.IsInTransaction() {
   230  			sc.Close()
   231  		}
   232  		sc.Releasef("kill all")
   233  		return
   234  	}
   235  	if updateTime {
   236  		sc.resetExpiryTime()
   237  	}
   238  	sf.active.Put(sc.ConnID)
   239  }
   240  
   241  // Capacity returns the pool capacity.
   242  func (sf *StatefulConnectionPool) Capacity() int {
   243  	return int(sf.conns.Capacity())
   244  }
   245  
   246  // renewConn unregister and registers with new id.
   247  func (sf *StatefulConnectionPool) renewConn(sc *StatefulConnection) error {
   248  	sf.active.Unregister(sc.ConnID, "renew existing connection")
   249  	sc.ConnID = sf.lastID.Add(1)
   250  	sc.resetExpiryTime()
   251  	return sf.active.Register(sc.ConnID, sc)
   252  }