github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/batcheval/transaction.go (about)

     1  // Copyright 2014 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package batcheval
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"fmt"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    19  	"github.com/cockroachdb/cockroach/pkg/storage"
    20  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    21  	"github.com/cockroachdb/cockroach/pkg/util"
    22  	"github.com/cockroachdb/cockroach/pkg/util/log"
    23  	"github.com/cockroachdb/errors"
    24  )
    25  
    26  // ErrTransactionUnsupported is returned when a non-transactional command is
    27  // evaluated in the context of a transaction.
    28  var ErrTransactionUnsupported = errors.New("not supported within a transaction")
    29  
    30  // VerifyTransaction runs sanity checks verifying that the transaction in the
    31  // header and the request are compatible.
    32  func VerifyTransaction(
    33  	h roachpb.Header, args roachpb.Request, permittedStatuses ...roachpb.TransactionStatus,
    34  ) error {
    35  	if h.Txn == nil {
    36  		return errors.Errorf("no transaction specified to %s", args.Method())
    37  	}
    38  	if !bytes.Equal(args.Header().Key, h.Txn.Key) {
    39  		return errors.Errorf("request key %s should match txn key %s", args.Header().Key, h.Txn.Key)
    40  	}
    41  	statusPermitted := false
    42  	for _, s := range permittedStatuses {
    43  		if h.Txn.Status == s {
    44  			statusPermitted = true
    45  			break
    46  		}
    47  	}
    48  	if !statusPermitted {
    49  		return roachpb.NewTransactionStatusError(
    50  			fmt.Sprintf("cannot perform %s with txn status %v", args.Method(), h.Txn.Status),
    51  		)
    52  	}
    53  	return nil
    54  }
    55  
    56  // WriteAbortSpanOnResolve returns true if the abort span must be written when
    57  // the transaction with the given status is resolved. It avoids instructing the
    58  // caller to write to the abort span if the caller didn't actually remove any
    59  // intents but intends to poison.
    60  func WriteAbortSpanOnResolve(status roachpb.TransactionStatus, poison, removedIntents bool) bool {
    61  	if status != roachpb.ABORTED {
    62  		// Only update the AbortSpan for aborted transactions.
    63  		return false
    64  	}
    65  	if !poison {
    66  		// We can remove any entries from the AbortSpan.
    67  		return true
    68  	}
    69  	// We only need to add AbortSpan entries for transactions that we have
    70  	// invalidated by removing intents. This avoids leaking AbortSpan entries if
    71  	// a request raced with txn record GC and mistakenly interpreted a committed
    72  	// txn as aborted only to return to the intent it wanted to push and find it
    73  	// already resolved. We're only required to write an entry if we do
    74  	// something that could confuse/invalidate a zombie transaction.
    75  	return removedIntents
    76  }
    77  
    78  // UpdateAbortSpan clears any AbortSpan entry if poison is false. Otherwise, if
    79  // poison is true, it creates an entry for this transaction in the AbortSpan to
    80  // prevent future reads or writes from spuriously succeeding on this range.
    81  func UpdateAbortSpan(
    82  	ctx context.Context,
    83  	rec EvalContext,
    84  	readWriter storage.ReadWriter,
    85  	ms *enginepb.MVCCStats,
    86  	txn enginepb.TxnMeta,
    87  	poison bool,
    88  ) error {
    89  	// Read the current state of the AbortSpan so we can detect when
    90  	// no changes are needed. This can help us avoid unnecessary Raft
    91  	// proposals.
    92  	var curEntry roachpb.AbortSpanEntry
    93  	exists, err := rec.AbortSpan().Get(ctx, readWriter, txn.ID, &curEntry)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	if !poison {
    99  		if !exists {
   100  			return nil
   101  		}
   102  		return rec.AbortSpan().Del(ctx, readWriter, ms, txn.ID)
   103  	}
   104  
   105  	entry := roachpb.AbortSpanEntry{
   106  		Key:       txn.Key,
   107  		Timestamp: txn.WriteTimestamp,
   108  		Priority:  txn.Priority,
   109  	}
   110  	if exists && curEntry.Equal(entry) {
   111  		return nil
   112  	}
   113  	// curEntry already escapes, so assign entry to curEntry and pass
   114  	// that to Put instead of allowing entry to escape as well.
   115  	curEntry = entry
   116  	return rec.AbortSpan().Put(ctx, readWriter, ms, txn.ID, &curEntry)
   117  }
   118  
   119  // CanPushWithPriority returns true if the given pusher can push the pushee
   120  // based on its priority.
   121  func CanPushWithPriority(pusher, pushee *roachpb.Transaction) bool {
   122  	return (pusher.Priority > enginepb.MinTxnPriority && pushee.Priority == enginepb.MinTxnPriority) ||
   123  		(pusher.Priority == enginepb.MaxTxnPriority && pushee.Priority < pusher.Priority)
   124  }
   125  
   126  // CanCreateTxnRecord determines whether a transaction record can be created for
   127  // the provided transaction. If not, the function will return an error. If so,
   128  // the function may modify the provided transaction.
   129  func CanCreateTxnRecord(rec EvalContext, txn *roachpb.Transaction) error {
   130  	// Provide the transaction's minimum timestamp. The transaction could not
   131  	// have written a transaction record previously with a timestamp below this.
   132  	ok, minCommitTS, reason := rec.CanCreateTxnRecord(txn.ID, txn.Key, txn.MinTimestamp)
   133  	if !ok {
   134  		return roachpb.NewTransactionAbortedError(reason)
   135  	}
   136  	txn.WriteTimestamp.Forward(minCommitTS)
   137  	return nil
   138  }
   139  
   140  // SynthesizeTxnFromMeta creates a synthetic transaction object from
   141  // the provided transaction metadata. The synthetic transaction is not
   142  // meant to be persisted, but can serve as a representation of the
   143  // transaction for outside observation. The function also checks
   144  // whether it is possible for the transaction to ever create a
   145  // transaction record in the future. If not, the returned transaction
   146  // will be marked as ABORTED and it is safe to assume that the
   147  // transaction record will never be written in the future.
   148  //
   149  // Note that the Transaction object returned by this function is
   150  // inadequate to perform further KV reads or to perform intent
   151  // resolution on its behalf, even if its state is PENDING. This is
   152  // because the original Transaction object may have been partially
   153  // rolled back and marked some of its intents as "ignored"
   154  // (txn.IgnoredSeqNums != nil), but this state is not stored in
   155  // TxnMeta. Proceeding to KV reads or intent resolution without this
   156  // information would cause a partial rollback, if any, to be reverted
   157  // and yield inconsistent data.
   158  func SynthesizeTxnFromMeta(rec EvalContext, txn enginepb.TxnMeta) roachpb.Transaction {
   159  	// Determine whether the transaction record could ever actually be written
   160  	// in the future.
   161  	txnMinTS := txn.MinTimestamp
   162  	if txnMinTS.IsEmpty() {
   163  		// If the transaction metadata's min timestamp is empty then provide its
   164  		// provisional commit timestamp to CanCreateTxnRecord. If this timestamp
   165  		// is larger than the transaction's real minimum timestamp then
   166  		// CanCreateTxnRecord may return false positives (i.e. it determines
   167  		// that the record could eventually be created when it actually
   168  		// couldn't) but will never return false negatives (i.e. it will never
   169  		// determine that the record could not be created when it actually
   170  		// could). This is important, because it means that we may end up
   171  		// failing to push a finalized transaction but will never determine that
   172  		// a transaction is finalized when it still could end up committing.
   173  		//
   174  		// TODO(nvanbenschoten): This case is only possible for intents that
   175  		// were written by a transaction coordinator before v19.2, which means
   176  		// that we can remove it in v20.1 and replace it with:
   177  		//
   178  		//  synthTxnRecord.Status = roachpb.ABORTED
   179  		//
   180  		txnMinTS = txn.WriteTimestamp
   181  
   182  		// If we don't need to worry about compatibility, disallow this case.
   183  		if util.RaceEnabled {
   184  			log.Fatalf(context.TODO(), "no minimum transaction timestamp provided: %v", txn)
   185  		}
   186  	}
   187  
   188  	// Construct the transaction object.
   189  	synthTxnRecord := roachpb.TransactionRecord{
   190  		TxnMeta: txn,
   191  		Status:  roachpb.PENDING,
   192  		// Set the LastHeartbeat timestamp to the transactions's MinTimestamp. We
   193  		// use this as an indication of client activity. Note that we cannot use
   194  		// txn.WriteTimestamp for that purpose, as the WriteTimestamp could have
   195  		// been bumped by other pushers.
   196  		LastHeartbeat: txnMinTS,
   197  	}
   198  
   199  	ok, minCommitTS, _ := rec.CanCreateTxnRecord(txn.ID, txn.Key, txnMinTS)
   200  	if ok {
   201  		// Forward the provisional commit timestamp by the minimum timestamp that
   202  		// the transaction would be able to create a transaction record at.
   203  		synthTxnRecord.WriteTimestamp.Forward(minCommitTS)
   204  	} else {
   205  		// Mark the transaction as ABORTED because it is uncommittable.
   206  		synthTxnRecord.Status = roachpb.ABORTED
   207  	}
   208  	return synthTxnRecord.AsTransaction()
   209  }