github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvclient/kvcoord/txn_interceptor_committer_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 17 "github.com/cockroachdb/cockroach/pkg/roachpb" 18 "github.com/cockroachdb/cockroach/pkg/settings/cluster" 19 "github.com/cockroachdb/cockroach/pkg/testutils" 20 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 21 "github.com/cockroachdb/cockroach/pkg/util/stop" 22 "github.com/cockroachdb/cockroach/pkg/util/syncutil" 23 "github.com/stretchr/testify/require" 24 ) 25 26 func makeMockTxnCommitter() (txnCommitter, *mockLockedSender) { 27 mockSender := &mockLockedSender{} 28 return txnCommitter{ 29 st: cluster.MakeTestingClusterSettings(), 30 stopper: stop.NewStopper(), 31 wrapped: mockSender, 32 mu: new(syncutil.Mutex), 33 }, mockSender 34 } 35 36 // TestTxnCommitterElideEndTxn tests that EndTxn requests for read-only 37 // transactions are removed from their batches because they are not necessary. 38 // The test verifies the case where the EndTxn request is part of a batch with 39 // other requests and the case where it is alone in its batch. 40 func TestTxnCommitterElideEndTxn(t *testing.T) { 41 defer leaktest.AfterTest(t)() 42 ctx := context.Background() 43 tc, mockSender := makeMockTxnCommitter() 44 defer tc.stopper.Stop(ctx) 45 46 txn := makeTxnProto() 47 keyA := roachpb.Key("a") 48 49 // Test with both commits and rollbacks. 50 testutils.RunTrueAndFalse(t, "commit", func(t *testing.T, commit bool) { 51 expStatus := roachpb.COMMITTED 52 if !commit { 53 expStatus = roachpb.ABORTED 54 } 55 56 // Test the case where the EndTxn request is part of a larger batch of 57 // requests. 58 var ba roachpb.BatchRequest 59 ba.Header = roachpb.Header{Txn: &txn} 60 ba.Add(&roachpb.GetRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}}) 61 ba.Add(&roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}}) 62 ba.Add(&roachpb.EndTxnRequest{Commit: commit, LockSpans: nil}) 63 64 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 65 require.Len(t, ba.Requests, 2) 66 require.IsType(t, &roachpb.GetRequest{}, ba.Requests[0].GetInner()) 67 require.IsType(t, &roachpb.PutRequest{}, ba.Requests[1].GetInner()) 68 69 br := ba.CreateReply() 70 br.Txn = ba.Txn 71 // The Sender did not receive an EndTxn request, so it keeps the Txn 72 // status as PENDING. 73 br.Txn.Status = roachpb.PENDING 74 return br, nil 75 }) 76 77 br, pErr := tc.SendLocked(ctx, ba) 78 require.Nil(t, pErr) 79 require.NotNil(t, br) 80 require.NotNil(t, br.Txn) 81 require.Equal(t, expStatus, br.Txn.Status) 82 83 // Test the case where the EndTxn request is alone. 84 ba.Requests = nil 85 ba.Add(&roachpb.EndTxnRequest{Commit: commit, LockSpans: nil}) 86 87 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 88 require.Fail(t, "should not have issued batch request", ba) 89 return nil, nil 90 }) 91 92 br, pErr = tc.SendLocked(ctx, ba) 93 require.Nil(t, pErr) 94 require.NotNil(t, br) 95 require.NotNil(t, br.Txn) 96 require.Equal(t, expStatus, br.Txn.Status) 97 }) 98 } 99 100 // TestTxnCommitterAttachesTxnKey tests that the txnCommitter attaches the 101 // transaction key to committing and aborting EndTxn requests. 102 func TestTxnCommitterAttachesTxnKey(t *testing.T) { 103 defer leaktest.AfterTest(t)() 104 ctx := context.Background() 105 tc, mockSender := makeMockTxnCommitter() 106 defer tc.stopper.Stop(ctx) 107 108 txn := makeTxnProto() 109 keyA := roachpb.Key("a") 110 111 // Attach LockSpans to each EndTxn request to avoid the elided EndTxn 112 // optimization. 113 intents := []roachpb.Span{{Key: keyA}} 114 115 // Verify that the txn key is attached to committing EndTxn requests. 116 var ba roachpb.BatchRequest 117 ba.Header = roachpb.Header{Txn: &txn} 118 ba.Add(&roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}}) 119 ba.Add(&roachpb.EndTxnRequest{Commit: true, LockSpans: intents}) 120 121 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 122 require.Len(t, ba.Requests, 2) 123 require.Equal(t, keyA, ba.Requests[0].GetInner().Header().Key) 124 require.Equal(t, roachpb.Key(txn.Key), ba.Requests[1].GetInner().Header().Key) 125 126 br := ba.CreateReply() 127 br.Txn = ba.Txn 128 br.Txn.Status = roachpb.COMMITTED 129 return br, nil 130 }) 131 132 br, pErr := tc.SendLocked(ctx, ba) 133 require.Nil(t, pErr) 134 require.NotNil(t, br) 135 136 // Verify that the txn key is attached to aborting EndTxn requests. 137 ba.Requests = nil 138 ba.Add(&roachpb.EndTxnRequest{Commit: false, LockSpans: intents}) 139 140 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 141 require.Len(t, ba.Requests, 1) 142 require.Equal(t, roachpb.Key(txn.Key), ba.Requests[0].GetInner().Header().Key) 143 144 br = ba.CreateReply() 145 br.Txn = ba.Txn 146 br.Txn.Status = roachpb.ABORTED 147 return br, nil 148 }) 149 150 br, pErr = tc.SendLocked(ctx, ba) 151 require.Nil(t, pErr) 152 require.NotNil(t, br) 153 } 154 155 // TestTxnCommitterStripsInFlightWrites tests that the txnCommitter strips the 156 // pipelined writes that have yet to be proven and the new writes that are part 157 // of the same batch as an EndTxn request from the in-flight write set when a 158 // parallel commit is not desired. It also tests that it keeps the in-flight 159 // writes attached to the EndTxn request when a parallel commit is desired. 160 func TestTxnCommitterStripsInFlightWrites(t *testing.T) { 161 defer leaktest.AfterTest(t)() 162 163 ctx := context.Background() 164 tc, mockSender := makeMockTxnCommitter() 165 defer tc.stopper.Stop(ctx) 166 167 // Start with parallel commits disabled. Should NOT attach in-flight writes. 168 parallelCommitsEnabled.Override(&tc.st.SV, false) 169 170 txn := makeTxnProto() 171 keyA, keyB := roachpb.Key("a"), roachpb.Key("b") 172 173 // Verify that the QueryIntent and the Put are both attached as lock spans 174 // to the committing EndTxn request when expected. 175 var ba roachpb.BatchRequest 176 ba.Header = roachpb.Header{Txn: &txn} 177 qiArgs := roachpb.QueryIntentRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}} 178 putArgs := roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyB}} 179 etArgs := roachpb.EndTxnRequest{Commit: true} 180 qiArgs.Txn.Sequence = 1 181 putArgs.Sequence = 2 182 etArgs.Sequence = 3 183 etArgs.InFlightWrites = []roachpb.SequencedWrite{ 184 {Key: keyA, Sequence: 1}, {Key: keyB, Sequence: 2}, 185 } 186 etArgsCopy := etArgs 187 ba.Add(&putArgs, &qiArgs, &etArgsCopy) 188 189 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 190 require.Len(t, ba.Requests, 3) 191 require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[2].GetInner()) 192 193 et := ba.Requests[2].GetInner().(*roachpb.EndTxnRequest) 194 require.True(t, et.Commit) 195 require.Len(t, et.LockSpans, 2) 196 require.Equal(t, []roachpb.Span{{Key: keyA}, {Key: keyB}}, et.LockSpans) 197 require.Len(t, et.InFlightWrites, 0) 198 199 br := ba.CreateReply() 200 br.Txn = ba.Txn 201 br.Txn.Status = roachpb.COMMITTED 202 return br, nil 203 }) 204 205 br, pErr := tc.SendLocked(ctx, ba) 206 require.Nil(t, pErr) 207 require.NotNil(t, br) 208 209 // Enable parallel commits and send the same batch. Should attach in-flight writes. 210 parallelCommitsEnabled.Override(&tc.st.SV, true) 211 212 ba.Requests = nil 213 etArgsCopy = etArgs 214 ba.Add(&putArgs, &qiArgs, &etArgsCopy) 215 216 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 217 require.Len(t, ba.Requests, 3) 218 require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[2].GetInner()) 219 220 et := ba.Requests[2].GetInner().(*roachpb.EndTxnRequest) 221 require.True(t, et.Commit) 222 require.Len(t, et.InFlightWrites, 2) 223 require.Equal(t, roachpb.SequencedWrite{Key: keyA, Sequence: 1}, et.InFlightWrites[0]) 224 require.Equal(t, roachpb.SequencedWrite{Key: keyB, Sequence: 2}, et.InFlightWrites[1]) 225 226 br = ba.CreateReply() 227 br.Txn = ba.Txn 228 br.Txn.Status = roachpb.COMMITTED 229 return br, nil 230 }) 231 232 br, pErr = tc.SendLocked(ctx, ba) 233 require.Nil(t, pErr) 234 require.NotNil(t, br) 235 236 // Send the same batch but with an EndTxn containing a commit trigger. 237 // In-flight writes should not be attached because commit triggers disable 238 // parallel commits. 239 ba.Requests = nil 240 etArgsWithTrigger := etArgs 241 etArgsWithTrigger.InternalCommitTrigger = &roachpb.InternalCommitTrigger{ 242 ModifiedSpanTrigger: &roachpb.ModifiedSpanTrigger{ 243 SystemConfigSpan: true, 244 }, 245 } 246 ba.Add(&putArgs, &qiArgs, &etArgsWithTrigger) 247 248 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 249 require.Len(t, ba.Requests, 3) 250 require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[2].GetInner()) 251 252 et := ba.Requests[2].GetInner().(*roachpb.EndTxnRequest) 253 require.True(t, et.Commit) 254 require.Len(t, et.LockSpans, 2) 255 require.Equal(t, []roachpb.Span{{Key: keyA}, {Key: keyB}}, et.LockSpans) 256 require.Len(t, et.InFlightWrites, 0) 257 258 br = ba.CreateReply() 259 br.Txn = ba.Txn 260 br.Txn.Status = roachpb.COMMITTED 261 return br, nil 262 }) 263 264 br, pErr = tc.SendLocked(ctx, ba) 265 require.Nil(t, pErr) 266 require.NotNil(t, br) 267 268 // Send the same batch but with a ranged write instead of a point write. 269 // In-flight writes should not be attached because ranged writes cannot 270 // be parallelized with a commit. 271 ba.Requests = nil 272 delRngArgs := roachpb.DeleteRangeRequest{RequestHeader: roachpb.RequestHeader{Key: keyA, EndKey: keyB}} 273 delRngArgs.Sequence = 2 274 etArgsWithRangedIntentSpan := etArgs 275 etArgsWithRangedIntentSpan.LockSpans = []roachpb.Span{{Key: keyA, EndKey: keyB}} 276 etArgsWithRangedIntentSpan.InFlightWrites = []roachpb.SequencedWrite{{Key: keyA, Sequence: 1}} 277 ba.Add(&delRngArgs, &qiArgs, &etArgsWithRangedIntentSpan) 278 279 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 280 require.Len(t, ba.Requests, 3) 281 require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[2].GetInner()) 282 283 et := ba.Requests[2].GetInner().(*roachpb.EndTxnRequest) 284 require.True(t, et.Commit) 285 require.Len(t, et.LockSpans, 1) 286 require.Equal(t, []roachpb.Span{{Key: keyA, EndKey: keyB}}, et.LockSpans) 287 require.Len(t, et.InFlightWrites, 0) 288 289 br = ba.CreateReply() 290 br.Txn = ba.Txn 291 br.Txn.Status = roachpb.COMMITTED 292 return br, nil 293 }) 294 295 br, pErr = tc.SendLocked(ctx, ba) 296 require.Nil(t, pErr) 297 require.NotNil(t, br) 298 299 // Send the same batch but with a point read instead of a point write. 300 // In-flight writes should not be attached because read-only requests 301 // cannot be parallelized with a commit. 302 ba.Requests = nil 303 getArgs := roachpb.GetRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}} 304 getArgs.Sequence = 2 305 etArgsCopy = etArgs 306 ba.Add(&getArgs, &qiArgs, &etArgsCopy) 307 308 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 309 require.Len(t, ba.Requests, 3) 310 require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[2].GetInner()) 311 312 et := ba.Requests[2].GetInner().(*roachpb.EndTxnRequest) 313 require.True(t, et.Commit) 314 require.Len(t, et.LockSpans, 2) 315 require.Equal(t, []roachpb.Span{{Key: keyA}, {Key: keyB}}, et.LockSpans) 316 require.Len(t, et.InFlightWrites, 0) 317 318 br = ba.CreateReply() 319 br.Txn = ba.Txn 320 br.Txn.Status = roachpb.COMMITTED 321 return br, nil 322 }) 323 324 br, pErr = tc.SendLocked(ctx, ba) 325 require.Nil(t, pErr) 326 require.NotNil(t, br) 327 } 328 329 // TestTxnCommitterAsyncExplicitCommitTask verifies that when txnCommitter 330 // performs a parallel commit and receives a STAGING transaction status, 331 // it launches an async task to make the transaction commit explicit. 332 func TestTxnCommitterAsyncExplicitCommitTask(t *testing.T) { 333 defer leaktest.AfterTest(t)() 334 ctx := context.Background() 335 tc, mockSender := makeMockTxnCommitter() 336 defer tc.stopper.Stop(ctx) 337 338 txn := makeTxnProto() 339 keyA := roachpb.Key("a") 340 341 // Verify that the Put is attached as in-flight write to the committing 342 // EndTxn request. 343 var ba roachpb.BatchRequest 344 ba.Header = roachpb.Header{Txn: &txn} 345 putArgs := roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}} 346 etArgs := roachpb.EndTxnRequest{Commit: true} 347 putArgs.Sequence = 1 348 etArgs.Sequence = 2 349 etArgs.InFlightWrites = []roachpb.SequencedWrite{{Key: keyA, Sequence: 1}} 350 ba.Add(&putArgs, &etArgs) 351 352 // Set the CanForwardReadTimestamp and CanCommitAtHigherTimestamp flags so 353 // we can make sure that these are propagated to the async explicit commit 354 // task. 355 ba.Header.CanForwardReadTimestamp = true 356 etArgs.CanCommitAtHigherTimestamp = true 357 358 explicitCommitCh := make(chan struct{}) 359 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 360 require.Len(t, ba.Requests, 2) 361 require.True(t, ba.CanForwardReadTimestamp) 362 require.IsType(t, &roachpb.PutRequest{}, ba.Requests[0].GetInner()) 363 require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[1].GetInner()) 364 365 et := ba.Requests[1].GetInner().(*roachpb.EndTxnRequest) 366 require.True(t, et.Commit) 367 require.True(t, et.CanCommitAtHigherTimestamp) 368 require.Len(t, et.InFlightWrites, 1) 369 require.Equal(t, roachpb.SequencedWrite{Key: keyA, Sequence: 1}, et.InFlightWrites[0]) 370 371 br := ba.CreateReply() 372 br.Txn = ba.Txn 373 br.Txn.Status = roachpb.STAGING 374 br.Responses[1].GetInner().(*roachpb.EndTxnResponse).StagingTimestamp = br.Txn.WriteTimestamp 375 376 // Before returning, mock out the sender again to test against the async 377 // task that should be sent to make the implicit txn commit explicit. 378 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 379 defer close(explicitCommitCh) 380 require.Len(t, ba.Requests, 1) 381 require.True(t, ba.CanForwardReadTimestamp) 382 require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[0].GetInner()) 383 384 et := ba.Requests[0].GetInner().(*roachpb.EndTxnRequest) 385 require.True(t, et.Commit) 386 require.True(t, et.CanCommitAtHigherTimestamp) 387 require.Len(t, et.InFlightWrites, 0) 388 389 br = ba.CreateReply() 390 br.Txn = ba.Txn 391 br.Txn.Status = roachpb.COMMITTED 392 return br, nil 393 }) 394 return br, nil 395 }) 396 397 br, pErr := tc.SendLocked(ctx, ba) 398 require.Nil(t, pErr) 399 require.NotNil(t, br) 400 401 // Wait until the explicit commit succeeds. 402 <-explicitCommitCh 403 } 404 405 // TestTxnCommitterRetryAfterStaging verifies that txnCommitter returns a retry 406 // error when a write performed in parallel with staging a transaction is pushed 407 // to a timestamp above the staging timestamp. 408 func TestTxnCommitterRetryAfterStaging(t *testing.T) { 409 defer leaktest.AfterTest(t)() 410 ctx := context.Background() 411 testutils.RunTrueAndFalse(t, "WriteTooOld", func(t *testing.T, writeTooOld bool) { 412 tc, mockSender := makeMockTxnCommitter() 413 defer tc.stopper.Stop(ctx) 414 415 txn := makeTxnProto() 416 keyA := roachpb.Key("a") 417 418 var ba roachpb.BatchRequest 419 ba.Header = roachpb.Header{Txn: &txn} 420 putArgs := roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}} 421 etArgs := roachpb.EndTxnRequest{Commit: true} 422 putArgs.Sequence = 1 423 etArgs.Sequence = 2 424 etArgs.InFlightWrites = []roachpb.SequencedWrite{{Key: keyA, Sequence: 1}} 425 ba.Add(&putArgs, &etArgs) 426 427 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 428 require.Len(t, ba.Requests, 2) 429 require.IsType(t, &roachpb.PutRequest{}, ba.Requests[0].GetInner()) 430 require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[1].GetInner()) 431 432 et := ba.Requests[1].GetInner().(*roachpb.EndTxnRequest) 433 require.True(t, et.Commit) 434 require.Len(t, et.InFlightWrites, 1) 435 require.Equal(t, roachpb.SequencedWrite{Key: keyA, Sequence: 1}, et.InFlightWrites[0]) 436 437 br := ba.CreateReply() 438 br.Txn = ba.Txn 439 br.Txn.Status = roachpb.STAGING 440 br.Responses[1].GetInner().(*roachpb.EndTxnResponse).StagingTimestamp = br.Txn.WriteTimestamp 441 442 // Pretend the PutRequest was split and sent to a different Range. It 443 // could hit the timestamp cache, or a WriteTooOld error (which sets the 444 // WriteTooOld flag). The intent will be written but the response 445 // transaction's timestamp will be larger than the staging timestamp. 446 br.Txn.WriteTooOld = writeTooOld 447 br.Txn.WriteTimestamp = br.Txn.WriteTimestamp.Add(1, 0) 448 return br, nil 449 }) 450 451 br, pErr := tc.SendLocked(ctx, ba) 452 require.Nil(t, br) 453 require.NotNil(t, pErr) 454 require.IsType(t, &roachpb.TransactionRetryError{}, pErr.GetDetail()) 455 expReason := roachpb.RETRY_SERIALIZABLE 456 if writeTooOld { 457 expReason = roachpb.RETRY_WRITE_TOO_OLD 458 } 459 require.Equal(t, expReason, pErr.GetDetail().(*roachpb.TransactionRetryError).Reason) 460 }) 461 } 462 463 // Test that parallel commits are inhibited on retries (i.e. after a successful 464 // refresh caused by a parallel-commit batch). See comments in the interceptor 465 // about why this is necessary. 466 func TestTxnCommitterNoParallelCommitsOnRetry(t *testing.T) { 467 defer leaktest.AfterTest(t)() 468 ctx := context.Background() 469 tc, mockSender := makeMockTxnCommitter() 470 defer tc.stopper.Stop(ctx) 471 472 txn := makeTxnProto() 473 keyA := roachpb.Key("a") 474 475 var ba roachpb.BatchRequest 476 ba.Header = roachpb.Header{Txn: &txn} 477 putArgs := roachpb.PutRequest{RequestHeader: roachpb.RequestHeader{Key: keyA}} 478 etArgs := roachpb.EndTxnRequest{Commit: true} 479 putArgs.Sequence = 1 480 etArgs.Sequence = 2 481 etArgs.InFlightWrites = []roachpb.SequencedWrite{{Key: keyA, Sequence: 1}} 482 483 // Pretend that this is a retry of the request (after a successful refresh). Having the key 484 // assigned is how the interceptor distinguishes retries. 485 etArgs.Key = keyA 486 487 ba.Add(&putArgs, &etArgs) 488 489 mockSender.MockSend(func(ba roachpb.BatchRequest) (*roachpb.BatchResponse, *roachpb.Error) { 490 require.Len(t, ba.Requests, 2) 491 require.IsType(t, &roachpb.PutRequest{}, ba.Requests[0].GetInner()) 492 require.IsType(t, &roachpb.EndTxnRequest{}, ba.Requests[1].GetInner()) 493 494 et := ba.Requests[1].GetInner().(*roachpb.EndTxnRequest) 495 require.True(t, et.Commit) 496 require.Len(t, et.InFlightWrites, 0, "expected parallel commit to be inhibited") 497 498 br := ba.CreateReply() 499 br.Txn = ba.Txn 500 br.Txn.Status = roachpb.COMMITTED 501 return br, nil 502 }) 503 504 _, pErr := tc.SendLocked(ctx, ba) 505 require.Nil(t, pErr) 506 }