github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/txn_recovery_integration_test.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 kvserver 12 13 import ( 14 "bytes" 15 "context" 16 "fmt" 17 "testing" 18 19 "github.com/cockroachdb/cockroach/pkg/kv" 20 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/txnwait" 21 "github.com/cockroachdb/cockroach/pkg/roachpb" 22 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 23 "github.com/cockroachdb/cockroach/pkg/util/stop" 24 ) 25 26 // TestTxnRecoveryFromStaging tests the recovery process for a transaction that 27 // stages its transaction record immediately before its coordinator dies. It 28 // tests that concurrent transactions are able to recover from the indeterminate 29 // commit state after it becomes clear that the original transaction is no 30 // longer live. The test checks both the case where the parallel commit was 31 // successful and the case where the parallel commit failed. 32 func TestTxnRecoveryFromStaging(t *testing.T) { 33 defer leaktest.AfterTest(t)() 34 ctx := context.Background() 35 36 keyA, keyB := roachpb.Key("a"), roachpb.Key("b") 37 for i, tc := range []struct { 38 // implicitCommit says whether we expect the transaction to satisfy the 39 // implicit-commit condition. 40 implicitCommit bool 41 // If implicitCommit is false, writeTooOld dictates what kind of push will 42 // be experienced by one of the txn's intents. An intent being pushed is the 43 // reason why the implicit-commit condition is expected to fail. We simulate 44 // both pushes by the timestamp cache, and by deferred write-too-old 45 // conditions. 46 writeTooOld bool 47 }{ 48 { 49 implicitCommit: true, 50 }, 51 { 52 implicitCommit: false, 53 writeTooOld: false, 54 }, 55 { 56 implicitCommit: false, 57 writeTooOld: true, 58 }, 59 } { 60 t.Run(fmt.Sprintf("%d-commit:%t,writeTooOld:%t", i, tc.writeTooOld, tc.implicitCommit), func(t *testing.T) { 61 stopper := stop.NewStopper() 62 defer stopper.Stop(ctx) 63 store, manual := createTestStore(t, testStoreOpts{createSystemRanges: true}, stopper) 64 65 // Create a transaction that will get stuck performing a parallel commit. 66 txn := newTransaction("txn", keyA, 1, store.Clock()) 67 68 // Issue two writes, which will be considered in-flight at the time of 69 // the transaction's EndTxn request. 70 keyAVal := []byte("value") 71 pArgs := putArgs(keyA, keyAVal) 72 pArgs.Sequence = 1 73 h := roachpb.Header{Txn: txn} 74 if _, pErr := kv.SendWrappedWith(ctx, store.TestSender(), h, &pArgs); pErr != nil { 75 t.Fatal(pErr) 76 } 77 78 // If we don't want this transaction to commit successfully, perform a 79 // read on keyB to prevent the transaction's write to keyB from writing 80 // at its desired timestamp. This prevents an implicit commit state. 81 if !tc.implicitCommit { 82 if !tc.writeTooOld { 83 gArgs := getArgs(keyB) 84 if _, pErr := kv.SendWrapped(ctx, store.TestSender(), &gArgs); pErr != nil { 85 t.Fatal(pErr) 86 } 87 } else { 88 pArgs = putArgs(keyB, []byte("pusher val")) 89 if _, pErr := kv.SendWrapped(ctx, store.TestSender(), &pArgs); pErr != nil { 90 t.Fatal(pErr) 91 } 92 } 93 } 94 95 pArgs = putArgs(keyB, []byte("value2")) 96 pArgs.Sequence = 2 97 if _, pErr := kv.SendWrappedWith(ctx, store.TestSender(), h, &pArgs); pErr != nil { 98 t.Fatal(pErr) 99 } 100 101 // Issue a parallel commit, which will put the transaction into a 102 // STAGING state. Include both writes as the EndTxn's in-flight writes. 103 et, etH := endTxnArgs(txn, true) 104 et.InFlightWrites = []roachpb.SequencedWrite{ 105 {Key: keyA, Sequence: 1}, 106 {Key: keyB, Sequence: 2}, 107 } 108 etReply, pErr := kv.SendWrappedWith(ctx, store.TestSender(), etH, &et) 109 if pErr != nil { 110 t.Fatal(pErr) 111 } 112 if replyTxn := etReply.Header().Txn; replyTxn.Status != roachpb.STAGING { 113 t.Fatalf("expected STAGING txn, found %v", replyTxn) 114 } 115 116 // Pretend the transaction coordinator for the parallel commit died at this point. 117 // Wait for longer than the TxnLivenessThreshold and then issue a read on one of 118 // the keys that the transaction wrote. This will result in a transaction push and 119 // eventually a full transaction recovery in order to resolve the indeterminate 120 // commit. 121 manual.Increment(txnwait.TxnLivenessThreshold.Nanoseconds() + 1) 122 123 gArgs := getArgs(keyA) 124 gReply, pErr := kv.SendWrapped(ctx, store.TestSender(), &gArgs) 125 if pErr != nil { 126 t.Fatal(pErr) 127 } 128 if tc.implicitCommit { 129 if val := gReply.(*roachpb.GetResponse).Value; val == nil { 130 t.Fatalf("expected non-nil value when reading key %v", keyA) 131 } else if valBytes, err := val.GetBytes(); err != nil { 132 t.Fatal(err) 133 } else if !bytes.Equal(valBytes, keyAVal) { 134 t.Fatalf("actual value %q did not match expected value %q", valBytes, keyAVal) 135 } 136 } else { 137 if val := gReply.(*roachpb.GetResponse).Value; val != nil { 138 t.Fatalf("expected nil value when reading key %v; found %v", keyA, val) 139 } 140 } 141 142 // Query the transaction and verify that it has the right status. 143 qtArgs := queryTxnArgs(txn.TxnMeta, false /* waitForUpdate */) 144 qtReply, pErr := kv.SendWrapped(ctx, store.TestSender(), &qtArgs) 145 if pErr != nil { 146 t.Fatal(pErr) 147 } 148 status := qtReply.(*roachpb.QueryTxnResponse).QueriedTxn.Status 149 expStatus := roachpb.ABORTED 150 if tc.implicitCommit { 151 expStatus = roachpb.COMMITTED 152 } 153 if status != expStatus { 154 t.Fatalf("expected transaction status %v; found %v", expStatus, status) 155 } 156 }) 157 } 158 }