github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/concurrency/lock_table_waiter_test.go (about)

     1  // Copyright 2020 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 concurrency
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"math/rand"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/intentresolver"
    20  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/spanset"
    21  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    22  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    23  	"github.com/cockroachdb/cockroach/pkg/storage/enginepb"
    24  	"github.com/cockroachdb/cockroach/pkg/testutils"
    25  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    26  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    27  	"github.com/cockroachdb/cockroach/pkg/util/stop"
    28  	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
    29  	"github.com/stretchr/testify/require"
    30  )
    31  
    32  type mockIntentResolver struct {
    33  	pushTxn        func(context.Context, *enginepb.TxnMeta, roachpb.Header, roachpb.PushTxnType) (*roachpb.Transaction, *Error)
    34  	resolveIntent  func(context.Context, roachpb.LockUpdate) *Error
    35  	resolveIntents func(context.Context, []roachpb.LockUpdate) *Error
    36  }
    37  
    38  // mockIntentResolver implements the IntentResolver interface.
    39  func (m *mockIntentResolver) PushTransaction(
    40  	ctx context.Context, txn *enginepb.TxnMeta, h roachpb.Header, pushType roachpb.PushTxnType,
    41  ) (*roachpb.Transaction, *Error) {
    42  	return m.pushTxn(ctx, txn, h, pushType)
    43  }
    44  
    45  func (m *mockIntentResolver) ResolveIntent(
    46  	ctx context.Context, intent roachpb.LockUpdate, _ intentresolver.ResolveOptions,
    47  ) *Error {
    48  	return m.resolveIntent(ctx, intent)
    49  }
    50  
    51  func (m *mockIntentResolver) ResolveIntents(
    52  	ctx context.Context, intents []roachpb.LockUpdate, opts intentresolver.ResolveOptions,
    53  ) *Error {
    54  	return m.resolveIntents(ctx, intents)
    55  }
    56  
    57  type mockLockTableGuard struct {
    58  	state         waitingState
    59  	signal        chan struct{}
    60  	stateObserved chan struct{}
    61  }
    62  
    63  // mockLockTableGuard implements the lockTableGuard interface.
    64  func (g *mockLockTableGuard) ShouldWait() bool            { return true }
    65  func (g *mockLockTableGuard) NewStateChan() chan struct{} { return g.signal }
    66  func (g *mockLockTableGuard) CurState() waitingState {
    67  	s := g.state
    68  	if g.stateObserved != nil {
    69  		g.stateObserved <- struct{}{}
    70  	}
    71  	return s
    72  }
    73  func (g *mockLockTableGuard) notify() { g.signal <- struct{}{} }
    74  
    75  // mockLockTableGuard implements the LockManager interface.
    76  func (g *mockLockTableGuard) OnLockAcquired(_ context.Context, _ *roachpb.LockAcquisition) {
    77  	panic("unimplemented")
    78  }
    79  func (g *mockLockTableGuard) OnLockUpdated(_ context.Context, up *roachpb.LockUpdate) {
    80  	if g.state.held && g.state.txn.ID == up.Txn.ID && g.state.key.Equal(up.Key) {
    81  		g.state = waitingState{kind: doneWaiting}
    82  		g.notify()
    83  	}
    84  }
    85  
    86  func setupLockTableWaiterTest() (*lockTableWaiterImpl, *mockIntentResolver, *mockLockTableGuard) {
    87  	ir := &mockIntentResolver{}
    88  	st := cluster.MakeTestingClusterSettings()
    89  	LockTableLivenessPushDelay.Override(&st.SV, 0)
    90  	LockTableDeadlockDetectionPushDelay.Override(&st.SV, 0)
    91  	guard := &mockLockTableGuard{
    92  		signal: make(chan struct{}, 1),
    93  	}
    94  	w := &lockTableWaiterImpl{
    95  		st:      st,
    96  		stopper: stop.NewStopper(),
    97  		ir:      ir,
    98  		lm:      guard,
    99  	}
   100  	return w, ir, guard
   101  }
   102  
   103  func makeTxnProto(name string) roachpb.Transaction {
   104  	return roachpb.MakeTransaction(name, []byte("key"), 0, hlc.Timestamp{WallTime: 10}, 0)
   105  }
   106  
   107  // TestLockTableWaiterWithTxn tests the lockTableWaiter's behavior under
   108  // different waiting states while a transactional request is waiting.
   109  func TestLockTableWaiterWithTxn(t *testing.T) {
   110  	defer leaktest.AfterTest(t)()
   111  	ctx := context.Background()
   112  
   113  	maxTS := hlc.Timestamp{WallTime: 15}
   114  	makeReq := func() Request {
   115  		txn := makeTxnProto("request")
   116  		txn.MaxTimestamp = maxTS
   117  		return Request{
   118  			Txn:       &txn,
   119  			Timestamp: txn.ReadTimestamp,
   120  		}
   121  	}
   122  
   123  	t.Run("state", func(t *testing.T) {
   124  		t.Run("waitFor", func(t *testing.T) {
   125  			testWaitPush(t, waitFor, makeReq, maxTS)
   126  		})
   127  
   128  		t.Run("waitForDistinguished", func(t *testing.T) {
   129  			testWaitPush(t, waitForDistinguished, makeReq, maxTS)
   130  		})
   131  
   132  		t.Run("waitElsewhere", func(t *testing.T) {
   133  			testWaitPush(t, waitElsewhere, makeReq, maxTS)
   134  		})
   135  
   136  		t.Run("waitSelf", func(t *testing.T) {
   137  			testWaitNoopUntilDone(t, waitSelf, makeReq)
   138  		})
   139  
   140  		t.Run("doneWaiting", func(t *testing.T) {
   141  			w, _, g := setupLockTableWaiterTest()
   142  			defer w.stopper.Stop(ctx)
   143  
   144  			g.state = waitingState{kind: doneWaiting}
   145  			g.notify()
   146  
   147  			err := w.WaitOn(ctx, makeReq(), g)
   148  			require.Nil(t, err)
   149  		})
   150  	})
   151  
   152  	t.Run("ctx done", func(t *testing.T) {
   153  		w, _, g := setupLockTableWaiterTest()
   154  		defer w.stopper.Stop(ctx)
   155  
   156  		ctxWithCancel, cancel := context.WithCancel(ctx)
   157  		go cancel()
   158  
   159  		err := w.WaitOn(ctxWithCancel, makeReq(), g)
   160  		require.NotNil(t, err)
   161  		require.Equal(t, context.Canceled.Error(), err.GoError().Error())
   162  	})
   163  
   164  	t.Run("stopper quiesce", func(t *testing.T) {
   165  		w, _, g := setupLockTableWaiterTest()
   166  		defer w.stopper.Stop(ctx)
   167  
   168  		go func() {
   169  			w.stopper.Quiesce(ctx)
   170  		}()
   171  
   172  		err := w.WaitOn(ctx, makeReq(), g)
   173  		require.NotNil(t, err)
   174  		require.IsType(t, &roachpb.NodeUnavailableError{}, err.GetDetail())
   175  	})
   176  }
   177  
   178  // TestLockTableWaiterWithNonTxn tests the lockTableWaiter's behavior under
   179  // different waiting states while a non-transactional request is waiting.
   180  func TestLockTableWaiterWithNonTxn(t *testing.T) {
   181  	defer leaktest.AfterTest(t)()
   182  	ctx := context.Background()
   183  
   184  	reqHeaderTS := hlc.Timestamp{WallTime: 10}
   185  	makeReq := func() Request {
   186  		return Request{
   187  			Timestamp: reqHeaderTS,
   188  			Priority:  roachpb.NormalUserPriority,
   189  		}
   190  	}
   191  
   192  	t.Run("state", func(t *testing.T) {
   193  		t.Run("waitFor", func(t *testing.T) {
   194  			t.Log("waitFor does not cause non-transactional requests to push")
   195  			testWaitNoopUntilDone(t, waitFor, makeReq)
   196  		})
   197  
   198  		t.Run("waitForDistinguished", func(t *testing.T) {
   199  			testWaitPush(t, waitForDistinguished, makeReq, reqHeaderTS)
   200  		})
   201  
   202  		t.Run("waitElsewhere", func(t *testing.T) {
   203  			testWaitPush(t, waitElsewhere, makeReq, reqHeaderTS)
   204  		})
   205  
   206  		t.Run("waitSelf", func(t *testing.T) {
   207  			t.Log("waitSelf is not possible for non-transactional request")
   208  		})
   209  
   210  		t.Run("doneWaiting", func(t *testing.T) {
   211  			w, _, g := setupLockTableWaiterTest()
   212  			defer w.stopper.Stop(ctx)
   213  
   214  			g.state = waitingState{kind: doneWaiting}
   215  			g.notify()
   216  
   217  			err := w.WaitOn(ctx, makeReq(), g)
   218  			require.Nil(t, err)
   219  		})
   220  	})
   221  
   222  	t.Run("ctx done", func(t *testing.T) {
   223  		w, _, g := setupLockTableWaiterTest()
   224  		defer w.stopper.Stop(ctx)
   225  
   226  		ctxWithCancel, cancel := context.WithCancel(ctx)
   227  		go cancel()
   228  
   229  		err := w.WaitOn(ctxWithCancel, makeReq(), g)
   230  		require.NotNil(t, err)
   231  		require.Equal(t, context.Canceled.Error(), err.GoError().Error())
   232  	})
   233  
   234  	t.Run("stopper quiesce", func(t *testing.T) {
   235  		w, _, g := setupLockTableWaiterTest()
   236  		defer w.stopper.Stop(ctx)
   237  
   238  		go func() {
   239  			w.stopper.Quiesce(ctx)
   240  		}()
   241  
   242  		err := w.WaitOn(ctx, makeReq(), g)
   243  		require.NotNil(t, err)
   244  		require.IsType(t, &roachpb.NodeUnavailableError{}, err.GetDetail())
   245  	})
   246  }
   247  
   248  func testWaitPush(t *testing.T, k waitKind, makeReq func() Request, expPushTS hlc.Timestamp) {
   249  	ctx := context.Background()
   250  	keyA := roachpb.Key("keyA")
   251  	testutils.RunTrueAndFalse(t, "lockHeld", func(t *testing.T, lockHeld bool) {
   252  		testutils.RunTrueAndFalse(t, "waitAsWrite", func(t *testing.T, waitAsWrite bool) {
   253  			w, ir, g := setupLockTableWaiterTest()
   254  			defer w.stopper.Stop(ctx)
   255  			pusheeTxn := makeTxnProto("pushee")
   256  
   257  			req := makeReq()
   258  			g.state = waitingState{
   259  				kind:        k,
   260  				txn:         &pusheeTxn.TxnMeta,
   261  				key:         keyA,
   262  				held:        lockHeld,
   263  				guardAccess: spanset.SpanReadOnly,
   264  			}
   265  			if waitAsWrite {
   266  				g.state.guardAccess = spanset.SpanReadWrite
   267  			}
   268  			g.notify()
   269  
   270  			// waitElsewhere does not cause a push if the lock is not held.
   271  			// It returns immediately.
   272  			if k == waitElsewhere && !lockHeld {
   273  				err := w.WaitOn(ctx, req, g)
   274  				require.Nil(t, err)
   275  				return
   276  			}
   277  
   278  			// Non-transactional requests do not push reservations, only locks.
   279  			// They wait for doneWaiting.
   280  			if req.Txn == nil && !lockHeld {
   281  				defer notifyUntilDone(t, g)()
   282  				err := w.WaitOn(ctx, req, g)
   283  				require.Nil(t, err)
   284  				return
   285  			}
   286  
   287  			ir.pushTxn = func(
   288  				_ context.Context,
   289  				pusheeArg *enginepb.TxnMeta,
   290  				h roachpb.Header,
   291  				pushType roachpb.PushTxnType,
   292  			) (*roachpb.Transaction, *Error) {
   293  				require.Equal(t, &pusheeTxn.TxnMeta, pusheeArg)
   294  				require.Equal(t, req.Txn, h.Txn)
   295  				require.Equal(t, expPushTS, h.Timestamp)
   296  				if waitAsWrite || !lockHeld {
   297  					require.Equal(t, roachpb.PUSH_ABORT, pushType)
   298  				} else {
   299  					require.Equal(t, roachpb.PUSH_TIMESTAMP, pushType)
   300  				}
   301  
   302  				resp := &roachpb.Transaction{TxnMeta: *pusheeArg, Status: roachpb.ABORTED}
   303  
   304  				// If the lock is held, we'll try to resolve it now that
   305  				// we know the holder is ABORTED. Otherwide, immediately
   306  				// tell the request to stop waiting.
   307  				if lockHeld {
   308  					ir.resolveIntent = func(_ context.Context, intent roachpb.LockUpdate) *Error {
   309  						require.Equal(t, keyA, intent.Key)
   310  						require.Equal(t, pusheeTxn.ID, intent.Txn.ID)
   311  						require.Equal(t, roachpb.ABORTED, intent.Status)
   312  						g.state = waitingState{kind: doneWaiting}
   313  						g.notify()
   314  						return nil
   315  					}
   316  				} else {
   317  					g.state = waitingState{kind: doneWaiting}
   318  					g.notify()
   319  				}
   320  				return resp, nil
   321  			}
   322  
   323  			err := w.WaitOn(ctx, req, g)
   324  			require.Nil(t, err)
   325  		})
   326  	})
   327  }
   328  
   329  func testWaitNoopUntilDone(t *testing.T, k waitKind, makeReq func() Request) {
   330  	ctx := context.Background()
   331  	w, _, g := setupLockTableWaiterTest()
   332  	defer w.stopper.Stop(ctx)
   333  
   334  	g.state = waitingState{kind: k}
   335  	g.notify()
   336  	defer notifyUntilDone(t, g)()
   337  
   338  	err := w.WaitOn(ctx, makeReq(), g)
   339  	require.Nil(t, err)
   340  }
   341  
   342  func notifyUntilDone(t *testing.T, g *mockLockTableGuard) func() {
   343  	// Set up an observer channel to detect when the current
   344  	// waiting state is observed.
   345  	g.stateObserved = make(chan struct{})
   346  	done := make(chan struct{})
   347  	go func() {
   348  		<-g.stateObserved
   349  		g.notify()
   350  		<-g.stateObserved
   351  		g.state = waitingState{kind: doneWaiting}
   352  		g.notify()
   353  		<-g.stateObserved
   354  		close(done)
   355  	}()
   356  	return func() { <-done }
   357  }
   358  
   359  // TestLockTableWaiterIntentResolverError tests that the lockTableWaiter
   360  // propagates errors from its intent resolver when it pushes transactions
   361  // or resolves their intents.
   362  func TestLockTableWaiterIntentResolverError(t *testing.T) {
   363  	defer leaktest.AfterTest(t)()
   364  	ctx := context.Background()
   365  	w, ir, g := setupLockTableWaiterTest()
   366  	defer w.stopper.Stop(ctx)
   367  
   368  	err1 := roachpb.NewErrorf("error1")
   369  	err2 := roachpb.NewErrorf("error2")
   370  
   371  	txn := makeTxnProto("request")
   372  	req := Request{
   373  		Txn:       &txn,
   374  		Timestamp: txn.ReadTimestamp,
   375  	}
   376  
   377  	// Test with both synchronous and asynchronous pushes.
   378  	// See the comments on pushLockTxn and pushRequestTxn.
   379  	testutils.RunTrueAndFalse(t, "sync", func(t *testing.T, sync bool) {
   380  		keyA := roachpb.Key("keyA")
   381  		pusheeTxn := makeTxnProto("pushee")
   382  		lockHeld := sync
   383  		g.state = waitingState{
   384  			kind:        waitForDistinguished,
   385  			txn:         &pusheeTxn.TxnMeta,
   386  			key:         keyA,
   387  			held:        lockHeld,
   388  			guardAccess: spanset.SpanReadWrite,
   389  		}
   390  
   391  		// Errors are propagated when observed while pushing transactions.
   392  		g.notify()
   393  		ir.pushTxn = func(
   394  			_ context.Context, _ *enginepb.TxnMeta, _ roachpb.Header, _ roachpb.PushTxnType,
   395  		) (*roachpb.Transaction, *Error) {
   396  			return nil, err1
   397  		}
   398  		err := w.WaitOn(ctx, req, g)
   399  		require.Equal(t, err1, err)
   400  
   401  		if lockHeld {
   402  			// Errors are propagated when observed while resolving intents.
   403  			g.notify()
   404  			ir.pushTxn = func(
   405  				_ context.Context, _ *enginepb.TxnMeta, _ roachpb.Header, _ roachpb.PushTxnType,
   406  			) (*roachpb.Transaction, *Error) {
   407  				return &pusheeTxn, nil
   408  			}
   409  			ir.resolveIntent = func(_ context.Context, intent roachpb.LockUpdate) *Error {
   410  				return err2
   411  			}
   412  			err = w.WaitOn(ctx, req, g)
   413  			require.Equal(t, err2, err)
   414  		}
   415  	})
   416  }
   417  
   418  // TestLockTableWaiterDeferredIntentResolverError tests that the lockTableWaiter
   419  // propagates errors from its intent resolver when it resolves intent batches.
   420  func TestLockTableWaiterDeferredIntentResolverError(t *testing.T) {
   421  	defer leaktest.AfterTest(t)()
   422  	ctx := context.Background()
   423  	w, ir, g := setupLockTableWaiterTest()
   424  	defer w.stopper.Stop(ctx)
   425  
   426  	txn := makeTxnProto("request")
   427  	req := Request{
   428  		Txn:       &txn,
   429  		Timestamp: txn.ReadTimestamp,
   430  	}
   431  	keyA := roachpb.Key("keyA")
   432  	pusheeTxn := makeTxnProto("pushee")
   433  
   434  	// Add the conflicting txn to the finalizedTxnCache so that the request
   435  	// avoids the transaction record push and defers the intent resolution.
   436  	pusheeTxn.Status = roachpb.ABORTED
   437  	w.finalizedTxnCache.add(&pusheeTxn)
   438  
   439  	g.state = waitingState{
   440  		kind:        waitForDistinguished,
   441  		txn:         &pusheeTxn.TxnMeta,
   442  		key:         keyA,
   443  		held:        true,
   444  		guardAccess: spanset.SpanReadWrite,
   445  	}
   446  	g.notify()
   447  
   448  	// Errors are propagated when observed while resolving batches of intents.
   449  	err1 := roachpb.NewErrorf("error1")
   450  	ir.resolveIntents = func(_ context.Context, intents []roachpb.LockUpdate) *Error {
   451  		require.Len(t, intents, 1)
   452  		require.Equal(t, keyA, intents[0].Key)
   453  		require.Equal(t, pusheeTxn.ID, intents[0].Txn.ID)
   454  		require.Equal(t, roachpb.ABORTED, intents[0].Status)
   455  		return err1
   456  	}
   457  	err := w.WaitOn(ctx, req, g)
   458  	require.Equal(t, err1, err)
   459  }
   460  
   461  func TestTxnCache(t *testing.T) {
   462  	var c txnCache
   463  	const overflow = 4
   464  	var txns [len(c.txns) + overflow]roachpb.Transaction
   465  	for i := range txns {
   466  		txns[i] = makeTxnProto(fmt.Sprintf("txn %d", i))
   467  	}
   468  
   469  	// Add each txn to the cache. Observe LRU eviction policy.
   470  	for i := range txns {
   471  		txn := &txns[i]
   472  		c.add(txn)
   473  		for j, txnInCache := range c.txns {
   474  			if j <= i {
   475  				require.Equal(t, &txns[i-j], txnInCache)
   476  			} else {
   477  				require.Nil(t, txnInCache)
   478  			}
   479  		}
   480  	}
   481  
   482  	// Access each txn in the cache in reverse order.
   483  	// Should reverse the order of the cache because of LRU policy.
   484  	for i := len(txns) - 1; i >= 0; i-- {
   485  		txn := &txns[i]
   486  		txnInCache, ok := c.get(txn.ID)
   487  		if i < overflow {
   488  			// Expect overflow.
   489  			require.Nil(t, txnInCache)
   490  			require.False(t, ok)
   491  		} else {
   492  			// Should be in cache.
   493  			require.Equal(t, txn, txnInCache)
   494  			require.True(t, ok)
   495  		}
   496  	}
   497  
   498  	// Cache should be in order again.
   499  	for i, txnInCache := range c.txns {
   500  		require.Equal(t, &txns[i+overflow], txnInCache)
   501  	}
   502  }
   503  
   504  func BenchmarkTxnCache(b *testing.B) {
   505  	rng := rand.New(rand.NewSource(timeutil.Now().UnixNano()))
   506  	var c txnCache
   507  	var txns [len(c.txns) + 4]roachpb.Transaction
   508  	for i := range txns {
   509  		txns[i] = makeTxnProto(fmt.Sprintf("txn %d", i))
   510  	}
   511  	txnOps := make([]*roachpb.Transaction, b.N)
   512  	for i := range txnOps {
   513  		txnOps[i] = &txns[rng.Intn(len(txns))]
   514  	}
   515  	b.ResetTimer()
   516  	for i, txnOp := range txnOps {
   517  		if i%2 == 0 {
   518  			c.add(txnOp)
   519  		} else {
   520  			_, _ = c.get(txnOp.ID)
   521  		}
   522  	}
   523  }