summaryrefslogtreecommitdiff
path: root/jstests/core/txns/multi_statement_transaction_abort.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/core/txns/multi_statement_transaction_abort.js')
-rw-r--r--jstests/core/txns/multi_statement_transaction_abort.js504
1 files changed, 253 insertions, 251 deletions
diff --git a/jstests/core/txns/multi_statement_transaction_abort.js b/jstests/core/txns/multi_statement_transaction_abort.js
index 3e8e8a62758..a7946af8eda 100644
--- a/jstests/core/txns/multi_statement_transaction_abort.js
+++ b/jstests/core/txns/multi_statement_transaction_abort.js
@@ -1,255 +1,257 @@
// Test basic multi-statement transaction abort.
// @tags: [uses_transactions, uses_snapshot_read_concern]
(function() {
- "use strict";
-
- const dbName = "test";
- const collName = "multi_statement_transaction_abort";
- const testDB = db.getSiblingDB(dbName);
- const testColl = testDB[collName];
-
- testDB.runCommand({drop: collName, writeConcern: {w: "majority"}});
-
- assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));
- let txnNumber = 0;
-
- const sessionOptions = {causalConsistency: false};
- const session = testDB.getMongo().startSession(sessionOptions);
- const sessionDb = session.getDatabase(dbName);
-
- jsTest.log("Insert two documents in a transaction and abort");
-
- // Insert a doc within the transaction.
- assert.commandWorked(sessionDb.runCommand({
- insert: collName,
- documents: [{_id: "insert-1"}],
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber),
- startTransaction: true,
- autocommit: false
- }));
-
- // Insert a doc within a transaction.
- assert.commandWorked(sessionDb.runCommand({
- insert: collName,
- documents: [{_id: "insert-2"}],
- txnNumber: NumberLong(txnNumber),
- autocommit: false
- }));
-
- // Cannot read with default read concern.
- assert.eq(null, testColl.findOne({_id: "insert-1"}));
- // Cannot read with default read concern.
- assert.eq(null, testColl.findOne({_id: "insert-2"}));
-
- // abortTransaction can only be run on the admin database.
- assert.commandWorked(sessionDb.adminCommand({
- abortTransaction: 1,
- writeConcern: {w: "majority"},
- txnNumber: NumberLong(txnNumber),
- autocommit: false
- }));
-
- // Read with default read concern cannot see the aborted transaction.
- assert.eq(null, testColl.findOne({_id: "insert-1"}));
- assert.eq(null, testColl.findOne({_id: "insert-2"}));
-
- jsTest.log("Insert two documents in a transaction and commit");
-
- // Insert a doc with the same _id in a new transaction should work.
- txnNumber++;
- assert.commandWorked(sessionDb.runCommand({
- insert: collName,
- documents: [{_id: "insert-1"}, {_id: "insert-2"}],
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber),
- startTransaction: true,
- autocommit: false
- }));
- // commitTransaction can only be called on the admin database.
- assert.commandWorked(sessionDb.adminCommand({
- commitTransaction: 1,
- writeConcern: {w: "majority"},
- txnNumber: NumberLong(txnNumber),
- autocommit: false
- }));
- // Read with default read concern sees the committed transaction.
- assert.eq({_id: "insert-1"}, testColl.findOne({_id: "insert-1"}));
- assert.eq({_id: "insert-2"}, testColl.findOne({_id: "insert-2"}));
-
- jsTest.log("Cannot abort empty transaction because it's not in progress");
- txnNumber++;
- // abortTransaction can only be called on the admin database.
- let res = sessionDb.adminCommand({
- abortTransaction: 1,
- writeConcern: {w: "majority"},
- txnNumber: NumberLong(txnNumber),
- autocommit: false
- });
- assert.commandFailedWithCode(res, ErrorCodes.NoSuchTransaction);
- assert.eq(res.errorLabels, ["TransientTransactionError"]);
-
- jsTest.log("Abort transaction on duplicated key errors");
- assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}}));
- assert.commandWorked(testColl.insert({_id: "insert-1"}, {writeConcern: {w: "majority"}}));
- txnNumber++;
- // The first insert works well.
- assert.commandWorked(sessionDb.runCommand({
- insert: collName,
- documents: [{_id: "insert-2"}],
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber),
- startTransaction: true,
- autocommit: false
- }));
- // But the second insert throws duplicated index key error.
- res = assert.commandFailedWithCode(sessionDb.runCommand({
- insert: collName,
- documents: [{_id: "insert-1", x: 0}],
- txnNumber: NumberLong(txnNumber),
- autocommit: false
- }),
- ErrorCodes.DuplicateKey);
- // DuplicateKey is not a transient error.
- assert.eq(res.errorLabels, null);
-
- // The error aborts the transaction.
- // commitTransaction can only be called on the admin database.
- assert.commandFailedWithCode(sessionDb.adminCommand({
- commitTransaction: 1,
- writeConcern: {w: "majority"},
- txnNumber: NumberLong(txnNumber),
- autocommit: false
- }),
- ErrorCodes.NoSuchTransaction);
- // Verify the documents are the same.
- assert.eq({_id: "insert-1"}, testColl.findOne({_id: "insert-1"}));
- assert.eq(null, testColl.findOne({_id: "insert-2"}));
-
- jsTest.log("Abort transaction on write conflict errors");
- assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}}));
- txnNumber++;
- const session2 = testDB.getMongo().startSession(sessionOptions);
- const sessionDb2 = session2.getDatabase(dbName);
- // Insert a doc from session 1.
- assert.commandWorked(sessionDb.runCommand({
- insert: collName,
- documents: [{_id: "insert-1", from: 1}],
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber),
- startTransaction: true,
- autocommit: false
- }));
- let txnNumber2 = 0;
- // Insert a doc from session 2 that doesn't conflict with session 1.
- assert.commandWorked(sessionDb2.runCommand({
- insert: collName,
- documents: [{_id: "insert-2", from: 2}],
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber2),
- startTransaction: true,
- autocommit: false
- }));
- // Insert a doc from session 2 that conflicts with session 1.
- res = sessionDb2.runCommand({
- insert: collName,
- documents: [{_id: "insert-1", from: 2}],
- txnNumber: NumberLong(txnNumber2),
- autocommit: false
- });
- assert.commandFailedWithCode(res, ErrorCodes.WriteConflict);
- assert.eq(res.errorLabels, ["TransientTransactionError"]);
-
- // Session 1 isn't affected.
- // commitTransaction can only be called on the admin database.
- assert.commandWorked(sessionDb.adminCommand({
- commitTransaction: 1,
- writeConcern: {w: "majority"},
- txnNumber: NumberLong(txnNumber),
- autocommit: false
- }));
- // Transaction on session 2 is aborted.
- assert.commandFailedWithCode(sessionDb2.adminCommand({
- commitTransaction: 1,
- writeConcern: {w: "majority"},
- txnNumber: NumberLong(txnNumber2),
- autocommit: false
- }),
- ErrorCodes.NoSuchTransaction);
- // Verify the documents only reflect the first transaction.
- assert.eq({_id: "insert-1", from: 1}, testColl.findOne({_id: "insert-1"}));
- assert.eq(null, testColl.findOne({_id: "insert-2"}));
-
- jsTest.log("Higher transaction number aborts existing running transaction.");
- txnNumber++;
- assert.commandWorked(sessionDb.runCommand({
- insert: collName,
- documents: [{_id: "running-txn-1"}, {_id: "running-txn-2"}],
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber),
- startTransaction: true,
- autocommit: false
- }));
- // A higher txnNumber aborts the old and inserts the same document.
- txnNumber++;
- assert.commandWorked(sessionDb.runCommand({
- insert: collName,
- documents: [{_id: "running-txn-2"}],
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber),
- startTransaction: true,
- autocommit: false
- }));
- // commitTransaction can only be called on the admin database.
- assert.commandWorked(sessionDb.adminCommand({
- commitTransaction: 1,
- writeConcern: {w: "majority"},
- txnNumber: NumberLong(txnNumber),
- autocommit: false
- }));
- // Read with default read concern sees the committed transaction but cannot see the aborted one.
- assert.eq(null, testColl.findOne({_id: "running-txn-1"}));
- assert.eq({_id: "running-txn-2"}, testColl.findOne({_id: "running-txn-2"}));
-
- jsTest.log("Higher transaction number aborts existing running snapshot read.");
- assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}}));
- assert.commandWorked(
- testColl.insert([{doc: 1}, {doc: 2}, {doc: 3}], {writeConcern: {w: "majority"}}));
- txnNumber++;
- // Perform a snapshot read under a new transaction.
- let runningReadResult = assert.commandWorked(sessionDb.runCommand({
- find: collName,
- batchSize: 2,
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber),
- startTransaction: true,
- autocommit: false
- }));
-
- // The cursor has not been exhausted.
- assert(runningReadResult.hasOwnProperty("cursor"), tojson(runningReadResult));
- assert.neq(0, runningReadResult.cursor.id, tojson(runningReadResult));
-
- txnNumber++;
- // Perform a second snapshot read under a new transaction.
- let newReadResult = assert.commandWorked(sessionDb.runCommand({
- find: collName,
- readConcern: {level: "snapshot"},
- txnNumber: NumberLong(txnNumber),
- startTransaction: true,
- autocommit: false
- }));
-
- // The cursor has been exhausted.
- assert(newReadResult.hasOwnProperty("cursor"), tojson(newReadResult));
- assert.eq(0, newReadResult.cursor.id, tojson(newReadResult));
- // commitTransaction can only be called on the admin database.
- assert.commandWorked(sessionDb.adminCommand({
- commitTransaction: 1,
- writeConcern: {w: "majority"},
- txnNumber: NumberLong(txnNumber),
- autocommit: false
- }));
-
- session.endSession();
+"use strict";
+
+const dbName = "test";
+const collName = "multi_statement_transaction_abort";
+const testDB = db.getSiblingDB(dbName);
+const testColl = testDB[collName];
+
+testDB.runCommand({drop: collName, writeConcern: {w: "majority"}});
+
+assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));
+let txnNumber = 0;
+
+const sessionOptions = {
+ causalConsistency: false
+};
+const session = testDB.getMongo().startSession(sessionOptions);
+const sessionDb = session.getDatabase(dbName);
+
+jsTest.log("Insert two documents in a transaction and abort");
+
+// Insert a doc within the transaction.
+assert.commandWorked(sessionDb.runCommand({
+ insert: collName,
+ documents: [{_id: "insert-1"}],
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(txnNumber),
+ startTransaction: true,
+ autocommit: false
+}));
+
+// Insert a doc within a transaction.
+assert.commandWorked(sessionDb.runCommand({
+ insert: collName,
+ documents: [{_id: "insert-2"}],
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+}));
+
+// Cannot read with default read concern.
+assert.eq(null, testColl.findOne({_id: "insert-1"}));
+// Cannot read with default read concern.
+assert.eq(null, testColl.findOne({_id: "insert-2"}));
+
+// abortTransaction can only be run on the admin database.
+assert.commandWorked(sessionDb.adminCommand({
+ abortTransaction: 1,
+ writeConcern: {w: "majority"},
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+}));
+
+// Read with default read concern cannot see the aborted transaction.
+assert.eq(null, testColl.findOne({_id: "insert-1"}));
+assert.eq(null, testColl.findOne({_id: "insert-2"}));
+
+jsTest.log("Insert two documents in a transaction and commit");
+
+// Insert a doc with the same _id in a new transaction should work.
+txnNumber++;
+assert.commandWorked(sessionDb.runCommand({
+ insert: collName,
+ documents: [{_id: "insert-1"}, {_id: "insert-2"}],
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(txnNumber),
+ startTransaction: true,
+ autocommit: false
+}));
+// commitTransaction can only be called on the admin database.
+assert.commandWorked(sessionDb.adminCommand({
+ commitTransaction: 1,
+ writeConcern: {w: "majority"},
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+}));
+// Read with default read concern sees the committed transaction.
+assert.eq({_id: "insert-1"}, testColl.findOne({_id: "insert-1"}));
+assert.eq({_id: "insert-2"}, testColl.findOne({_id: "insert-2"}));
+
+jsTest.log("Cannot abort empty transaction because it's not in progress");
+txnNumber++;
+// abortTransaction can only be called on the admin database.
+let res = sessionDb.adminCommand({
+ abortTransaction: 1,
+ writeConcern: {w: "majority"},
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+});
+assert.commandFailedWithCode(res, ErrorCodes.NoSuchTransaction);
+assert.eq(res.errorLabels, ["TransientTransactionError"]);
+
+jsTest.log("Abort transaction on duplicated key errors");
+assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}}));
+assert.commandWorked(testColl.insert({_id: "insert-1"}, {writeConcern: {w: "majority"}}));
+txnNumber++;
+// The first insert works well.
+assert.commandWorked(sessionDb.runCommand({
+ insert: collName,
+ documents: [{_id: "insert-2"}],
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(txnNumber),
+ startTransaction: true,
+ autocommit: false
+}));
+// But the second insert throws duplicated index key error.
+res = assert.commandFailedWithCode(sessionDb.runCommand({
+ insert: collName,
+ documents: [{_id: "insert-1", x: 0}],
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+}),
+ ErrorCodes.DuplicateKey);
+// DuplicateKey is not a transient error.
+assert.eq(res.errorLabels, null);
+
+// The error aborts the transaction.
+// commitTransaction can only be called on the admin database.
+assert.commandFailedWithCode(sessionDb.adminCommand({
+ commitTransaction: 1,
+ writeConcern: {w: "majority"},
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+}),
+ ErrorCodes.NoSuchTransaction);
+// Verify the documents are the same.
+assert.eq({_id: "insert-1"}, testColl.findOne({_id: "insert-1"}));
+assert.eq(null, testColl.findOne({_id: "insert-2"}));
+
+jsTest.log("Abort transaction on write conflict errors");
+assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}}));
+txnNumber++;
+const session2 = testDB.getMongo().startSession(sessionOptions);
+const sessionDb2 = session2.getDatabase(dbName);
+// Insert a doc from session 1.
+assert.commandWorked(sessionDb.runCommand({
+ insert: collName,
+ documents: [{_id: "insert-1", from: 1}],
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(txnNumber),
+ startTransaction: true,
+ autocommit: false
+}));
+let txnNumber2 = 0;
+// Insert a doc from session 2 that doesn't conflict with session 1.
+assert.commandWorked(sessionDb2.runCommand({
+ insert: collName,
+ documents: [{_id: "insert-2", from: 2}],
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(txnNumber2),
+ startTransaction: true,
+ autocommit: false
+}));
+// Insert a doc from session 2 that conflicts with session 1.
+res = sessionDb2.runCommand({
+ insert: collName,
+ documents: [{_id: "insert-1", from: 2}],
+ txnNumber: NumberLong(txnNumber2),
+ autocommit: false
+});
+assert.commandFailedWithCode(res, ErrorCodes.WriteConflict);
+assert.eq(res.errorLabels, ["TransientTransactionError"]);
+
+// Session 1 isn't affected.
+// commitTransaction can only be called on the admin database.
+assert.commandWorked(sessionDb.adminCommand({
+ commitTransaction: 1,
+ writeConcern: {w: "majority"},
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+}));
+// Transaction on session 2 is aborted.
+assert.commandFailedWithCode(sessionDb2.adminCommand({
+ commitTransaction: 1,
+ writeConcern: {w: "majority"},
+ txnNumber: NumberLong(txnNumber2),
+ autocommit: false
+}),
+ ErrorCodes.NoSuchTransaction);
+// Verify the documents only reflect the first transaction.
+assert.eq({_id: "insert-1", from: 1}, testColl.findOne({_id: "insert-1"}));
+assert.eq(null, testColl.findOne({_id: "insert-2"}));
+
+jsTest.log("Higher transaction number aborts existing running transaction.");
+txnNumber++;
+assert.commandWorked(sessionDb.runCommand({
+ insert: collName,
+ documents: [{_id: "running-txn-1"}, {_id: "running-txn-2"}],
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(txnNumber),
+ startTransaction: true,
+ autocommit: false
+}));
+// A higher txnNumber aborts the old and inserts the same document.
+txnNumber++;
+assert.commandWorked(sessionDb.runCommand({
+ insert: collName,
+ documents: [{_id: "running-txn-2"}],
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(txnNumber),
+ startTransaction: true,
+ autocommit: false
+}));
+// commitTransaction can only be called on the admin database.
+assert.commandWorked(sessionDb.adminCommand({
+ commitTransaction: 1,
+ writeConcern: {w: "majority"},
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+}));
+// Read with default read concern sees the committed transaction but cannot see the aborted one.
+assert.eq(null, testColl.findOne({_id: "running-txn-1"}));
+assert.eq({_id: "running-txn-2"}, testColl.findOne({_id: "running-txn-2"}));
+
+jsTest.log("Higher transaction number aborts existing running snapshot read.");
+assert.commandWorked(testColl.remove({}, {writeConcern: {w: "majority"}}));
+assert.commandWorked(
+ testColl.insert([{doc: 1}, {doc: 2}, {doc: 3}], {writeConcern: {w: "majority"}}));
+txnNumber++;
+// Perform a snapshot read under a new transaction.
+let runningReadResult = assert.commandWorked(sessionDb.runCommand({
+ find: collName,
+ batchSize: 2,
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(txnNumber),
+ startTransaction: true,
+ autocommit: false
+}));
+
+// The cursor has not been exhausted.
+assert(runningReadResult.hasOwnProperty("cursor"), tojson(runningReadResult));
+assert.neq(0, runningReadResult.cursor.id, tojson(runningReadResult));
+
+txnNumber++;
+// Perform a second snapshot read under a new transaction.
+let newReadResult = assert.commandWorked(sessionDb.runCommand({
+ find: collName,
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(txnNumber),
+ startTransaction: true,
+ autocommit: false
+}));
+
+// The cursor has been exhausted.
+assert(newReadResult.hasOwnProperty("cursor"), tojson(newReadResult));
+assert.eq(0, newReadResult.cursor.id, tojson(newReadResult));
+// commitTransaction can only be called on the admin database.
+assert.commandWorked(sessionDb.adminCommand({
+ commitTransaction: 1,
+ writeConcern: {w: "majority"},
+ txnNumber: NumberLong(txnNumber),
+ autocommit: false
+}));
+
+session.endSession();
}());