github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvclient/kvcoord/txn_coord_sender_savepoints.go (about) 1 // Copyright 2019 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 kvcoord 12 13 import ( 14 "context" 15 16 "github.com/cockroachdb/cockroach/pkg/kv" 17 "github.com/cockroachdb/cockroach/pkg/roachpb" 18 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 19 "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" 20 "github.com/cockroachdb/cockroach/pkg/util/uuid" 21 "github.com/cockroachdb/errors" 22 ) 23 24 // savepoint captures the state in the TxnCoordSender necessary to restore that 25 // state upon a savepoint rollback. 26 type savepoint struct { 27 // active is a snapshot of TxnCoordSender.active. 28 active bool 29 30 // txnID and epoch are set for savepoints with the active field set. 31 // txnID and epoch are used to disallow rollbacks past transaction restarts. 32 // Savepoints without the active field set are allowed to be used to rollback 33 // past transaction restarts too, because it's trivial to rollback to the 34 // beginning of the transaction. 35 txnID uuid.UUID 36 epoch enginepb.TxnEpoch 37 38 // seqNum represents the write seq num at the time the savepoint was created. 39 // On rollback, it configures the txn to ignore all seqnums from this value 40 // until the most recent seqnum. 41 seqNum enginepb.TxnSeq 42 43 // txnSpanRefresher fields. 44 refreshSpans []roachpb.Span 45 refreshInvalid bool 46 } 47 48 var _ kv.SavepointToken = (*savepoint)(nil) 49 50 // statically allocated savepoint marking the beginning of a transaction. Used 51 // to avoid allocations for such savepoints. 52 var initialSavepoint = savepoint{} 53 54 // Initial implements the client.SavepointToken interface. 55 func (s *savepoint) Initial() bool { 56 return !s.active 57 } 58 59 // CreateSavepoint is part of the client.TxnSender interface. 60 func (tc *TxnCoordSender) CreateSavepoint(ctx context.Context) (kv.SavepointToken, error) { 61 if tc.typ != kv.RootTxn { 62 return nil, errors.AssertionFailedf("cannot get savepoint in non-root txn") 63 } 64 65 tc.mu.Lock() 66 defer tc.mu.Unlock() 67 68 if err := tc.assertNotFinalized(); err != nil { 69 return nil, err 70 } 71 72 if tc.mu.txnState != txnPending { 73 return nil, ErrSavepointOperationInErrorTxn 74 } 75 76 if !tc.mu.active { 77 // Return a preallocated savepoint for the common case of savepoints placed 78 // at the beginning of transactions. 79 return &initialSavepoint, nil 80 } 81 82 s := &savepoint{ 83 active: true, // we've handled the not-active case above 84 txnID: tc.mu.txn.ID, 85 epoch: tc.mu.txn.Epoch, 86 } 87 for _, reqInt := range tc.interceptorStack { 88 reqInt.createSavepointLocked(ctx, s) 89 } 90 91 return s, nil 92 } 93 94 // RollbackToSavepoint is part of the client.TxnSender interface. 95 func (tc *TxnCoordSender) RollbackToSavepoint(ctx context.Context, s kv.SavepointToken) error { 96 if tc.typ != kv.RootTxn { 97 return errors.AssertionFailedf("cannot rollback savepoint in non-root txn") 98 } 99 100 tc.mu.Lock() 101 defer tc.mu.Unlock() 102 103 if err := tc.assertNotFinalized(); err != nil { 104 return err 105 } 106 107 // We don't allow rollback to savepoint after errors (except after 108 // ConditionFailedError which is special-cased elsewhere and doesn't move the 109 // txn to the txnError state). In particular, we cannot allow rollbacks to 110 // savepoint after ambiguous errors where it's possible for a 111 // previously-successfully written intent to have been pushed at a timestamp 112 // higher than the coordinator's WriteTimestamp. Doing so runs the risk that 113 // we'll commit at the lower timestamp, at which point the respective intent 114 // will be discarded. See 115 // https://github.com/cockroachdb/cockroach/issues/47587. 116 // 117 // TODO(andrei): White-list more errors. 118 if tc.mu.txnState == txnError { 119 return unimplemented.New("rollback_error", "cannot rollback to savepoint after error") 120 } 121 122 sp := s.(*savepoint) 123 err := tc.checkSavepointLocked(sp) 124 if err != nil { 125 if errors.Is(err, errSavepointInvalidAfterTxnRestart) { 126 err = roachpb.NewTransactionRetryWithProtoRefreshError( 127 "cannot rollback to savepoint after a transaction restart", 128 tc.mu.txn.ID, 129 // The transaction inside this error doesn't matter. 130 roachpb.Transaction{}, 131 ) 132 } 133 return err 134 } 135 136 // Restore the transaction's state, in case we're rewiding after an error. 137 tc.mu.txnState = txnPending 138 139 tc.mu.active = sp.active 140 141 for _, reqInt := range tc.interceptorStack { 142 reqInt.rollbackToSavepointLocked(ctx, *sp) 143 } 144 145 // If there's been any more writes since the savepoint was created, they'll 146 // need to be ignored. 147 if sp.seqNum < tc.interceptorAlloc.txnSeqNumAllocator.writeSeq { 148 tc.mu.txn.AddIgnoredSeqNumRange( 149 enginepb.IgnoredSeqNumRange{ 150 Start: sp.seqNum + 1, End: tc.interceptorAlloc.txnSeqNumAllocator.writeSeq, 151 }) 152 } 153 154 return nil 155 } 156 157 // ReleaseSavepoint is part of the client.TxnSender interface. 158 func (tc *TxnCoordSender) ReleaseSavepoint(ctx context.Context, s kv.SavepointToken) error { 159 if tc.typ != kv.RootTxn { 160 return errors.AssertionFailedf("cannot release savepoint in non-root txn") 161 } 162 163 tc.mu.Lock() 164 defer tc.mu.Unlock() 165 166 if tc.mu.txnState != txnPending { 167 return ErrSavepointOperationInErrorTxn 168 } 169 170 sp := s.(*savepoint) 171 err := tc.checkSavepointLocked(sp) 172 if errors.Is(err, errSavepointInvalidAfterTxnRestart) { 173 err = roachpb.NewTransactionRetryWithProtoRefreshError( 174 "cannot release savepoint after a transaction restart", 175 tc.mu.txn.ID, 176 // The transaction inside this error doesn't matter. 177 roachpb.Transaction{}, 178 ) 179 } 180 return err 181 } 182 183 type errSavepointOperationInErrorTxn struct{} 184 185 // ErrSavepointOperationInErrorTxn is reported when CreateSavepoint() 186 // or ReleaseSavepoint() is called over a txn currently in error. 187 var ErrSavepointOperationInErrorTxn error = errSavepointOperationInErrorTxn{} 188 189 func (err errSavepointOperationInErrorTxn) Error() string { 190 return "cannot create or release savepoint after an error has occurred" 191 } 192 193 func (tc *TxnCoordSender) assertNotFinalized() error { 194 if tc.mu.txnState == txnFinalized { 195 return errors.AssertionFailedf("operation invalid for finalized txns") 196 } 197 return nil 198 } 199 200 var errSavepointInvalidAfterTxnRestart = errors.New("savepoint invalid after transaction restart") 201 202 // checkSavepointLocked checks whether the provided savepoint is still valid. 203 // Returns errSavepointInvalidAfterTxnRestart if the savepoint is not an 204 // "initial" one and the transaction has restarted since the savepoint was 205 // created. 206 func (tc *TxnCoordSender) checkSavepointLocked(s *savepoint) error { 207 // Only savepoints taken before any activity are allowed to be used after a 208 // transaction restart. 209 if s.Initial() { 210 return nil 211 } 212 if s.txnID != tc.mu.txn.ID { 213 return errSavepointInvalidAfterTxnRestart 214 } 215 if s.epoch != tc.mu.txn.Epoch { 216 return errSavepointInvalidAfterTxnRestart 217 } 218 219 if s.seqNum < 0 || s.seqNum > tc.interceptorAlloc.txnSeqNumAllocator.writeSeq { 220 return errors.AssertionFailedf("invalid savepoint: got %d, expected 0-%d", 221 s.seqNum, tc.interceptorAlloc.txnSeqNumAllocator.writeSeq) 222 } 223 224 return nil 225 }