/** * Tests that initial sync successfully reconstructs a prepared transaction even when the sync * source started a transaction before the oplog application phase began but after the syncing node * fetched the oldest active transaction timestamp. * * To test this, we pause initial sync after the syncing node has fetched the oldest active * transaction timestamp from its sync source. Since there are no in-progress transactions, this * value will be null. While paused, the syncing node will start and prepare a transaction and do an * additional write. This will mean that when initial sync is unpaused, the syncing node will set * the begin applying timestamp to after the prepare timestamp. We can then test that the node still * fetches all the oplog entries it needs to reconstruct the prepared transaction. * * @tags: [ * uses_prepare_transaction, * uses_transactions, * ] */ (function() { "use strict"; load("jstests/core/txns/libs/prepare_helpers.js"); load("jstests/libs/fail_point_util.js"); const replTest = new ReplSetTest({nodes: 3}); replTest.startSet(); replTest.initiateWithHighElectionTimeout(); const primary = replTest.getPrimary(); let secondary = replTest.getSecondary(); // The default WC is majority and this test can't satisfy majority writes. assert.commandWorked(primary.adminCommand( {setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}, writeConcern: {w: "majority"}})); replTest.awaitReplication(); const dbName = "test"; const collName = jsTestName(); const testDB = primary.getDB(dbName); const testColl = testDB.getCollection(collName); const session1 = primary.startSession(); const sessionDB1 = session1.getDatabase(dbName); const sessionColl1 = sessionDB1.getCollection(collName); assert.commandWorked(sessionColl1.insert({_id: 1})); jsTestLog("Restarting the secondary and pausing initial sync"); // Restart the secondary with startClean set to true so that it goes through initial sync. Also // restart the node with a failpoint turned on that will pause initial sync after it has fetched // the oldest active transaction timestamp from its sync source to use as the begin fetching // timestamp. This means we can do some writes on the sync source to change the oldest active // transaction timestamp before getting the top of the sync source's oplog and make sure the node // fetches all the oplog entries it needs to reconstruct the prepared transaction. replTest.stop(secondary); secondary = replTest.start(secondary, { startClean: true, setParameter: { 'failpoint.initialSyncHangAfterGettingBeginFetchingTimestamp': tojson({mode: 'alwaysOn'}), 'numInitialSyncAttempts': 1 } }, true /* wait */); // Wait for failpoint to be reached so we know that the node is paused. At this point, the // the beginFetchingTimestamp is Timestamp(0, 0) because there were no open transactions. assert.commandWorked(secondary.adminCommand({ waitForFailPoint: "initialSyncHangAfterGettingBeginFetchingTimestamp", timesEntered: 1, maxTimeMS: kDefaultWaitForFailPointTimeout })); jsTestLog("Preparing a transaction after the initial syncing node fetched the beginFetchingOptime"); session1.startTransaction(); assert.commandWorked(sessionColl1.update({_id: 1}, {_id: 1, a: 1})); const prepareTimestamp = PrepareHelpers.prepareTransaction(session1); jsTestLog("Inserting another document to advance lastApplied on the sync source"); // This write will advance the last applied timestamp on the sync source, which will mean that // the beginApplyingTimestamp will be after the prepareTimestamp. assert.commandWorked(testColl.insert({_id: 2})); jsTestLog("Resuming initial sync"); assert.commandWorked(secondary.adminCommand( {configureFailPoint: "initialSyncHangAfterGettingBeginFetchingTimestamp", mode: "off"})); // Wait for the secondary to complete initial sync. replTest.awaitSecondaryNodes(); jsTestLog("Initial sync completed"); const secondaryColl = secondary.getDB(dbName).getCollection(collName); jsTestLog("Checking that the first transaction is properly prepared"); // Make sure that we can't read changes to the document from the first prepared transaction // after initial sync. assert.docEq(secondaryColl.findOne({_id: 1}), {_id: 1}); jsTestLog("Committing the transaction"); assert.commandWorked(PrepareHelpers.commitTransaction(session1, prepareTimestamp)); replTest.awaitReplication(); // Make sure that we can see the data from a committed transaction on the secondary if it was // applied during secondary oplog application. assert.docEq(secondaryColl.findOne({_id: 1}), {_id: 1, a: 1}); replTest.stopSet(); })();