summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamy Lanka <samy.lanka@gmail.com>2018-08-07 16:54:00 -0400
committerSamy Lanka <samy.lanka@gmail.com>2018-08-13 16:23:05 -0400
commitd8c1ad1bd4154ebbd19ceae2ca0c46164d57fcc0 (patch)
tree1c2605b67d5509c33bc021af52156ca687640694
parent5948fb10a2255f3e7074dad4c08deafce1a4056b (diff)
downloadmongo-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.yml1
-rw-r--r--jstests/core/txns/prepare_nonexistent_transaction.js103
-rw-r--r--src/mongo/db/transaction_participant_test.cpp7
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);