diff options
author | Samy Lanka <samy.lanka@gmail.com> | 2018-08-07 16:54:00 -0400 |
---|---|---|
committer | Samy Lanka <samy.lanka@gmail.com> | 2018-08-13 16:23:05 -0400 |
commit | d8c1ad1bd4154ebbd19ceae2ca0c46164d57fcc0 (patch) | |
tree | 1c2605b67d5509c33bc021af52156ca687640694 | |
parent | 5948fb10a2255f3e7074dad4c08deafce1a4056b (diff) | |
download | mongo-d8c1ad1bd4154ebbd19ceae2ca0c46164d57fcc0.tar.gz |
SERVER-35787 Return NoSuchTransaction or TransactionTooOld if prepare is
run on a non-existing transaction
-rw-r--r-- | buildscripts/resmokeconfig/suites/sharded_core_txns.yml | 1 | ||||
-rw-r--r-- | jstests/core/txns/prepare_nonexistent_transaction.js | 103 | ||||
-rw-r--r-- | src/mongo/db/transaction_participant_test.cpp | 7 |
3 files changed, 111 insertions, 0 deletions
diff --git a/buildscripts/resmokeconfig/suites/sharded_core_txns.yml b/buildscripts/resmokeconfig/suites/sharded_core_txns.yml index c2188ce6cff..44d9f5774ed 100644 --- a/buildscripts/resmokeconfig/suites/sharded_core_txns.yml +++ b/buildscripts/resmokeconfig/suites/sharded_core_txns.yml @@ -22,6 +22,7 @@ selector: - jstests/core/txns/no_new_transactions_when_prepared_transaction_in_progress.js - jstests/core/txns/prepare_committed_transaction.js - jstests/core/txns/prepare_conflict.js + - jstests/core/txns/prepare_nonexistent_transaction.js - jstests/core/txns/timestamped_reads_wait_for_prepare_oplog_visibility.js - jstests/core/txns/prepare_requires_fcv42.js diff --git a/jstests/core/txns/prepare_nonexistent_transaction.js b/jstests/core/txns/prepare_nonexistent_transaction.js new file mode 100644 index 00000000000..95707baf92a --- /dev/null +++ b/jstests/core/txns/prepare_nonexistent_transaction.js @@ -0,0 +1,103 @@ +/** + * Test error cases when calling prepare on a non-existent transaction. + * + * @tags: [uses_transactions] + */ +(function() { + "use strict"; + + const dbName = "test"; + const collName = "prepare_nonexistent_transaction"; + const testDB = db.getSiblingDB(dbName); + const testColl = testDB.getCollection(collName); + + testColl.drop({writeConcern: {w: "majority"}}); + assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}})); + + const session = db.getMongo().startSession({causalConsistency: false}); + const sessionDB = session.getDatabase(dbName); + const sessionColl = sessionDB.getCollection(collName); + + const doc = {x: 1}; + + jsTestLog("Test that if there is no transaction active on the current session, errors with " + + "'NoSuchTransaction'."); + assert.commandFailedWithCode( + sessionDB.adminCommand( + {prepareTransaction: 1, txnNumber: NumberLong(0), autocommit: false}), + ErrorCodes.NoSuchTransaction); + + jsTestLog("Test that if there is a transaction running on the current session and the " + + "'txnNumber' given is greater than the current transaction, errors with " + + "'NoSuchTransaction'."); + session.startTransaction(); + assert.commandWorked(sessionColl.insert(doc)); + assert.commandFailedWithCode(sessionDB.adminCommand({ + prepareTransaction: 1, + txnNumber: NumberLong(session.getTxnNumber_forTesting() + 1), + autocommit: false + }), + ErrorCodes.NoSuchTransaction); + session.abortTransaction(); + + session.startTransaction(); + assert.commandWorked(sessionColl.insert(doc)); + session.abortTransaction(); + jsTestLog("Test that if there is no transaction active on the current session, the " + + "'txnNumber' given matches the last known transaction for this session and the " + + "last known transaction was aborted then it errors with 'NoSuchTransaction'."); + assert.commandFailedWithCode(sessionDB.adminCommand({ + prepareTransaction: 1, + txnNumber: NumberLong(session.getTxnNumber_forTesting()), + autocommit: false + }), + ErrorCodes.NoSuchTransaction); + + jsTestLog("Test that if there is a transaction running on the current session and the " + + "'txnNumber' given is less than the current transaction, errors with " + + "'TransactionTooOld'."); + session.startTransaction(); + assert.commandWorked(sessionColl.insert(doc)); + assert.commandFailedWithCode( + sessionDB.adminCommand( + {prepareTransaction: 1, txnNumber: NumberLong(0), autocommit: false}), + ErrorCodes.TransactionTooOld); + session.abortTransaction(); + + jsTestLog("Test that if there is no transaction active on the current session and the " + + "'txnNumber' given is less than the current transaction, errors with " + + "'TransactionTooOld'."); + assert.commandFailedWithCode( + sessionDB.adminCommand( + {prepareTransaction: 1, txnNumber: NumberLong(0), autocommit: false}), + ErrorCodes.TransactionTooOld); + + jsTestLog("Test the error precedence when calling prepare on a nonexistent transaction but " + + "not providing txnNumber to prepareTransaction."); + assert.commandFailedWithCode(sessionDB.adminCommand({prepareTransaction: 1, autocommit: false}), + ErrorCodes.InvalidOptions); + + // It isn't ideal that NoSuchTransaction is thrown instead of InvalidOptions here, but it's okay + // to leave as is for now since this case fails in some way. + jsTestLog("Test the error precedence when calling prepare on a nonexistent transaction but " + + "not providing autocommit to prepareTransaction."); + assert.commandFailedWithCode(sessionDB.adminCommand({ + prepareTransaction: 1, + txnNumber: NumberLong(session.getTxnNumber_forTesting() + 1), + }), + ErrorCodes.NoSuchTransaction); + + jsTestLog("Test the error precedence when calling prepare on a nonexistent transaction and " + + "providing startTransaction to prepareTransaction."); + assert.commandFailedWithCode(sessionDB.adminCommand({ + prepareTransaction: 1, + // The last txnNumber we used was saved on the server's session, so we use a txnNumber that + // is greater than that to make sure it has never been seen before. + txnNumber: NumberLong(session.getTxnNumber_forTesting() + 2), + autocommit: false, + startTransaction: true + }), + ErrorCodes.OperationNotSupportedInTransaction); + + session.endSession(); +}()); diff --git a/src/mongo/db/transaction_participant_test.cpp b/src/mongo/db/transaction_participant_test.cpp index 9f3ca3a9a2c..7873312b19a 100644 --- a/src/mongo/db/transaction_participant_test.cpp +++ b/src/mongo/db/transaction_participant_test.cpp @@ -1119,6 +1119,7 @@ TEST_F(TxnParticipantTest, ConcurrencyOfPrepareTransactionAndMigration) { } TEST_F(TxnParticipantTest, ContinuingATransactionWithNoResourcesAborts) { + OperationContextSessionMongod(opCtx(), true, false, true); ASSERT_THROWS_CODE(OperationContextSessionMongod(opCtx(), true, false, boost::none), AssertionException, ErrorCodes::NoSuchTransaction); @@ -1276,6 +1277,12 @@ DEATH_TEST_F(TxnParticipantTest, AbortIsIllegalDuringCommittingPreparedTransacti txnParticipant->commitPreparedTransaction(opCtx(), prepareTimestamp); } +TEST_F(TxnParticipantTest, CannotContinueNonExistentTransaction) { + ASSERT_THROWS_CODE(OperationContextSessionMongod(opCtx(), true, false, boost::none), + AssertionException, + ErrorCodes::NoSuchTransaction); +} + // Tests that a transaction aborts if it becomes too large before trying to commit it. TEST_F(TxnParticipantTest, TransactionTooLargeWhileBuilding) { OperationContextSessionMongod opCtxSession(opCtx(), true, false, true); |