vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/tx_executor.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 tabletserver 18 19 import ( 20 "context" 21 "time" 22 23 "vitess.io/vitess/go/vt/vttablet/tabletserver/tx" 24 25 "vitess.io/vitess/go/trace" 26 "vitess.io/vitess/go/vt/log" 27 28 querypb "vitess.io/vitess/go/vt/proto/query" 29 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 30 "vitess.io/vitess/go/vt/vterrors" 31 "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" 32 ) 33 34 // TxExecutor is used for executing a transactional request. 35 // TODO: merge this with tx_engine 36 type TxExecutor struct { 37 // TODO(sougou): Parameterize this. 38 ctx context.Context 39 logStats *tabletenv.LogStats 40 te *TxEngine 41 } 42 43 // Prepare performs a prepare on a connection including the redo log work. 44 // If there is any failure, an error is returned. No cleanup is performed. 45 // A subsequent call to RollbackPrepared, which is required by the 2PC 46 // protocol, will perform all the cleanup. 47 func (txe *TxExecutor) Prepare(transactionID int64, dtid string) error { 48 if !txe.te.twopcEnabled { 49 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") 50 } 51 defer txe.te.env.Stats().QueryTimings.Record("PREPARE", time.Now()) 52 txe.logStats.TransactionID = transactionID 53 54 conn, err := txe.te.txPool.GetAndLock(transactionID, "for prepare") 55 if err != nil { 56 return err 57 } 58 59 // If no queries were executed, we just rollback. 60 if len(conn.TxProperties().Queries) == 0 { 61 conn.Release(tx.TxRollback) 62 return nil 63 } 64 65 err = txe.te.preparedPool.Put(conn, dtid) 66 if err != nil { 67 txe.te.txPool.RollbackAndRelease(txe.ctx, conn) 68 return vterrors.Errorf(vtrpcpb.Code_RESOURCE_EXHAUSTED, "prepare failed for transaction %d: %v", transactionID, err) 69 } 70 71 return txe.inTransaction(func(localConn *StatefulConnection) error { 72 return txe.te.twoPC.SaveRedo(txe.ctx, localConn, dtid, conn.TxProperties().Queries) 73 }) 74 75 } 76 77 // CommitPrepared commits a prepared transaction. If the operation 78 // fails, an error counter is incremented and the transaction is 79 // marked as failed in the redo log. 80 func (txe *TxExecutor) CommitPrepared(dtid string) error { 81 if !txe.te.twopcEnabled { 82 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") 83 } 84 defer txe.te.env.Stats().QueryTimings.Record("COMMIT_PREPARED", time.Now()) 85 conn, err := txe.te.preparedPool.FetchForCommit(dtid) 86 if err != nil { 87 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "cannot commit dtid %s, state: %v", dtid, err) 88 } 89 if conn == nil { 90 return nil 91 } 92 // We have to use a context that will never give up, 93 // even if the original context expires. 94 ctx := trace.CopySpan(context.Background(), txe.ctx) 95 defer txe.te.txPool.RollbackAndRelease(ctx, conn) 96 err = txe.te.twoPC.DeleteRedo(ctx, conn, dtid) 97 if err != nil { 98 txe.markFailed(ctx, dtid) 99 return err 100 } 101 _, err = txe.te.txPool.Commit(ctx, conn) 102 if err != nil { 103 txe.markFailed(ctx, dtid) 104 return err 105 } 106 txe.te.preparedPool.Forget(dtid) 107 return nil 108 } 109 110 // markFailed does the necessary work to mark a CommitPrepared 111 // as failed. It marks the dtid as failed in the prepared pool, 112 // increments the InternalErros counter, and also changes the 113 // state of the transaction in the redo log as failed. If the 114 // state change does not succeed, it just logs the event. 115 // The function uses the passed in context that has no timeout 116 // instead of TxExecutor's context. 117 func (txe *TxExecutor) markFailed(ctx context.Context, dtid string) { 118 txe.te.env.Stats().InternalErrors.Add("TwopcCommit", 1) 119 txe.te.preparedPool.SetFailed(dtid) 120 conn, _, _, err := txe.te.txPool.Begin(ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil) 121 if err != nil { 122 log.Errorf("markFailed: Begin failed for dtid %s: %v", dtid, err) 123 return 124 } 125 defer txe.te.txPool.RollbackAndRelease(ctx, conn) 126 127 if err = txe.te.twoPC.UpdateRedo(ctx, conn, dtid, RedoStateFailed); err != nil { 128 log.Errorf("markFailed: UpdateRedo failed for dtid %s: %v", dtid, err) 129 return 130 } 131 132 if _, err = txe.te.txPool.Commit(ctx, conn); err != nil { 133 log.Errorf("markFailed: Commit failed for dtid %s: %v", dtid, err) 134 } 135 } 136 137 // RollbackPrepared rolls back a prepared transaction. This function handles 138 // the case of an incomplete prepare. 139 // 140 // If the prepare completely failed, it will just rollback the original 141 // transaction identified by originalID. 142 // 143 // If the connection was moved to the prepared pool, but redo log 144 // creation failed, then it will rollback that transaction and 145 // return the conn to the txPool. 146 // 147 // If prepare was fully successful, it will also delete the redo log. 148 // If the redo log deletion fails, it returns an error indicating that 149 // a retry is needed. 150 // 151 // In recovery mode, the original transaction id will not be available. 152 // If so, it must be set to 0, and the function will not attempt that 153 // step. If the original transaction is still alive, the transaction 154 // killer will be the one to eventually roll it back. 155 func (txe *TxExecutor) RollbackPrepared(dtid string, originalID int64) error { 156 if !txe.te.twopcEnabled { 157 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") 158 } 159 defer txe.te.env.Stats().QueryTimings.Record("ROLLBACK_PREPARED", time.Now()) 160 defer func() { 161 if preparedConn := txe.te.preparedPool.FetchForRollback(dtid); preparedConn != nil { 162 txe.te.txPool.RollbackAndRelease(txe.ctx, preparedConn) 163 } 164 if originalID != 0 { 165 txe.te.Rollback(txe.ctx, originalID) 166 } 167 }() 168 return txe.inTransaction(func(conn *StatefulConnection) error { 169 return txe.te.twoPC.DeleteRedo(txe.ctx, conn, dtid) 170 }) 171 } 172 173 // CreateTransaction creates the metadata for a 2PC transaction. 174 func (txe *TxExecutor) CreateTransaction(dtid string, participants []*querypb.Target) error { 175 if !txe.te.twopcEnabled { 176 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") 177 } 178 defer txe.te.env.Stats().QueryTimings.Record("CREATE_TRANSACTION", time.Now()) 179 return txe.inTransaction(func(conn *StatefulConnection) error { 180 return txe.te.twoPC.CreateTransaction(txe.ctx, conn, dtid, participants) 181 }) 182 } 183 184 // StartCommit atomically commits the transaction along with the 185 // decision to commit the associated 2pc transaction. 186 func (txe *TxExecutor) StartCommit(transactionID int64, dtid string) error { 187 if !txe.te.twopcEnabled { 188 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") 189 } 190 defer txe.te.env.Stats().QueryTimings.Record("START_COMMIT", time.Now()) 191 txe.logStats.TransactionID = transactionID 192 193 conn, err := txe.te.txPool.GetAndLock(transactionID, "for 2pc commit") 194 if err != nil { 195 return err 196 } 197 defer txe.te.txPool.RollbackAndRelease(txe.ctx, conn) 198 199 err = txe.te.twoPC.Transition(txe.ctx, conn, dtid, querypb.TransactionState_COMMIT) 200 if err != nil { 201 return err 202 } 203 _, err = txe.te.txPool.Commit(txe.ctx, conn) 204 return err 205 } 206 207 // SetRollback transitions the 2pc transaction to the Rollback state. 208 // If a transaction id is provided, that transaction is also rolled back. 209 func (txe *TxExecutor) SetRollback(dtid string, transactionID int64) error { 210 if !txe.te.twopcEnabled { 211 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") 212 } 213 defer txe.te.env.Stats().QueryTimings.Record("SET_ROLLBACK", time.Now()) 214 txe.logStats.TransactionID = transactionID 215 216 if transactionID != 0 { 217 txe.te.Rollback(txe.ctx, transactionID) 218 } 219 220 return txe.inTransaction(func(conn *StatefulConnection) error { 221 return txe.te.twoPC.Transition(txe.ctx, conn, dtid, querypb.TransactionState_ROLLBACK) 222 }) 223 } 224 225 // ConcludeTransaction deletes the 2pc transaction metadata 226 // essentially resolving it. 227 func (txe *TxExecutor) ConcludeTransaction(dtid string) error { 228 if !txe.te.twopcEnabled { 229 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") 230 } 231 defer txe.te.env.Stats().QueryTimings.Record("RESOLVE", time.Now()) 232 233 return txe.inTransaction(func(conn *StatefulConnection) error { 234 return txe.te.twoPC.DeleteTransaction(txe.ctx, conn, dtid) 235 }) 236 } 237 238 // ReadTransaction returns the metadata for the sepcified dtid. 239 func (txe *TxExecutor) ReadTransaction(dtid string) (*querypb.TransactionMetadata, error) { 240 if !txe.te.twopcEnabled { 241 return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") 242 } 243 return txe.te.twoPC.ReadTransaction(txe.ctx, dtid) 244 } 245 246 // ReadTwopcInflight returns info about all in-flight 2pc transactions. 247 func (txe *TxExecutor) ReadTwopcInflight() (distributed []*tx.DistributedTx, prepared, failed []*tx.PreparedTx, err error) { 248 if !txe.te.twopcEnabled { 249 return nil, nil, nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "2pc is not enabled") 250 } 251 prepared, failed, err = txe.te.twoPC.ReadAllRedo(txe.ctx) 252 if err != nil { 253 return nil, nil, nil, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "Could not read redo: %v", err) 254 } 255 distributed, err = txe.te.twoPC.ReadAllTransactions(txe.ctx) 256 if err != nil { 257 return nil, nil, nil, vterrors.Errorf(vtrpcpb.Code_UNKNOWN, "Could not read redo: %v", err) 258 } 259 return distributed, prepared, failed, nil 260 } 261 262 func (txe *TxExecutor) inTransaction(f func(*StatefulConnection) error) error { 263 conn, _, _, err := txe.te.txPool.Begin(txe.ctx, &querypb.ExecuteOptions{}, false, 0, nil, nil) 264 if err != nil { 265 return err 266 } 267 defer txe.te.txPool.RollbackAndRelease(txe.ctx, conn) 268 269 err = f(conn) 270 if err != nil { 271 return err 272 } 273 274 _, err = txe.te.txPool.Commit(txe.ctx, conn) 275 if err != nil { 276 return err 277 } 278 return nil 279 }