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  }