github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvclient/kvcoord/txn_interceptor_heartbeater_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 kvcoord
    12  
    13  import (
    14  	"context"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/concurrency/lock"
    19  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    20  	"github.com/cockroachdb/cockroach/pkg/testutils"
    21  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    22  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    23  	"github.com/cockroachdb/cockroach/pkg/util/log"
    24  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    25  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    26  	"github.com/cockroachdb/cockroach/pkg/util/tracing"
    27  	"github.com/cockroachdb/errors"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  func makeMockTxnHeartbeater(
    32  	txn *roachpb.Transaction,
    33  ) (th txnHeartbeater, mockSender, mockGatekeeper *mockLockedSender) {
    34  	mockSender, mockGatekeeper = &mockLockedSender{}, &mockLockedSender{}
    35  	manual := hlc.NewManualClock(123)
    36  	th.init(
    37  		log.AmbientContext{Tracer: tracing.NewTracer()},
    38  		stop.NewStopper(),
    39  		hlc.NewClock(manual.UnixNano, time.Nanosecond),
    40  		new(TxnMetrics),
    41  		1*time.Millisecond,
    42  		mockGatekeeper,
    43  		new(syncutil.Mutex),
    44  		txn,
    45  	)
    46  	th.setWrapped(mockSender)
    47  	return th, mockSender, mockGatekeeper
    48  }
    49  
    50  func waitForHeartbeatLoopToStop(t *testing.T, th *txnHeartbeater) {
    51  	t.Helper()
    52  	testutils.SucceedsSoon(t, func() error {
    53  		th.mu.Lock()
    54  		defer th.mu.Unlock()
    55  		if th.heartbeatLoopRunningLocked() {
    56  			return errors.New("txn heartbeat loop running")
    57  		}
    58  		return nil
    59  	})
    60  }
    61  
    62  // TestTxnHeartbeaterSetsTransactionKey tests that the txnHeartbeater sets the
    63  // transaction key to the key of the first write that is sent through it.
    64  func TestTxnHeartbeaterSetsTransactionKey(t *testing.T) {
    65  	defer leaktest.AfterTest(t)()
    66  	ctx := context.Background()
    67  	txn := makeTxnProto()
    68  	txn.Key = nil // reset
    69  	th, mockSender, _ := makeMockTxnHeartbeater(&txn)
    70  	defer th.stopper.Stop(ctx)
    71  
    72  	// No key is set on a read-only batch.
    73  	keyA, keyB := roachpb.Key("a"), roachpb.Key("b")
    74  	var ba roachpb.BatchRequest
    75  	ba.Header = roachpb.Header{Txn: txn.Clone()}
    76  	ba.Add(&roachpb.GetRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}})
    77  	ba.Add(&roachpb.GetRequest{RequestHeader: roachpb.RequestHeader{Key: keyB}})
    78  
    79  	mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) {
    80  		require.Len(t, ba.Requests, 2)
    81  		require.Equal(t, keyA, ba.Requests[0].GetInner().Header().Key)
    82  		require.Equal(t, keyB, ba.Requests[1].GetInner().Header().Key)
    83  
    84  		require.Equal(t, txn.ID, ba.Txn.ID)
    85  		require.Nil(t, ba.Txn.Key)
    86  
    87  		br := ba.CreateReply()
    88  		br.Txn = ba.Txn
    89  		return br, nil
    90  	})
    91  
    92  	br, pErr := th.SendLocked(ctx, ba)
    93  	require.Nil(t, pErr)
    94  	require.NotNil(t, br)
    95  	require.Nil(t, txn.Key)
    96  
    97  	// The key of the first write is set as the transaction key.
    98  	ba.Requests = nil
    99  	ba.Add(&roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyB}})
   100  	ba.Add(&roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}})
   101  
   102  	mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) {
   103  		require.Len(t, ba.Requests, 2)
   104  		require.Equal(t, keyB, ba.Requests[0].GetInner().Header().Key)
   105  		require.Equal(t, keyA, ba.Requests[1].GetInner().Header().Key)
   106  
   107  		require.Equal(t, txn.ID, ba.Txn.ID)
   108  		require.Equal(t, keyB, roachpb.Key(ba.Txn.Key))
   109  
   110  		br = ba.CreateReply()
   111  		br.Txn = ba.Txn
   112  		return br, nil
   113  	})
   114  
   115  	br, pErr = th.SendLocked(ctx, ba)
   116  	require.Nil(t, pErr)
   117  	require.NotNil(t, br)
   118  	require.Equal(t, keyB, roachpb.Key(txn.Key))
   119  
   120  	// The transaction key is not changed on subsequent batches.
   121  	ba.Requests = nil
   122  	ba.Add(&roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}})
   123  
   124  	mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) {
   125  		require.Len(t, ba.Requests, 1)
   126  		require.Equal(t, keyA, ba.Requests[0].GetInner().Header().Key)
   127  
   128  		require.Equal(t, txn.ID, ba.Txn.ID)
   129  		require.Equal(t, keyB, roachpb.Key(ba.Txn.Key))
   130  
   131  		br = ba.CreateReply()
   132  		br.Txn = ba.Txn
   133  		return br, nil
   134  	})
   135  
   136  	br, pErr = th.SendLocked(ctx, ba)
   137  	require.Nil(t, pErr)
   138  	require.NotNil(t, br)
   139  	require.Equal(t, keyB, roachpb.Key(txn.Key))
   140  }
   141  
   142  // TestTxnHeartbeaterLoopStartedOnFirstLock tests that the txnHeartbeater
   143  // doesn't start its heartbeat loop until it observes the transaction issues
   144  // a request that will acquire locks.
   145  func TestTxnHeartbeaterLoopStartedOnFirstLock(t *testing.T) {
   146  	defer leaktest.AfterTest(t)()
   147  	testutils.RunTrueAndFalse(t, "write", func(t *testing.T, write bool) {
   148  		ctx := context.Background()
   149  		txn := makeTxnProto()
   150  		th, _, _ := makeMockTxnHeartbeater(&txn)
   151  		defer th.stopper.Stop(ctx)
   152  
   153  		// Read-only requests don't start the heartbeat loop.
   154  		keyA := roachpb.Key("a")
   155  		keyAHeader := roachpb.RequestHeader{Key: keyA}
   156  		var ba roachpb.BatchRequest
   157  		ba.Header = roachpb.Header{Txn: txn.Clone()}
   158  		ba.Add(&roachpb.GetRequest{RequestHeader: keyAHeader})
   159  
   160  		br, pErr := th.SendLocked(ctx, ba)
   161  		require.Nil(t, pErr)
   162  		require.NotNil(t, br)
   163  
   164  		th.mu.Lock()
   165  		require.False(t, th.mu.loopStarted)
   166  		require.False(t, th.heartbeatLoopRunningLocked())
   167  		th.mu.Unlock()
   168  
   169  		// The heartbeat loop is started on the first locking request.
   170  		ba.Requests = nil
   171  		if write {
   172  			ba.Add(&roachpb.PutRequest{RequestHeader: keyAHeader})
   173  		} else {
   174  			ba.Add(&roachpb.ScanRequest{RequestHeader: keyAHeader, KeyLocking: lock.Exclusive})
   175  		}
   176  
   177  		br, pErr = th.SendLocked(ctx, ba)
   178  		require.Nil(t, pErr)
   179  		require.NotNil(t, br)
   180  
   181  		th.mu.Lock()
   182  		require.True(t, th.mu.loopStarted)
   183  		require.True(t, th.heartbeatLoopRunningLocked())
   184  		th.mu.Unlock()
   185  
   186  		// Closing the interceptor stops the heartbeat loop.
   187  		th.mu.Lock()
   188  		th.closeLocked()
   189  		th.mu.Unlock()
   190  		waitForHeartbeatLoopToStop(t, &th)
   191  		require.True(t, th.mu.loopStarted) // still set
   192  	})
   193  }
   194  
   195  // TestTxnHeartbeaterLoopNotStartedFor1PC tests that the txnHeartbeater does
   196  // not start a heartbeat loop if it detects a 1PC transaction.
   197  func TestTxnHeartbeaterLoopNotStartedFor1PC(t *testing.T) {
   198  	defer leaktest.AfterTest(t)()
   199  	ctx := context.Background()
   200  	txn := makeTxnProto()
   201  	th, _, _ := makeMockTxnHeartbeater(&txn)
   202  	defer th.stopper.Stop(ctx)
   203  
   204  	keyA := roachpb.Key("a")
   205  	var ba roachpb.BatchRequest
   206  	ba.Header = roachpb.Header{Txn: txn.Clone()}
   207  	ba.Add(&roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}})
   208  	ba.Add(&roachpb.EndTxnRequest{Commit: true})
   209  
   210  	br, pErr := th.SendLocked(ctx, ba)
   211  	require.Nil(t, pErr)
   212  	require.NotNil(t, br)
   213  
   214  	th.mu.Lock()
   215  	require.False(t, th.mu.loopStarted)
   216  	require.False(t, th.heartbeatLoopRunningLocked())
   217  	th.mu.Unlock()
   218  }
   219  
   220  // TestTxnHeartbeaterLoopRequests tests that the HeartbeatTxnRequests that the
   221  // txnHeartbeater sends contain the correct information. It then tests that the
   222  // heartbeat loop shuts itself down if it detects a committed transaction. This
   223  // can occur through two different paths. A heartbeat request itself can find
   224  // a committed transaction record or the request can race with a request sent
   225  // from the transaction coordinator that finalizes the transaction.
   226  func TestTxnHeartbeaterLoopRequests(t *testing.T) {
   227  	defer leaktest.AfterTest(t)()
   228  	testutils.RunTrueAndFalse(t, "heartbeatObserved", func(t *testing.T, heartbeatObserved bool) {
   229  		ctx := context.Background()
   230  		txn := makeTxnProto()
   231  		th, _, mockGatekeeper := makeMockTxnHeartbeater(&txn)
   232  		defer th.stopper.Stop(ctx)
   233  
   234  		var count int
   235  		var lastTime hlc.Timestamp
   236  		mockGatekeeper.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) {
   237  			require.Len(t, ba.Requests, 1)
   238  			require.IsType(t, &roachpb.HeartbeatTxnRequest{}, ba.Requests[0].GetInner())
   239  
   240  			hbReq := ba.Requests[0].GetInner().(*roachpb.HeartbeatTxnRequest)
   241  			require.Equal(t, &txn, ba.Txn)
   242  			require.Equal(t, roachpb.Key(txn.Key), hbReq.Key)
   243  			require.True(t, lastTime.Less(hbReq.Now))
   244  
   245  			count++
   246  			lastTime = hbReq.Now
   247  
   248  			br := ba.CreateReply()
   249  			br.Txn = ba.Txn
   250  			return br, nil
   251  		})
   252  
   253  		// Kick off the heartbeat loop.
   254  		keyA := roachpb.Key("a")
   255  		var ba roachpb.BatchRequest
   256  		ba.Header = roachpb.Header{Txn: txn.Clone()}
   257  		ba.Add(&roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}})
   258  
   259  		br, pErr := th.SendLocked(ctx, ba)
   260  		require.Nil(t, pErr)
   261  		require.NotNil(t, br)
   262  
   263  		// Wait for 5 heartbeat requests.
   264  		testutils.SucceedsSoon(t, func() error {
   265  			th.mu.Lock()
   266  			defer th.mu.Unlock()
   267  			require.True(t, th.mu.loopStarted)
   268  			require.True(t, th.heartbeatLoopRunningLocked())
   269  			if count < 5 {
   270  				return errors.Errorf("waiting for more heartbeat requests, found %d", count)
   271  			}
   272  			return nil
   273  		})
   274  
   275  		// Mark the coordinator's transaction record as COMMITTED while a heartbeat
   276  		// is in-flight. This should cause the heartbeat loop to shut down.
   277  		th.mu.Lock()
   278  		mockGatekeeper.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) {
   279  			require.Len(t, ba.Requests, 1)
   280  			require.IsType(t, &roachpb.HeartbeatTxnRequest{}, ba.Requests[0].GetInner())
   281  
   282  			br := ba.CreateReply()
   283  			br.Txn = ba.Txn
   284  			if heartbeatObserved {
   285  				// Mimic a Heartbeat request that observed a committed record.
   286  				br.Txn.Status = roachpb.COMMITTED
   287  			} else {
   288  				// Mimic an EndTxn that raced with the heartbeat loop.
   289  				txn.Status = roachpb.COMMITTED
   290  			}
   291  			return br, nil
   292  		})
   293  		th.mu.Unlock()
   294  		waitForHeartbeatLoopToStop(t, &th)
   295  
   296  		// Depending on how the committed transaction was observed, we may or
   297  		// may not expect the heartbeater's final observed status to be set.
   298  		if heartbeatObserved {
   299  			require.Equal(t, roachpb.COMMITTED, th.mu.finalObservedStatus)
   300  		} else {
   301  			require.Equal(t, roachpb.PENDING, th.mu.finalObservedStatus)
   302  		}
   303  	})
   304  }
   305  
   306  // TestTxnHeartbeaterAsyncAbort tests that the txnHeartbeater rolls back the
   307  // transaction asynchronously if it detects an aborted transaction, either
   308  // through a TransactionAbortedError or through an ABORTED transaction proto
   309  // in the HeartbeatTxn response.
   310  func TestTxnHeartbeaterAsyncAbort(t *testing.T) {
   311  	defer leaktest.AfterTest(t)()
   312  	testutils.RunTrueAndFalse(t, "abortedErr", func(t *testing.T, abortedErr bool) {
   313  		ctx := context.Background()
   314  		txn := makeTxnProto()
   315  		th, mockSender, mockGatekeeper := makeMockTxnHeartbeater(&txn)
   316  		defer th.stopper.Stop(ctx)
   317  
   318  		putDone, asyncAbortDone := make(chan struct{}), make(chan struct{})
   319  		mockGatekeeper.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) {
   320  			// Wait for the Put to finish to avoid a data race.
   321  			<-putDone
   322  
   323  			require.Len(t, ba.Requests, 1)
   324  			require.IsType(t, &roachpb.HeartbeatTxnRequest{}, ba.Requests[0].GetInner())
   325  
   326  			if abortedErr {
   327  				return nil, roachpb.NewErrorWithTxn(
   328  					roachpb.NewTransactionAbortedError(roachpb.ABORT_REASON_UNKNOWN), ba.Txn,
   329  				)
   330  			}
   331  			br := ba.CreateReply()
   332  			br.Txn = ba.Txn
   333  			br.Txn.Status = roachpb.ABORTED
   334  			return br, nil
   335  		})
   336  
   337  		// Kick off the heartbeat loop.
   338  		keyA := roachpb.Key("a")
   339  		var ba roachpb.BatchRequest
   340  		ba.Header = roachpb.Header{Txn: txn.Clone()}
   341  		ba.Add(&roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}})
   342  
   343  		br, pErr := th.SendLocked(ctx, ba)
   344  		require.Nil(t, pErr)
   345  		require.NotNil(t, br)
   346  
   347  		// Test that the transaction is rolled back.
   348  		mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) {
   349  			defer close(asyncAbortDone)
   350  			require.Len(t, ba.Requests, 1)
   351  			require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[0].GetInner())
   352  
   353  			etReq := ba.Requests[0].GetInner().(*roachpb.EndTxnRequest)
   354  			require.Equal(t, &txn, ba.Txn)
   355  			require.Nil(t, etReq.Key) // set in txnCommitter
   356  			require.False(t, etReq.Commit)
   357  			require.True(t, etReq.Poison)
   358  
   359  			br = ba.CreateReply()
   360  			br.Txn = ba.Txn
   361  			br.Txn.Status = roachpb.ABORTED
   362  			return br, nil
   363  		})
   364  		close(putDone)
   365  
   366  		// The heartbeat loop should eventually close.
   367  		waitForHeartbeatLoopToStop(t, &th)
   368  
   369  		// Wait for the async abort to finish.
   370  		<-asyncAbortDone
   371  
   372  		// Regardless of which channel informed the heartbeater of the
   373  		// transaction's aborted status, we expect the heartbeater's final
   374  		// observed status to be set.
   375  		require.Equal(t, roachpb.ABORTED, th.mu.finalObservedStatus)
   376  	})
   377  }