summaryrefslogtreecommitdiff
path: root/jstests/replsets/recover_prepared_transaction_state.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/replsets/recover_prepared_transaction_state.js')
-rw-r--r--jstests/replsets/recover_prepared_transaction_state.js331
1 files changed, 164 insertions, 167 deletions
diff --git a/jstests/replsets/recover_prepared_transaction_state.js b/jstests/replsets/recover_prepared_transaction_state.js
index f87d35496c8..1b054718778 100644
--- a/jstests/replsets/recover_prepared_transaction_state.js
+++ b/jstests/replsets/recover_prepared_transaction_state.js
@@ -15,185 +15,182 @@
* @tags: [uses_transactions, uses_prepare_transaction]
*/
(function() {
- "use strict";
+"use strict";
- load("jstests/aggregation/extras/utils.js");
- load("jstests/core/txns/libs/prepare_helpers.js");
- load("jstests/replsets/libs/rollback_test.js");
+load("jstests/aggregation/extras/utils.js");
+load("jstests/core/txns/libs/prepare_helpers.js");
+load("jstests/replsets/libs/rollback_test.js");
- const dbName = "test";
- const collName = "recover_prepared_transaction_state_after_rollback";
+const dbName = "test";
+const collName = "recover_prepared_transaction_state_after_rollback";
- const rollbackTest = new RollbackTest(dbName);
- let primary = rollbackTest.getPrimary();
+const rollbackTest = new RollbackTest(dbName);
+let primary = rollbackTest.getPrimary();
- // Create collection we're using beforehand.
- const testDB = primary.getDB(dbName);
- const testColl = testDB.getCollection(collName);
+// Create collection we're using beforehand.
+const testDB = primary.getDB(dbName);
+const testColl = testDB.getCollection(collName);
- assert.commandWorked(testDB.runCommand({create: collName}));
+assert.commandWorked(testDB.runCommand({create: collName}));
- // Start two different sessions on the primary.
- let session1 = primary.startSession({causalConsistency: false});
- let session2 = primary.startSession({causalConsistency: false});
+// Start two different sessions on the primary.
+let session1 = primary.startSession({causalConsistency: false});
+let session2 = primary.startSession({causalConsistency: false});
- // Save both session IDs so we can later start sessions with the same IDs and commit or
- // abort a prepared transaction on them.
- const sessionID1 = session1.getSessionId();
- const sessionID2 = session2.getSessionId();
+// Save both session IDs so we can later start sessions with the same IDs and commit or
+// abort a prepared transaction on them.
+const sessionID1 = session1.getSessionId();
+const sessionID2 = session2.getSessionId();
- let sessionDB1 = session1.getDatabase(dbName);
- const sessionColl1 = sessionDB1.getCollection(collName);
+let sessionDB1 = session1.getDatabase(dbName);
+const sessionColl1 = sessionDB1.getCollection(collName);
- let sessionDB2 = session2.getDatabase(dbName);
- const sessionColl2 = sessionDB2.getCollection(collName);
+let sessionDB2 = session2.getDatabase(dbName);
+const sessionColl2 = sessionDB2.getCollection(collName);
- assert.commandWorked(sessionColl1.insert({_id: 1}));
- assert.commandWorked(sessionColl1.insert({_id: 2}));
+assert.commandWorked(sessionColl1.insert({_id: 1}));
+assert.commandWorked(sessionColl1.insert({_id: 2}));
- rollbackTest.awaitLastOpCommitted();
+rollbackTest.awaitLastOpCommitted();
- // Prepare a transaction on the first session whose commit will be rolled-back.
- session1.startTransaction();
- assert.commandWorked(sessionColl1.insert({_id: 3}));
- assert.commandWorked(sessionColl1.update({_id: 1}, {$set: {a: 1}}));
- const prepareTimestamp = PrepareHelpers.prepareTransaction(session1);
+// Prepare a transaction on the first session whose commit will be rolled-back.
+session1.startTransaction();
+assert.commandWorked(sessionColl1.insert({_id: 3}));
+assert.commandWorked(sessionColl1.update({_id: 1}, {$set: {a: 1}}));
+const prepareTimestamp = PrepareHelpers.prepareTransaction(session1);
- // Prevent the stable timestamp from moving beyond the following prepared transactions so
- // that when we replay the oplog from the stable timestamp, we correctly recover them.
- assert.commandWorked(
- primary.adminCommand({configureFailPoint: 'disableSnapshotting', mode: 'alwaysOn'}));
-
- // The following transactions will be prepared before the common point, so they must be in
- // prepare after rollback recovery.
-
- // Prepare another transaction on the second session whose abort will be rolled-back.
- session2.startTransaction();
- assert.commandWorked(sessionColl2.insert({_id: 4}));
- assert.commandWorked(sessionColl2.update({_id: 2}, {$set: {b: 2}}));
- const prepareTimestamp2 = PrepareHelpers.prepareTransaction(session2, {w: 1});
-
- // Check that we have two transactions in the transactions table.
- assert.eq(primary.getDB('config')['transactions'].find().itcount(), 2);
-
- // This characterizes the current behavior of fastcount, which is that the two open transaction
- // count toward the value.
- assert.eq(testColl.count(), 4);
-
- // The following commit and abort will be rolled back.
- rollbackTest.transitionToRollbackOperations();
- PrepareHelpers.commitTransaction(session1, prepareTimestamp);
- assert.commandWorked(session2.abortTransaction_forTesting());
-
- // The fastcount should be accurate because there are no open transactions.
- assert.eq(testColl.count(), 3);
-
- rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
- rollbackTest.transitionToSyncSourceOperationsDuringRollback();
- try {
- rollbackTest.transitionToSteadyStateOperations({skipDataConsistencyChecks: true});
- } finally {
- assert.commandWorked(
- primary.adminCommand({configureFailPoint: 'disableSnapshotting', mode: 'off'}));
- }
-
- // Make sure there are two transactions in the transactions table after rollback recovery.
- assert.eq(primary.getDB('config')['transactions'].find().itcount(), 2);
-
- // Make sure we can only see the first write and cannot see the writes from the prepared
- // transactions or the write that was rolled back.
- arrayEq(sessionColl1.find().toArray(), [{_id: 1}, {_id: 2}]);
- arrayEq(testColl.find().toArray(), [{_id: 1}, {_id: 2}]);
-
- // This check characterizes the current behavior of fastcount after rollback. It will not be
- // correct, but reflects the count at the point where both transactions are not yet committed or
- // aborted (because the operations were not majority committed). The count will eventually be
- // correct once the commit and abort are retried.
- assert.eq(sessionColl1.count(), 4);
- assert.eq(testColl.count(), 4);
-
- // Get the correct primary after the topology changes.
- primary = rollbackTest.getPrimary();
- rollbackTest.awaitReplication();
-
- // Make sure we can successfully commit the first rolled back prepared transaction.
- session1 =
- PrepareHelpers.createSessionWithGivenId(primary, sessionID1, {causalConsistency: false});
- sessionDB1 = session1.getDatabase(dbName);
- // The next transaction on this session should have a txnNumber of 0. We explicitly set this
- // since createSessionWithGivenId does not restore the current txnNumber in the shell.
- session1.setTxnNumber_forTesting(0);
- const txnNumber1 = session1.getTxnNumber_forTesting();
-
- // Make sure we cannot add any operations to a prepared transaction.
- assert.commandFailedWithCode(sessionDB1.runCommand({
- insert: collName,
- txnNumber: NumberLong(txnNumber1),
- documents: [{_id: 10}],
- autocommit: false,
- }),
- ErrorCodes.PreparedTransactionInProgress);
-
- // Make sure that writing to a document that was updated in the first prepared transaction
- // causes a write conflict.
- assert.commandFailedWithCode(
- sessionDB1.runCommand(
- {update: collName, updates: [{q: {_id: 1}, u: {$set: {a: 2}}}], maxTimeMS: 5 * 1000}),
- ErrorCodes.MaxTimeMSExpired);
-
- const commitTimestamp = Timestamp(prepareTimestamp.getTime(), prepareTimestamp.getInc() + 1);
- assert.commandWorked(sessionDB1.adminCommand({
- commitTransaction: 1,
- commitTimestamp: commitTimestamp,
- txnNumber: NumberLong(txnNumber1),
- autocommit: false,
- }));
- // Retry the commitTransaction command after rollback.
- assert.commandWorked(sessionDB1.adminCommand({
- commitTransaction: 1,
- commitTimestamp: commitTimestamp,
- txnNumber: NumberLong(txnNumber1),
- autocommit: false,
- }));
-
- // Make sure we can successfully abort the second recovered prepared transaction.
- session2 =
- PrepareHelpers.createSessionWithGivenId(primary, sessionID2, {causalConsistency: false});
- sessionDB2 = session2.getDatabase(dbName);
- // The next transaction on this session should have a txnNumber of 0. We explicitly set this
- // since createSessionWithGivenId does not restore the current txnNumber in the shell.
- session2.setTxnNumber_forTesting(0);
- const txnNumber2 = session2.getTxnNumber_forTesting();
-
- // Make sure we cannot add any operations to a prepared transaction.
- assert.commandFailedWithCode(sessionDB2.runCommand({
- insert: collName,
- txnNumber: NumberLong(txnNumber2),
- documents: [{_id: 10}],
- autocommit: false,
- }),
- ErrorCodes.PreparedTransactionInProgress);
-
- // Make sure that writing to a document that was updated in the second prepared transaction
- // causes a write conflict.
- assert.commandFailedWithCode(
- sessionDB2.runCommand(
- {update: collName, updates: [{q: {_id: 2}, u: {$set: {b: 3}}}], maxTimeMS: 5 * 1000}),
- ErrorCodes.MaxTimeMSExpired);
-
- assert.commandWorked(sessionDB2.adminCommand({
- abortTransaction: 1,
- txnNumber: NumberLong(txnNumber2),
- autocommit: false,
- }));
-
- rollbackTest.awaitReplication();
-
- // Make sure we can see the result of the committed prepared transaction and cannot see the
- // write from the aborted transaction.
- arrayEq(testColl.find().toArray(), [{_id: 1, a: 1}, {_id: 2}, {_id: 3}]);
- assert.eq(testColl.count(), 3);
-
- rollbackTest.stop();
+// Prevent the stable timestamp from moving beyond the following prepared transactions so
+// that when we replay the oplog from the stable timestamp, we correctly recover them.
+assert.commandWorked(
+ primary.adminCommand({configureFailPoint: 'disableSnapshotting', mode: 'alwaysOn'}));
+
+// The following transactions will be prepared before the common point, so they must be in
+// prepare after rollback recovery.
+
+// Prepare another transaction on the second session whose abort will be rolled-back.
+session2.startTransaction();
+assert.commandWorked(sessionColl2.insert({_id: 4}));
+assert.commandWorked(sessionColl2.update({_id: 2}, {$set: {b: 2}}));
+const prepareTimestamp2 = PrepareHelpers.prepareTransaction(session2, {w: 1});
+
+// Check that we have two transactions in the transactions table.
+assert.eq(primary.getDB('config')['transactions'].find().itcount(), 2);
+// This characterizes the current behavior of fastcount, which is that the two open transaction
+// count toward the value.
+assert.eq(testColl.count(), 4);
+
+// The following commit and abort will be rolled back.
+rollbackTest.transitionToRollbackOperations();
+PrepareHelpers.commitTransaction(session1, prepareTimestamp);
+assert.commandWorked(session2.abortTransaction_forTesting());
+
+// The fastcount should be accurate because there are no open transactions.
+assert.eq(testColl.count(), 3);
+
+rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+try {
+ rollbackTest.transitionToSteadyStateOperations({skipDataConsistencyChecks: true});
+} finally {
+ assert.commandWorked(
+ primary.adminCommand({configureFailPoint: 'disableSnapshotting', mode: 'off'}));
+}
+
+// Make sure there are two transactions in the transactions table after rollback recovery.
+assert.eq(primary.getDB('config')['transactions'].find().itcount(), 2);
+
+// Make sure we can only see the first write and cannot see the writes from the prepared
+// transactions or the write that was rolled back.
+arrayEq(sessionColl1.find().toArray(), [{_id: 1}, {_id: 2}]);
+arrayEq(testColl.find().toArray(), [{_id: 1}, {_id: 2}]);
+
+// This check characterizes the current behavior of fastcount after rollback. It will not be
+// correct, but reflects the count at the point where both transactions are not yet committed or
+// aborted (because the operations were not majority committed). The count will eventually be
+// correct once the commit and abort are retried.
+assert.eq(sessionColl1.count(), 4);
+assert.eq(testColl.count(), 4);
+
+// Get the correct primary after the topology changes.
+primary = rollbackTest.getPrimary();
+rollbackTest.awaitReplication();
+
+// Make sure we can successfully commit the first rolled back prepared transaction.
+session1 = PrepareHelpers.createSessionWithGivenId(primary, sessionID1, {causalConsistency: false});
+sessionDB1 = session1.getDatabase(dbName);
+// The next transaction on this session should have a txnNumber of 0. We explicitly set this
+// since createSessionWithGivenId does not restore the current txnNumber in the shell.
+session1.setTxnNumber_forTesting(0);
+const txnNumber1 = session1.getTxnNumber_forTesting();
+
+// Make sure we cannot add any operations to a prepared transaction.
+assert.commandFailedWithCode(sessionDB1.runCommand({
+ insert: collName,
+ txnNumber: NumberLong(txnNumber1),
+ documents: [{_id: 10}],
+ autocommit: false,
+}),
+ ErrorCodes.PreparedTransactionInProgress);
+
+// Make sure that writing to a document that was updated in the first prepared transaction
+// causes a write conflict.
+assert.commandFailedWithCode(
+ sessionDB1.runCommand(
+ {update: collName, updates: [{q: {_id: 1}, u: {$set: {a: 2}}}], maxTimeMS: 5 * 1000}),
+ ErrorCodes.MaxTimeMSExpired);
+
+const commitTimestamp = Timestamp(prepareTimestamp.getTime(), prepareTimestamp.getInc() + 1);
+assert.commandWorked(sessionDB1.adminCommand({
+ commitTransaction: 1,
+ commitTimestamp: commitTimestamp,
+ txnNumber: NumberLong(txnNumber1),
+ autocommit: false,
+}));
+// Retry the commitTransaction command after rollback.
+assert.commandWorked(sessionDB1.adminCommand({
+ commitTransaction: 1,
+ commitTimestamp: commitTimestamp,
+ txnNumber: NumberLong(txnNumber1),
+ autocommit: false,
+}));
+
+// Make sure we can successfully abort the second recovered prepared transaction.
+session2 = PrepareHelpers.createSessionWithGivenId(primary, sessionID2, {causalConsistency: false});
+sessionDB2 = session2.getDatabase(dbName);
+// The next transaction on this session should have a txnNumber of 0. We explicitly set this
+// since createSessionWithGivenId does not restore the current txnNumber in the shell.
+session2.setTxnNumber_forTesting(0);
+const txnNumber2 = session2.getTxnNumber_forTesting();
+
+// Make sure we cannot add any operations to a prepared transaction.
+assert.commandFailedWithCode(sessionDB2.runCommand({
+ insert: collName,
+ txnNumber: NumberLong(txnNumber2),
+ documents: [{_id: 10}],
+ autocommit: false,
+}),
+ ErrorCodes.PreparedTransactionInProgress);
+
+// Make sure that writing to a document that was updated in the second prepared transaction
+// causes a write conflict.
+assert.commandFailedWithCode(
+ sessionDB2.runCommand(
+ {update: collName, updates: [{q: {_id: 2}, u: {$set: {b: 3}}}], maxTimeMS: 5 * 1000}),
+ ErrorCodes.MaxTimeMSExpired);
+
+assert.commandWorked(sessionDB2.adminCommand({
+ abortTransaction: 1,
+ txnNumber: NumberLong(txnNumber2),
+ autocommit: false,
+}));
+
+rollbackTest.awaitReplication();
+
+// Make sure we can see the result of the committed prepared transaction and cannot see the
+// write from the aborted transaction.
+arrayEq(testColl.find().toArray(), [{_id: 1, a: 1}, {_id: 2}, {_id: 3}]);
+assert.eq(testColl.count(), 3);
+
+rollbackTest.stop();
}());