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  }