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 }