diff options
author | Lingzhi Deng <lingzhi.deng@mongodb.com> | 2019-07-10 13:00:33 -0400 |
---|---|---|
committer | Lingzhi Deng <lingzhi.deng@mongodb.com> | 2019-07-11 13:23:01 -0400 |
commit | a1195adf60e8086219f247be250d8928df8577ce (patch) | |
tree | e870ebd1c94fe828c22631eddec923f72464119e /src | |
parent | ab1006e0831321c6e80693d7cf42b2d90fec34ee (diff) | |
download | mongo-a1195adf60e8086219f247be250d8928df8577ce.tar.gz |
SERVER-41942: Clear partialTxnList after committing a prepared transaction during initial sync
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/repl/sync_tail.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/repl/sync_tail_test.cpp | 116 |
2 files changed, 117 insertions, 0 deletions
diff --git a/src/mongo/db/repl/sync_tail.cpp b/src/mongo/db/repl/sync_tail.cpp index 7431107d29d..4781dc71500 100644 --- a/src/mongo/db/repl/sync_tail.cpp +++ b/src/mongo/db/repl/sync_tail.cpp @@ -1245,6 +1245,7 @@ void SyncTail::_fillWriterVectors(OperationContext* opCtx, auto commitOplogEntryOpTime = op.getOpTime(); derivedOps->emplace_back(readTransactionOperationsFromOplogChain( opCtx, prevOplogEntry, partialTxnList, commitOplogEntryOpTime.getTimestamp())); + partialTxnList.clear(); } _fillWriterVectors(opCtx, &derivedOps->back(), writerVectors, derivedOps, nullptr); diff --git a/src/mongo/db/repl/sync_tail_test.cpp b/src/mongo/db/repl/sync_tail_test.cpp index 95c981f6ead..0c11fb1432b 100644 --- a/src/mongo/db/repl/sync_tail_test.cpp +++ b/src/mongo/db/repl/sync_tail_test.cpp @@ -3352,6 +3352,83 @@ TEST_F(IdempotencyTestTxns, CommitUnpreparedTransactionWithPartialTxnOps) { ASSERT_TRUE(docExists(_opCtx.get(), nss, doc2)); } +TEST_F(IdempotencyTestTxns, CommitTwoUnpreparedTransactionsWithPartialTxnOpsAtOnce) { + createCollectionWithUuid(_opCtx.get(), NamespaceString::kSessionTransactionsTableNamespace); + auto uuid = createCollectionWithUuid(_opCtx.get(), nss); + auto lsid = makeLogicalSessionId(_opCtx.get()); + TxnNumber txnNum1(1); + TxnNumber txnNum2(2); + + auto partialOp1 = partialTxn( + lsid, txnNum1, StmtId(0), OpTime(), BSON_ARRAY(makeInsertApplyOpsEntry(nss, uuid, doc))); + auto commitOp1 = + commitUnprepared(lsid, txnNum1, StmtId(1), BSONArray(), partialOp1.getOpTime()); + + // The second transaction (with a different transaction number) in the same session. + auto partialOp2 = partialTxn( + lsid, txnNum2, StmtId(0), OpTime(), BSON_ARRAY(makeInsertApplyOpsEntry(nss, uuid, doc2))); + auto commitOp2 = + commitUnprepared(lsid, txnNum2, StmtId(1), BSONArray(), partialOp2.getOpTime()); + + ASSERT_OK(ReplicationCoordinator::get(getGlobalServiceContext()) + ->setFollowerMode(MemberState::RS_RECOVERING)); + + // This also tests that we clear the partialTxnList for the session after applying the commit of + // the first transaction. Otherwise, saving operations from the second transaction to the same + // partialTxnList as the first transaction will trigger an invariant because of the mismatching + // transaction numbers. + testOpsAreIdempotent({partialOp1, commitOp1, partialOp2, commitOp2}); + + // The transaction table should only contain the second transaction of the session. + repl::checkTxnTable(_opCtx.get(), + lsid, + txnNum2, + commitOp2.getOpTime(), + *commitOp2.getWallClockTime(), + boost::none, + DurableTxnStateEnum::kCommitted); + ASSERT_TRUE(docExists(_opCtx.get(), nss, doc)); + ASSERT_TRUE(docExists(_opCtx.get(), nss, doc2)); +} + +TEST_F(IdempotencyTestTxns, CommitAndAbortTwoTransactionsWithPartialTxnOpsAtOnce) { + createCollectionWithUuid(_opCtx.get(), NamespaceString::kSessionTransactionsTableNamespace); + auto uuid = createCollectionWithUuid(_opCtx.get(), nss); + auto lsid = makeLogicalSessionId(_opCtx.get()); + TxnNumber txnNum1(1); + TxnNumber txnNum2(2); + + auto partialOp1 = partialTxn( + lsid, txnNum1, StmtId(0), OpTime(), BSON_ARRAY(makeInsertApplyOpsEntry(nss, uuid, doc))); + auto abortOp1 = abortPrepared(lsid, txnNum1, StmtId(1), partialOp1.getOpTime()); + + // The second transaction (with a different transaction number) in the same session. + auto partialOp2 = partialTxn( + lsid, txnNum2, StmtId(0), OpTime(), BSON_ARRAY(makeInsertApplyOpsEntry(nss, uuid, doc2))); + auto commitOp2 = + commitUnprepared(lsid, txnNum2, StmtId(1), BSONArray(), partialOp2.getOpTime()); + + ASSERT_OK(ReplicationCoordinator::get(getGlobalServiceContext()) + ->setFollowerMode(MemberState::RS_RECOVERING)); + + // This also tests that we clear the partialTxnList for the session after applying the abort of + // the first transaction. Otherwise, saving operations from the second transaction to the same + // partialTxnList as the first transaction will trigger an invariant because of the mismatching + // transaction numbers. + testOpsAreIdempotent({partialOp1, abortOp1, partialOp2, commitOp2}); + + // The transaction table should only contain the second transaction of the session. + repl::checkTxnTable(_opCtx.get(), + lsid, + txnNum2, + commitOp2.getOpTime(), + *commitOp2.getWallClockTime(), + boost::none, + DurableTxnStateEnum::kCommitted); + ASSERT_FALSE(docExists(_opCtx.get(), nss, doc)); + ASSERT_TRUE(docExists(_opCtx.get(), nss, doc2)); +} + TEST_F(IdempotencyTestTxns, CommitUnpreparedTransactionWithPartialTxnOpsAndDataPartiallyApplied) { createCollectionWithUuid(_opCtx.get(), NamespaceString::kSessionTransactionsTableNamespace); auto uuid = createCollectionWithUuid(_opCtx.get(), nss); @@ -3471,6 +3548,45 @@ TEST_F(IdempotencyTestTxns, CommitPreparedTransactionWithPartialTxnOps) { ASSERT_TRUE(docExists(_opCtx.get(), nss, doc2)); } +TEST_F(IdempotencyTestTxns, CommitTwoPreparedTransactionsWithPartialTxnOpsAtOnce) { + createCollectionWithUuid(_opCtx.get(), NamespaceString::kSessionTransactionsTableNamespace); + auto uuid = createCollectionWithUuid(_opCtx.get(), nss); + auto lsid = makeLogicalSessionId(_opCtx.get()); + TxnNumber txnNum1(1); + TxnNumber txnNum2(2); + + auto partialOp1 = partialTxn( + lsid, txnNum1, StmtId(0), OpTime(), BSON_ARRAY(makeInsertApplyOpsEntry(nss, uuid, doc))); + auto prepareOp1 = prepare(lsid, txnNum1, StmtId(1), BSONArray(), partialOp1.getOpTime()); + auto commitOp1 = commitPrepared(lsid, txnNum1, StmtId(2), prepareOp1.getOpTime()); + + // The second transaction (with a different transaction number) in the same session. + auto partialOp2 = partialTxn( + lsid, txnNum2, StmtId(0), OpTime(), BSON_ARRAY(makeInsertApplyOpsEntry(nss, uuid, doc2))); + auto prepareOp2 = prepare(lsid, txnNum2, StmtId(1), BSONArray(), partialOp2.getOpTime()); + auto commitOp2 = commitPrepared(lsid, txnNum2, StmtId(2), prepareOp2.getOpTime()); + + ASSERT_OK(ReplicationCoordinator::get(getGlobalServiceContext()) + ->setFollowerMode(MemberState::RS_RECOVERING)); + + // This also tests that we clear the partialTxnList for the session after applying the commit of + // the first prepared transaction. Otherwise, saving operations from the second transaction to + // the same partialTxnList as the first transaction will trigger an invariant because of the + // mismatching transaction numbers. + testOpsAreIdempotent({partialOp1, prepareOp1, commitOp1, partialOp2, prepareOp2, commitOp2}); + + // The transaction table should only contain the second transaction of the session. + repl::checkTxnTable(_opCtx.get(), + lsid, + txnNum2, + commitOp2.getOpTime(), + *commitOp2.getWallClockTime(), + boost::none, + DurableTxnStateEnum::kCommitted); + ASSERT_TRUE(docExists(_opCtx.get(), nss, doc)); + ASSERT_TRUE(docExists(_opCtx.get(), nss, doc2)); +} + TEST_F(IdempotencyTestTxns, CommitPreparedTransactionWithPartialTxnOpsAndDataPartiallyApplied) { createCollectionWithUuid(_opCtx.get(), NamespaceString::kSessionTransactionsTableNamespace); auto uuid = createCollectionWithUuid(_opCtx.get(), nss); |