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 }