github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/intentresolver/intent_resolver_test.go (about) 1 // Copyright 2016 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 intentresolver 12 13 import ( 14 "context" 15 "fmt" 16 "reflect" 17 "sort" 18 "sync" 19 "sync/atomic" 20 "testing" 21 "time" 22 23 "github.com/cockroachdb/cockroach/pkg/kv" 24 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/batcheval/result" 25 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverbase" 26 "github.com/cockroachdb/cockroach/pkg/roachpb" 27 "github.com/cockroachdb/cockroach/pkg/storage/enginepb" 28 "github.com/cockroachdb/cockroach/pkg/testutils" 29 "github.com/cockroachdb/cockroach/pkg/util/hlc" 30 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 31 "github.com/cockroachdb/cockroach/pkg/util/log" 32 "github.com/cockroachdb/cockroach/pkg/util/stop" 33 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 34 "github.com/cockroachdb/cockroach/pkg/util/tracing" 35 "github.com/cockroachdb/cockroach/pkg/util/uuid" 36 "github.com/cockroachdb/errors" 37 "github.com/stretchr/testify/assert" 38 ) 39 40 // TestCleanupTxnIntentsOnGCAsync exercises the code which is used to 41 // asynchronously clean up transaction intents and then transaction records. 42 // This method is invoked from the storage GC queue. 43 func TestCleanupTxnIntentsOnGCAsync(t *testing.T) { 44 defer leaktest.AfterTest(t)() 45 46 ctx, cancel := context.WithCancel(context.Background()) 47 defer cancel() 48 stopper := stop.NewStopper() 49 defer stopper.Stop(ctx) 50 clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond) 51 cfg := Config{ 52 Stopper: stopper, 53 Clock: clock, 54 } 55 type testCase struct { 56 txn *roachpb.Transaction 57 intents []roachpb.LockUpdate 58 sendFuncs *sendFuncs 59 expectPushed bool 60 expectSucceed bool 61 } 62 63 // This test creates 3 transaction for use in the below test cases. 64 // A new intent resolver is created for each test case so they operate 65 // completely independently. 66 key := roachpb.Key("a") 67 // Txn0 is in the pending state and is not old enough to have expired so the 68 // code ought to send nothing. 69 txn0 := newTransaction("txn0", key, 1, clock) 70 // Txn1 is in the pending state but is expired. 71 txn1 := newTransaction("txn1", key, 1, clock) 72 txn1.ReadTimestamp.WallTime -= int64(100 * time.Second) 73 txn1.DeprecatedOrigTimestamp = txn1.ReadTimestamp 74 txn1.LastHeartbeat = txn1.ReadTimestamp 75 // Txn2 is in the staging state and is not old enough to have expired so the 76 // code ought to send nothing. 77 txn2 := newTransaction("txn2", key, 1, clock) 78 txn2.Status = roachpb.STAGING 79 // Txn3 is in the staging state but is expired. 80 txn3 := newTransaction("txn3", key, 1, clock) 81 txn3.Status = roachpb.STAGING 82 txn3.ReadTimestamp.WallTime -= int64(100 * time.Second) 83 txn3.DeprecatedOrigTimestamp = txn3.ReadTimestamp 84 txn3.LastHeartbeat = txn3.ReadTimestamp 85 // Txn4 is in the committed state. 86 txn4 := newTransaction("txn4", key, 1, clock) 87 txn4.Status = roachpb.COMMITTED 88 cases := []*testCase{ 89 // This one has an unexpired pending transaction so it's skipped. 90 { 91 txn: txn0, 92 sendFuncs: newSendFuncs(t), 93 }, 94 // Txn1 is pending and expired so the code should attempt to push the txn. 95 // The provided sender will fail on the first request. The callback should 96 // indicate that the transaction was pushed but that the resolution was not 97 // successful. 98 { 99 txn: txn1, 100 sendFuncs: newSendFuncs(t, failSendFunc), 101 expectPushed: true, 102 }, 103 // Txn1 is pending and expired so the code should attempt to push the txn 104 // and then work to resolve its intents. The intent resolution happens in 105 // different requests for individual keys and then for spans. This case will 106 // allow the individual key intent to be resolved completely but will fail 107 // to resolve the span. The callback should indicate that the transaction 108 // has been pushed but that the garbage collection was not successful. 109 { 110 txn: txn1, 111 intents: []roachpb.LockUpdate{ 112 roachpb.MakeLockUpdate(txn1, roachpb.Span{Key: key}), 113 roachpb.MakeLockUpdate(txn1, roachpb.Span{Key: key, EndKey: roachpb.Key("b")}), 114 }, 115 sendFuncs: newSendFuncs(t, 116 singlePushTxnSendFunc(t), 117 resolveIntentsSendFunc(t), 118 failSendFunc, 119 ), 120 expectPushed: true, 121 }, 122 // Txn1 is pending and expired so the code should attempt to push the txn 123 // and then work to resolve its intents. The intent resolution happens in 124 // different requests for individual keys and then for spans. This case will 125 // allow the individual key intents to be resolved in one request and for 126 // the span request to be resolved in another. Finally it will succeed on 127 // the GCRequest. This is a positive case and the callback should indicate 128 // that the txn has both been pushed and successfully resolved. 129 { 130 txn: txn1, 131 intents: []roachpb.LockUpdate{ 132 roachpb.MakeLockUpdate(txn1, roachpb.Span{Key: key}), 133 roachpb.MakeLockUpdate(txn1, roachpb.Span{Key: roachpb.Key("aa")}), 134 roachpb.MakeLockUpdate(txn1, roachpb.Span{Key: key, EndKey: roachpb.Key("b")}), 135 }, 136 sendFuncs: func() *sendFuncs { 137 s := newSendFuncs(t) 138 s.pushFrontLocked( 139 singlePushTxnSendFunc(t), 140 resolveIntentsSendFuncs(s, 3, 2), 141 gcSendFunc(t), 142 ) 143 return s 144 }(), 145 expectPushed: true, 146 expectSucceed: true, 147 }, 148 // This one has an unexpired staging transaction so it's skipped. 149 { 150 txn: txn2, 151 sendFuncs: newSendFuncs(t), 152 }, 153 // Txn3 is staging and expired so the code should attempt to push the txn. 154 // The provided sender will fail on the first request. The callback should 155 // indicate that the transaction was pushed but that the resolution was not 156 // successful. 157 { 158 txn: txn3, 159 sendFuncs: newSendFuncs(t, failSendFunc), 160 expectPushed: true, 161 }, 162 // Txn3 is staging and expired so the code should attempt to push the txn 163 // and then work to resolve its intents. The intent resolution happens in 164 // different requests for individual keys and then for spans. This case will 165 // allow the individual key intent to be resolved completely but will fail 166 // to resolve the span. The callback should indicate that the transaction 167 // has been pushed but that the garbage collection was not successful. 168 { 169 txn: txn3, 170 intents: []roachpb.LockUpdate{ 171 roachpb.MakeLockUpdate(txn3, roachpb.Span{Key: key}), 172 roachpb.MakeLockUpdate(txn3, roachpb.Span{Key: key, EndKey: roachpb.Key("b")}), 173 }, 174 sendFuncs: newSendFuncs(t, 175 singlePushTxnSendFunc(t), 176 resolveIntentsSendFunc(t), 177 failSendFunc, 178 ), 179 expectPushed: true, 180 }, 181 // Txn3 is staging and expired so the code should attempt to push the txn 182 // and then work to resolve its intents. The intent resolution happens in 183 // different requests for individual keys and then for spans. This case will 184 // allow the individual key intents to be resolved in one request and for 185 // the span request to be resolved in another. Finally it will succeed on 186 // the GCRequest. This is a positive case and the callback should indicate 187 // that the txn has both been pushed and successfully resolved. 188 { 189 txn: txn3, 190 intents: []roachpb.LockUpdate{ 191 roachpb.MakeLockUpdate(txn3, roachpb.Span{Key: key}), 192 roachpb.MakeLockUpdate(txn3, roachpb.Span{Key: roachpb.Key("aa")}), 193 roachpb.MakeLockUpdate(txn3, roachpb.Span{Key: key, EndKey: roachpb.Key("b")}), 194 }, 195 sendFuncs: func() *sendFuncs { 196 s := newSendFuncs(t) 197 s.pushFrontLocked( 198 singlePushTxnSendFunc(t), 199 resolveIntentsSendFuncs(s, 3, 2), 200 gcSendFunc(t), 201 ) 202 return s 203 }(), 204 expectPushed: true, 205 expectSucceed: true, 206 }, 207 // Txn4 is committed so it should not be pushed. Also it has no intents so 208 // it should only send a GCRequest. The callback should indicate that there 209 // is no push but that the gc has occurred successfully. 210 { 211 txn: txn4, 212 intents: []roachpb.LockUpdate{}, 213 sendFuncs: newSendFuncs(t, gcSendFunc(t)), 214 expectSucceed: true, 215 }, 216 } 217 218 for _, c := range cases { 219 t.Run("", func(t *testing.T) { 220 ir := newIntentResolverWithSendFuncs(cfg, c.sendFuncs) 221 var didPush, didSucceed bool 222 done := make(chan struct{}) 223 onComplete := func(pushed, succeeded bool) { 224 didPush, didSucceed = pushed, succeeded 225 close(done) 226 } 227 err := ir.CleanupTxnIntentsOnGCAsync(ctx, 1, c.txn, c.intents, clock.Now(), onComplete) 228 if err != nil { 229 t.Fatalf("unexpected error sending async transaction") 230 } 231 <-done 232 if c.sendFuncs.len() != 0 { 233 t.Errorf("Not all send funcs called") 234 } 235 if didSucceed != c.expectSucceed { 236 t.Fatalf("unexpected success value: got %v, expected %v", didSucceed, c.expectSucceed) 237 } 238 if didPush != c.expectPushed { 239 t.Fatalf("unexpected pushed value: got %v, expected %v", didPush, c.expectPushed) 240 } 241 }) 242 } 243 } 244 245 // TestCleanupIntentsAsync verifies that CleanupIntentsAsync either runs 246 // synchronously or returns an error when there are too many concurrently 247 // running tasks. 248 func TestCleanupIntentsAsyncThrottled(t *testing.T) { 249 clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond) 250 stopper := stop.NewStopper() 251 defer stopper.Stop(context.Background()) 252 cfg := Config{ 253 Stopper: stopper, 254 Clock: clock, 255 } 256 txn := newTransaction("txn", roachpb.Key("a"), 1, clock) 257 sf := newSendFuncs(t, 258 pushTxnSendFunc(t, 1), 259 resolveIntentsSendFunc(t), 260 ) 261 ir := newIntentResolverWithSendFuncs(cfg, sf) 262 // Run defaultTaskLimit tasks which will block until blocker is closed. 263 blocker := make(chan struct{}) 264 defer close(blocker) 265 var wg sync.WaitGroup 266 wg.Add(defaultTaskLimit) 267 for i := 0; i < defaultTaskLimit; i++ { 268 if err := ir.runAsyncTask(context.Background(), false, func(context.Context) { 269 wg.Done() 270 <-blocker 271 }); err != nil { 272 t.Fatalf("Failed to run blocking async task: %+v", err) 273 } 274 } 275 wg.Wait() 276 testIntents := []roachpb.Intent{ 277 roachpb.MakeIntent(&txn.TxnMeta, roachpb.Key("a")), 278 } 279 // Running with allowSyncProcessing = false should result in an error and no 280 // requests being sent. 281 err := ir.CleanupIntentsAsync(context.Background(), testIntents, false) 282 assert.True(t, errors.Is(err, stop.ErrThrottled)) 283 // Running with allowSyncProcessing = true should result in the synchronous 284 // processing of the intents resulting in no error and the consumption of the 285 // sendFuncs. 286 err = ir.CleanupIntentsAsync(context.Background(), testIntents, true) 287 assert.Nil(t, err) 288 assert.Equal(t, sf.len(), 0) 289 } 290 291 // TestCleanupIntentsAsync verifies that CleanupIntentsAsync sends the expected 292 // requests. 293 func TestCleanupIntentsAsync(t *testing.T) { 294 defer leaktest.AfterTest(t)() 295 type testCase struct { 296 intents []roachpb.Intent 297 sendFuncs []sendFunc 298 } 299 clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond) 300 txn := newTransaction("txn", roachpb.Key("a"), 1, clock) 301 testIntents := []roachpb.Intent{ 302 roachpb.MakeIntent(&txn.TxnMeta, roachpb.Key("a")), 303 } 304 cases := []testCase{ 305 { 306 intents: testIntents, 307 sendFuncs: []sendFunc{ 308 singlePushTxnSendFunc(t), 309 resolveIntentsSendFunc(t), 310 }, 311 }, 312 { 313 intents: testIntents, 314 sendFuncs: []sendFunc{ 315 singlePushTxnSendFunc(t), 316 failSendFunc, 317 }, 318 }, 319 { 320 intents: testIntents, 321 sendFuncs: []sendFunc{ 322 failSendFunc, 323 }, 324 }, 325 } 326 for _, c := range cases { 327 t.Run("", func(t *testing.T) { 328 stopper := stop.NewStopper() 329 sf := newSendFuncs(t, c.sendFuncs...) 330 cfg := Config{ 331 Stopper: stopper, 332 Clock: clock, 333 } 334 ir := newIntentResolverWithSendFuncs(cfg, sf) 335 err := ir.CleanupIntentsAsync(context.Background(), c.intents, true) 336 sf.drain(t) 337 stopper.Stop(context.Background()) 338 assert.Nil(t, err, "error from CleanupIntentsAsync") 339 }) 340 } 341 } 342 343 // TestCleanupMultipleIntentsAsync verifies that CleanupIntentsAsync sends the 344 // expected requests when multiple intents are provided to it. 345 func TestCleanupMultipleIntentsAsync(t *testing.T) { 346 defer leaktest.AfterTest(t)() 347 ctx := context.Background() 348 clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond) 349 txn1 := newTransaction("txn1", roachpb.Key("a"), 1, clock) 350 txn2 := newTransaction("txn2", roachpb.Key("c"), 1, clock) 351 testIntents := []roachpb.Intent{ 352 roachpb.MakeIntent(&txn1.TxnMeta, roachpb.Key("a")), 353 roachpb.MakeIntent(&txn1.TxnMeta, roachpb.Key("b")), 354 roachpb.MakeIntent(&txn2.TxnMeta, roachpb.Key("c")), 355 roachpb.MakeIntent(&txn2.TxnMeta, roachpb.Key("d")), 356 } 357 358 // We expect to see a single PushTxn req for all four intents and a 359 // ResolveIntent req for each intent. However, because these requests are 360 // all async, it's unclear which order these will be issued in. Handle all 361 // orders and record the resolved intents. 362 var reqs struct { 363 syncutil.Mutex 364 pushed []string 365 resolved []string 366 } 367 pushOrResolveFunc := func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 368 switch ba.Requests[0].GetInner().Method() { 369 case roachpb.PushTxn: 370 for _, ru := range ba.Requests { 371 reqs.Lock() 372 reqs.pushed = append(reqs.pushed, string(ru.GetPushTxn().Key)) 373 reqs.Unlock() 374 } 375 return pushTxnSendFunc(t, len(ba.Requests))(ba) 376 case roachpb.ResolveIntent: 377 for _, ru := range ba.Requests { 378 reqs.Lock() 379 reqs.resolved = append(reqs.resolved, string(ru.GetResolveIntent().Key)) 380 reqs.Unlock() 381 } 382 return resolveIntentsSendFunc(t)(ba) 383 default: 384 return nil, roachpb.NewErrorf("unexpected") 385 } 386 } 387 sf := newSendFuncs(t, repeat(pushOrResolveFunc, 5)...) 388 389 stopper := stop.NewStopper() 390 cfg := Config{ 391 Stopper: stopper, 392 Clock: clock, 393 // Don't let the intent resolution requests be batched with each other. 394 // This would make it harder to determine how to drain sf. 395 TestingKnobs: kvserverbase.IntentResolverTestingKnobs{ 396 MaxIntentResolutionBatchSize: 1, 397 }, 398 } 399 ir := newIntentResolverWithSendFuncs(cfg, sf) 400 err := ir.CleanupIntentsAsync(ctx, testIntents, false) 401 sf.drain(t) 402 stopper.Stop(ctx) 403 assert.Nil(t, err) 404 405 // Both txns should be pushed and all four intents should be resolved. 406 sort.Strings(reqs.pushed) 407 sort.Strings(reqs.resolved) 408 assert.Equal(t, []string{"a", "c"}, reqs.pushed) 409 assert.Equal(t, []string{"a", "b", "c", "d"}, reqs.resolved) 410 } 411 412 func repeat(f sendFunc, n int) []sendFunc { 413 fns := make([]sendFunc, n) 414 for i := range fns { 415 fns[i] = f 416 } 417 return fns 418 } 419 420 func newSendFuncs(t *testing.T, sf ...sendFunc) *sendFuncs { 421 return &sendFuncs{t: t, sendFuncs: sf} 422 } 423 424 type sendFuncs struct { 425 t *testing.T 426 mu syncutil.Mutex 427 sendFuncs []sendFunc 428 } 429 430 func (sf *sendFuncs) len() int { 431 sf.mu.Lock() 432 defer sf.mu.Unlock() 433 return len(sf.sendFuncs) 434 } 435 436 func (sf *sendFuncs) pushFrontLocked(f ...sendFunc) { 437 sf.sendFuncs = append(f, sf.sendFuncs...) 438 } 439 440 func (sf *sendFuncs) popLocked() sendFunc { 441 if len(sf.sendFuncs) == 0 { 442 sf.t.Errorf("No send funcs left!") 443 } 444 ret := sf.sendFuncs[0] 445 sf.sendFuncs = sf.sendFuncs[1:] 446 return ret 447 } 448 449 func (sf *sendFuncs) drain(t *testing.T) { 450 testutils.SucceedsSoon(t, func() error { 451 if l := sf.len(); l > 0 { 452 return errors.Errorf("still have %d funcs to send", l) 453 } 454 return nil 455 }) 456 } 457 458 // TestTxnCleanupIntentsAsyncWithPartialRollback verifies that 459 // CleanupIntentsAsync properly forwards the ignored seqnum list in 460 // the resolve intent requests. 461 func TestCleanupTxnIntentsAsyncWithPartialRollback(t *testing.T) { 462 clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond) 463 txn := newTransaction("txn", roachpb.Key("a"), 1, clock) 464 txn.LockSpans = []roachpb.Span{ 465 {Key: roachpb.Key("a")}, 466 {Key: roachpb.Key("b"), EndKey: roachpb.Key("c")}, 467 } 468 txn.IgnoredSeqNums = []enginepb.IgnoredSeqNumRange{{Start: 1, End: 1}} 469 470 var gotResolveIntent, gotResolveIntentRange int32 471 check := func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 472 for _, r := range ba.Requests { 473 if ri, ok := r.GetInner().(*roachpb.ResolveIntentRequest); ok { 474 atomic.StoreInt32(&gotResolveIntent, 1) 475 if !reflect.DeepEqual(ri.IgnoredSeqNums, txn.IgnoredSeqNums) { 476 t.Errorf("expected ignored list %v, got %v", txn.IgnoredSeqNums, ri.IgnoredSeqNums) 477 } 478 } else if rir, ok := r.GetInner().(*roachpb.ResolveIntentRangeRequest); ok { 479 atomic.StoreInt32(&gotResolveIntentRange, 1) 480 if !reflect.DeepEqual(rir.IgnoredSeqNums, txn.IgnoredSeqNums) { 481 t.Errorf("expected ignored list %v, got %v", txn.IgnoredSeqNums, rir.IgnoredSeqNums) 482 } 483 } 484 } 485 return respForResolveIntentBatch(t, ba), nil 486 } 487 sf := newSendFuncs(t, 488 sendFunc(check), 489 sendFunc(check), 490 gcSendFunc(t), 491 ) 492 stopper := stop.NewStopper() 493 defer stopper.Stop(context.Background()) 494 cfg := Config{ 495 Stopper: stopper, 496 Clock: clock, 497 } 498 ir := newIntentResolverWithSendFuncs(cfg, sf) 499 500 intents := []result.EndTxnIntents{{Txn: txn}} 501 502 if err := ir.CleanupTxnIntentsAsync(context.Background(), 1, intents, true /*allowAsyncProcessing*/); err != nil { 503 t.Fatal(err) 504 } 505 testutils.SucceedsSoon(t, func() error { 506 if atomic.LoadInt32(&gotResolveIntent) == 0 { 507 return errors.New("still waiting for resolve intent req") 508 } 509 if atomic.LoadInt32(&gotResolveIntentRange) == 0 { 510 return errors.New("still waiting for resolve intent range req") 511 } 512 return nil 513 }) 514 } 515 516 // TestCleanupTxnIntentsAsync verifies that CleanupTxnIntentsAsync sends the 517 // expected requests. 518 func TestCleanupTxnIntentsAsync(t *testing.T) { 519 defer leaktest.AfterTest(t)() 520 type testCase struct { 521 intents []result.EndTxnIntents 522 before func(*testCase, *IntentResolver) func() 523 sendFuncs *sendFuncs 524 } 525 testEndTxnIntents := []result.EndTxnIntents{ 526 { 527 Txn: &roachpb.Transaction{ 528 TxnMeta: enginepb.TxnMeta{ 529 ID: uuid.MakeV4(), 530 MinTimestamp: hlc.Timestamp{WallTime: 123}, 531 }, 532 LockSpans: []roachpb.Span{ 533 {Key: roachpb.Key("a")}, 534 {Key: roachpb.Key("b")}, 535 {Key: roachpb.Key("c"), EndKey: roachpb.Key("d")}, 536 {Key: roachpb.Key("e"), EndKey: roachpb.Key("f")}, 537 }, 538 }, 539 }, 540 } 541 542 cases := []testCase{ 543 { 544 intents: testEndTxnIntents, 545 sendFuncs: newSendFuncs(t), 546 before: func(tc *testCase, ir *IntentResolver) func() { 547 _, f := ir.lockInFlightTxnCleanup(context.Background(), tc.intents[0].Txn.ID) 548 return f 549 }, 550 }, 551 { 552 intents: testEndTxnIntents, 553 sendFuncs: func() *sendFuncs { 554 s := newSendFuncs(t) 555 s.pushFrontLocked( 556 resolveIntentsSendFuncs(s, 4, 2), 557 gcSendFunc(t), 558 ) 559 return s 560 }(), 561 }, 562 } 563 564 for _, c := range cases { 565 t.Run("", func(t *testing.T) { 566 stopper := stop.NewStopper() 567 clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond) 568 cfg := Config{ 569 Stopper: stopper, 570 Clock: clock, 571 } 572 ir := newIntentResolverWithSendFuncs(cfg, c.sendFuncs) 573 if c.before != nil { 574 defer c.before(&c, ir)() 575 } 576 err := ir.CleanupTxnIntentsAsync(context.Background(), 1, c.intents, false) 577 testutils.SucceedsSoon(t, func() error { 578 if left := c.sendFuncs.len(); left != 0 { 579 return fmt.Errorf("still waiting for %d calls", left) 580 } 581 return nil 582 }) 583 stopper.Stop(context.Background()) 584 assert.Nil(t, err) 585 }) 586 } 587 } 588 589 // TestCleanupMultipleTxnIntentsAsync verifies that CleanupTxnIntentsAsync sends 590 // the expected requests when multiple EndTxnIntents are provided to it. 591 func TestCleanupMultipleTxnIntentsAsync(t *testing.T) { 592 defer leaktest.AfterTest(t)() 593 ctx := context.Background() 594 clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond) 595 txn1 := newTransaction("txn1", roachpb.Key("a"), 1, clock) 596 txn2 := newTransaction("txn2", roachpb.Key("c"), 1, clock) 597 testEndTxnIntents := []result.EndTxnIntents{ 598 { 599 Txn: &roachpb.Transaction{ 600 TxnMeta: txn1.TxnMeta, 601 LockSpans: []roachpb.Span{ 602 {Key: roachpb.Key("a")}, 603 {Key: roachpb.Key("b")}, 604 {Key: roachpb.Key("c"), EndKey: roachpb.Key("d")}, 605 }, 606 }, 607 }, 608 { 609 Txn: &roachpb.Transaction{ 610 TxnMeta: txn2.TxnMeta, 611 LockSpans: []roachpb.Span{ 612 {Key: roachpb.Key("e")}, 613 {Key: roachpb.Key("f")}, 614 {Key: roachpb.Key("g"), EndKey: roachpb.Key("h")}, 615 }, 616 }, 617 }, 618 } 619 620 // We expect to see a ResolveIntent req for each intent and a GC req for 621 // each txn. However, because these requests are all async, it's unclear 622 // which order these will be issued in. Handle all orders and record the 623 // GCed transaction records. 624 var reqs struct { 625 syncutil.Mutex 626 resolved []string 627 gced []string 628 } 629 resolveOrGCFunc := func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 630 if len(ba.Requests) != 1 { 631 return nil, roachpb.NewErrorf("unexpected") 632 } 633 ru := ba.Requests[0] 634 switch ru.GetInner().Method() { 635 case roachpb.ResolveIntent: 636 reqs.Lock() 637 reqs.resolved = append(reqs.resolved, string(ru.GetResolveIntent().Key)) 638 reqs.Unlock() 639 return resolveIntentsSendFunc(t)(ba) 640 case roachpb.ResolveIntentRange: 641 reqs.Lock() 642 req := ru.GetResolveIntentRange() 643 reqs.resolved = append(reqs.resolved, 644 fmt.Sprintf("%s-%s", string(req.Key), string(req.EndKey))) 645 reqs.Unlock() 646 return resolveIntentsSendFunc(t)(ba) 647 case roachpb.GC: 648 reqs.Lock() 649 reqs.gced = append(reqs.gced, string(ru.GetGc().Key)) 650 reqs.Unlock() 651 return gcSendFunc(t)(ba) 652 default: 653 return nil, roachpb.NewErrorf("unexpected") 654 } 655 } 656 sf := newSendFuncs(t, repeat(resolveOrGCFunc, 8)...) 657 658 stopper := stop.NewStopper() 659 cfg := Config{ 660 Stopper: stopper, 661 Clock: clock, 662 // Don't let the transaction record GC requests or the intent resolution 663 // requests be batched with each other. This would make it harder to 664 // determine how to drain sf. 665 TestingKnobs: kvserverbase.IntentResolverTestingKnobs{ 666 MaxGCBatchSize: 1, 667 MaxIntentResolutionBatchSize: 1, 668 }, 669 } 670 ir := newIntentResolverWithSendFuncs(cfg, sf) 671 err := ir.CleanupTxnIntentsAsync(ctx, 1, testEndTxnIntents, false) 672 sf.drain(t) 673 stopper.Stop(ctx) 674 assert.Nil(t, err) 675 676 // All four intents should be resolved and both txn records should be GCed. 677 sort.Strings(reqs.resolved) 678 sort.Strings(reqs.gced) 679 assert.Equal(t, []string{"a", "b", "c-d", "e", "f", "g-h"}, reqs.resolved) 680 assert.Equal(t, []string{"a", "c"}, reqs.gced) 681 } 682 683 // TestCleanupIntents verifies that CleanupIntents sends the expected requests 684 // and returns the appropriate errors. 685 func TestCleanupIntents(t *testing.T) { 686 defer leaktest.AfterTest(t)() 687 clock := hlc.NewClock(hlc.UnixNano, time.Nanosecond) 688 txn := newTransaction("txn", roachpb.Key("a"), roachpb.MinUserPriority, clock) 689 // Set txn.ID to a very small value so it's sorted deterministically first. 690 txn.ID = uuid.UUID{15: 0x01} 691 testIntents := []roachpb.Intent{ 692 roachpb.MakeIntent(&txn.TxnMeta, roachpb.Key("a")), 693 } 694 type testCase struct { 695 intents []roachpb.Intent 696 sendFuncs *sendFuncs 697 expectedErr bool 698 expectedNum int 699 cfg Config 700 } 701 cases := []testCase{ 702 { 703 intents: testIntents, 704 sendFuncs: newSendFuncs(t, 705 singlePushTxnSendFunc(t), 706 resolveIntentsSendFunc(t), 707 ), 708 expectedNum: 1, 709 }, 710 { 711 intents: testIntents, 712 sendFuncs: newSendFuncs(t, 713 failSendFunc, 714 ), 715 expectedErr: true, 716 }, 717 { 718 intents: append(makeTxnIntents(t, clock, 3*intentResolverBatchSize), 719 // Three intents with the same transaction will only attempt to push the 720 // txn 1 time. Hence 3 full batches plus 1 extra. 721 testIntents[0], testIntents[0], testIntents[0]), 722 sendFuncs: func() *sendFuncs { 723 sf := newSendFuncs(t) 724 sf.pushFrontLocked( // don't need to lock 725 pushTxnSendFuncs(sf, intentResolverBatchSize), 726 resolveIntentsSendFuncs(sf, 102 /* numIntents */, 2 /* minNumReqs */), 727 pushTxnSendFuncs(sf, intentResolverBatchSize), 728 resolveIntentsSendFuncs(sf, 100 /* numIntents */, 1 /* minNumReqs */), 729 pushTxnSendFuncs(sf, intentResolverBatchSize), 730 resolveIntentsSendFuncs(sf, 100 /* numIntents */, 1 /* minNumReqs */), 731 pushTxnSendFuncs(sf, 1), 732 resolveIntentsSendFuncs(sf, 1 /* numIntents */, 1 /* minNumReqs */), 733 ) 734 return sf 735 }(), 736 expectedNum: 3*intentResolverBatchSize + 3, 737 cfg: Config{ 738 MaxIntentResolutionBatchWait: -1, // disabled 739 MaxIntentResolutionBatchIdle: 1 * time.Microsecond, 740 }, 741 }, 742 } 743 stopper := stop.NewStopper() 744 defer stopper.Stop(context.Background()) 745 for _, c := range cases { 746 t.Run("", func(t *testing.T) { 747 c.cfg.Stopper = stopper 748 c.cfg.Clock = clock 749 ir := newIntentResolverWithSendFuncs(c.cfg, c.sendFuncs) 750 num, err := ir.CleanupIntents(context.Background(), c.intents, clock.Now(), roachpb.PUSH_ABORT) 751 assert.Equal(t, num, c.expectedNum, "number of resolved intents") 752 assert.Equal(t, err != nil, c.expectedErr, "error during CleanupIntents: %v", err) 753 }) 754 } 755 } 756 757 func newTransaction( 758 name string, baseKey roachpb.Key, userPriority roachpb.UserPriority, clock *hlc.Clock, 759 ) *roachpb.Transaction { 760 var offset int64 761 var now hlc.Timestamp 762 if clock != nil { 763 offset = clock.MaxOffset().Nanoseconds() 764 now = clock.Now() 765 } 766 txn := roachpb.MakeTransaction(name, baseKey, userPriority, now, offset) 767 return &txn 768 } 769 770 // makeTxnIntents creates a slice of Intent which each have a unique txn. 771 func makeTxnIntents(t *testing.T, clock *hlc.Clock, numIntents int) []roachpb.Intent { 772 ret := make([]roachpb.Intent, 0, numIntents) 773 for i := 0; i < numIntents; i++ { 774 txn := newTransaction("test", roachpb.Key("a"), 1, clock) 775 ret = append(ret, 776 roachpb.MakeIntent(&txn.TxnMeta, txn.Key)) 777 } 778 return ret 779 } 780 781 // sendFunc is a function used to control behavior for a specific request that 782 // the IntentResolver tries to send. They are used in conjunction with the below 783 // function to create an IntentResolver with a slice of sendFuncs. 784 // A library of useful sendFuncs are defined below. 785 type sendFunc func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) 786 787 func newIntentResolverWithSendFuncs(c Config, sf *sendFuncs) *IntentResolver { 788 txnSenderFactory := kv.NonTransactionalFactoryFunc( 789 func(_ context.Context, ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 790 sf.mu.Lock() 791 defer sf.mu.Unlock() 792 f := sf.popLocked() 793 return f(ba) 794 }) 795 db := kv.NewDB(log.AmbientContext{ 796 Tracer: tracing.NewTracer(), 797 }, txnSenderFactory, c.Clock) 798 c.DB = db 799 c.MaxGCBatchWait = time.Nanosecond 800 return New(c) 801 } 802 803 // pushTxnSendFuncs allows the pushing of N txns across several invocations. 804 func pushTxnSendFuncs(sf *sendFuncs, N int) sendFunc { 805 toPush := int64(N) 806 var f sendFunc 807 f = func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 808 if remaining := atomic.LoadInt64(&toPush); len(ba.Requests) > int(remaining) { 809 sf.t.Errorf("expected at most %d PushTxnRequests in batch, got %d", 810 remaining, len(ba.Requests)) 811 } 812 nowRemaining := atomic.AddInt64(&toPush, -1*int64(len(ba.Requests))) 813 if nowRemaining > 0 { 814 sf.pushFrontLocked(f) 815 } 816 return respForPushTxnBatch(sf.t, ba), nil 817 } 818 return f 819 } 820 821 func pushTxnSendFunc(t *testing.T, numPushes int) sendFunc { 822 return func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 823 if len(ba.Requests) != numPushes { 824 t.Errorf("expected %d PushTxnRequests in batch, got %d", 825 numPushes, len(ba.Requests)) 826 } 827 return respForPushTxnBatch(t, ba), nil 828 } 829 } 830 831 func singlePushTxnSendFunc(t *testing.T) sendFunc { 832 return pushTxnSendFunc(t, 1) 833 } 834 835 func resolveIntentsSendFuncs(sf *sendFuncs, numIntents int, minRequests int) sendFunc { 836 toResolve := int64(numIntents) 837 reqsSeen := int64(0) 838 var f sendFunc 839 f = func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 840 if remaining := atomic.LoadInt64(&toResolve); len(ba.Requests) > int(remaining) { 841 sf.t.Errorf("expected at most %d ResolveIntentRequests in batch, got %d", 842 remaining, len(ba.Requests)) 843 } 844 nowRemaining := atomic.AddInt64(&toResolve, -1*int64(len(ba.Requests))) 845 seen := atomic.AddInt64(&reqsSeen, 1) 846 if nowRemaining > 0 { 847 sf.pushFrontLocked(f) 848 } else if seen < int64(minRequests) { 849 sf.t.Errorf("expected at least %d requests to resolve %d intents, only saw %d", 850 minRequests, numIntents, seen) 851 } 852 return respForResolveIntentBatch(sf.t, ba), nil 853 } 854 return f 855 } 856 857 func resolveIntentsSendFunc(t *testing.T) sendFunc { 858 return func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 859 return respForResolveIntentBatch(t, ba), nil 860 } 861 } 862 863 func failSendFunc(roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 864 return nil, roachpb.NewError(fmt.Errorf("boom")) 865 } 866 867 func gcSendFunc(t *testing.T) sendFunc { 868 return func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 869 resp := &roachpb.BatchResponse{} 870 for _, r := range ba.Requests { 871 if _, ok := r.GetInner().(*roachpb.GCRequest); !ok { 872 t.Errorf("Unexpected request type %T, expected GCRequest", r.GetInner()) 873 } 874 resp.Add(&roachpb.GCResponse{}) 875 } 876 return resp, nil 877 } 878 } 879 880 func respForPushTxnBatch(t *testing.T, ba roachpb.BatchRequest) *roachpb.BatchResponse { 881 resp := &roachpb.BatchResponse{} 882 for _, r := range ba.Requests { 883 var txn enginepb.TxnMeta 884 if req, ok := r.GetInner().(*roachpb.PushTxnRequest); ok { 885 txn = req.PusheeTxn 886 } else { 887 t.Errorf("Unexpected request type %T, expected PushTxnRequest", r.GetInner()) 888 } 889 resp.Add(&roachpb.PushTxnResponse{ 890 PusheeTxn: roachpb.Transaction{ 891 Status: roachpb.ABORTED, 892 TxnMeta: txn, 893 }, 894 }) 895 } 896 return resp 897 } 898 899 func respForResolveIntentBatch(t *testing.T, ba roachpb.BatchRequest) *roachpb.BatchResponse { 900 resp := &roachpb.BatchResponse{} 901 for _, r := range ba.Requests { 902 if _, ok := r.GetInner().(*roachpb.ResolveIntentRequest); ok { 903 resp.Add(&roachpb.ResolveIntentResponse{}) 904 } else if _, ok := r.GetInner().(*roachpb.ResolveIntentRangeRequest); ok { 905 resp.Add(&roachpb.ResolveIntentRangeResponse{}) 906 } else { 907 t.Errorf("Unexpected request in batch for intent resolution: %T", r.GetInner()) 908 } 909 } 910 return resp 911 }