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 }