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 }