diff options
author | Ali Mir <ali.mir@mongodb.com> | 2020-02-18 16:19:33 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2020-02-18 16:19:33 +0000 |
commit | eb284b042c71edf0eac445d3ceb79f7fdeabc5d1 (patch) | |
tree | 3edc885bd11497dd8cca1073f72df3e4909ecd94 /src | |
parent | d9a68d2c084bfed60527aa9aa29e2a843b97e9c2 (diff) | |
download | mongo-eb284b042c71edf0eac445d3ceb79f7fdeabc5d1.tar.gz |
SERVER-43889 Distinguish between retryable write and transaction when failing a command
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/transaction_participant.cpp | 24 | ||||
-rw-r--r-- | src/mongo/db/transaction_participant_retryable_writes_test.cpp | 36 | ||||
-rw-r--r-- | src/mongo/db/transaction_participant_test.cpp | 37 |
3 files changed, 92 insertions, 5 deletions
diff --git a/src/mongo/db/transaction_participant.cpp b/src/mongo/db/transaction_participant.cpp index c8180467c20..adf348e6eed 100644 --- a/src/mongo/db/transaction_participant.cpp +++ b/src/mongo/db/transaction_participant.cpp @@ -495,11 +495,25 @@ void TransactionParticipant::Participant::beginOrContinue(OperationContext* opCt getTestCommandsEnabled()); } - uassert(ErrorCodes::TransactionTooOld, - str::stream() << "Cannot start transaction " << txnNumber << " on session " - << _sessionId() << " because a newer transaction " << o().activeTxnNumber - << " has already started.", - txnNumber >= o().activeTxnNumber); + if (txnNumber < o().activeTxnNumber) { + const std::string currOperation = + o().txnState.isInRetryableWriteMode() ? "retryable write" : "transaction"; + if (!autocommit) { + uasserted(ErrorCodes::TransactionTooOld, + str::stream() + << "Retryable write with txnNumber " << txnNumber + << " is prohibited on session " << _sessionId() << " because a newer " + << currOperation << " with txnNumber " << o().activeTxnNumber + << " has already started on this session."); + } else { + uasserted(ErrorCodes::TransactionTooOld, + str::stream() << "Cannot start transaction " << txnNumber << " on session " + << _sessionId() << " because a newer " << currOperation + << " with txnNumber " << o().activeTxnNumber + << " has already started on this session."); + } + } + // Requests without an autocommit field are interpreted as retryable writes. They cannot specify // startTransaction, which is verified earlier when parsing the request. diff --git a/src/mongo/db/transaction_participant_retryable_writes_test.cpp b/src/mongo/db/transaction_participant_retryable_writes_test.cpp index 6e4d4b096e7..36b0a98ab83 100644 --- a/src/mongo/db/transaction_participant_retryable_writes_test.cpp +++ b/src/mongo/db/transaction_participant_retryable_writes_test.cpp @@ -379,6 +379,42 @@ TEST_F(TransactionParticipantRetryableWritesTest, StartingOldTxnShouldAssert) { ASSERT(txnParticipant.getLastWriteOpTime().isNull()); } +TEST_F(TransactionParticipantRetryableWritesTest, + OlderRetryableWriteFailsOnSessionWithNewerRetryableWrite) { + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); + const TxnNumber txnNum = 22; + const auto& sessionId = *opCtx()->getLogicalSessionId(); + + StringBuilder sb; + sb << "Retryable write with txnNumber 21 is prohibited on session " << sessionId + << " because a newer retryable write with txnNumber 22 has already started on this session."; + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); + ASSERT_THROWS_WHAT( + txnParticipant.beginOrContinue(opCtx(), txnNum - 1, boost::none, boost::none), + AssertionException, + sb.str()); + ASSERT(txnParticipant.getLastWriteOpTime().isNull()); +} + +TEST_F(TransactionParticipantRetryableWritesTest, + OldTransactionFailsOnSessionWithNewerRetryableWrite) { + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); + const TxnNumber txnNum = 22; + auto autocommit = false; + const auto& sessionId = *opCtx()->getLogicalSessionId(); + + StringBuilder sb; + sb << "Cannot start transaction 21 on session " << sessionId + << " because a newer retryable write with txnNumber 22 has already started on this session."; + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); + ASSERT_THROWS_WHAT(txnParticipant.beginOrContinue(opCtx(), txnNum - 1, autocommit, boost::none), + AssertionException, + sb.str()); + ASSERT(txnParticipant.getLastWriteOpTime().isNull()); +} + TEST_F(TransactionParticipantRetryableWritesTest, SessionTransactionsCollectionNotDefaultCreated) { auto txnParticipant = TransactionParticipant::get(opCtx()); txnParticipant.refreshFromStorageIfNeeded(opCtx()); diff --git a/src/mongo/db/transaction_participant_test.cpp b/src/mongo/db/transaction_participant_test.cpp index 932ef261e88..b02fd45ba4c 100644 --- a/src/mongo/db/transaction_participant_test.cpp +++ b/src/mongo/db/transaction_participant_test.cpp @@ -1145,6 +1145,43 @@ TEST_F(TxnParticipantTest, CannotContinueTransactionIfNotPrimary) { ErrorCodes::NotMaster); } +TEST_F(TxnParticipantTest, OlderTransactionFailsOnSessionWithNewerTransaction) { + // Will start the transaction. + auto sessionCheckout = checkOutSession(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + ASSERT_TRUE(txnParticipant.transactionIsOpen()); + auto autocommit = false; + auto startTransaction = true; + const auto& sessionId = *opCtx()->getLogicalSessionId(); + + StringBuilder sb; + sb << "Cannot start transaction 19 on session " << sessionId + << " because a newer transaction with txnNumber 20 has already started on this session."; + ASSERT_THROWS_WHAT(txnParticipant.beginOrContinue( + opCtx(), *opCtx()->getTxnNumber() - 1, autocommit, startTransaction), + AssertionException, + sb.str()); + ASSERT(txnParticipant.getLastWriteOpTime().isNull()); +} + + +TEST_F(TxnParticipantTest, OldRetryableWriteFailsOnSessionWithNewerTransaction) { + // Will start the transaction. + auto sessionCheckout = checkOutSession(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + ASSERT_TRUE(txnParticipant.transactionIsOpen()); + const auto& sessionId = *opCtx()->getLogicalSessionId(); + + StringBuilder sb; + sb << "Retryable write with txnNumber 19 is prohibited on session " << sessionId + << " because a newer transaction with txnNumber 20 has already started on this session."; + ASSERT_THROWS_WHAT(txnParticipant.beginOrContinue( + opCtx(), *opCtx()->getTxnNumber() - 1, boost::none, boost::none), + AssertionException, + sb.str()); + ASSERT(txnParticipant.getLastWriteOpTime().isNull()); +} + TEST_F(TxnParticipantTest, CannotStartNewTransactionWhilePreparedTransactionInProgress) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); |