/** * Tests that initial sync successfully reconstructs a prepared transaction using the oplog seed * entry after failing the first attempt of initial sync and that we can commit the transactions * afterwards. During the first attempt of initial sync, there will be some oplog entries that need * to be applied and the lastApplied OpTime will be advanced along with the oplog application. We * test that the lastApplied and the localSnapshot are properly reset after the first attempt fails. * During the second attempt of initial sync, there will be no oplog entries that need to be applied * and the prepare oplog entry will be inserted as the oplog seed. We then make sure the oplog seed * entry is visible and the prepared transaction is properly reconstructed. * * @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: 2}); replTest.startSet(); const config = replTest.getReplSetConfig(); // Increase the election timeout so that we do not accidentally trigger an election while the // secondary is restarting. config.settings = { "electionTimeoutMillis": 12 * 60 * 60 * 1000 }; replTest.initiate(config); 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"}})); const dbName = "test"; const collName = "reconstruct_prepared_transactions_initial_sync_on_oplog_seed"; let testDB = primary.getDB(dbName); let testColl = testDB.getCollection(collName); const session = primary.startSession(); const sessionDB = session.getDatabase(dbName); const sessionColl = sessionDB.getCollection(collName); assert.commandWorked(testColl.insert({_id: 1})); jsTestLog("Restarting the secondary"); // Restart the secondary with startClean set to true so that it goes through initial sync. replTest.stop(secondary, undefined /* signal */, {skipValidation: true}); secondary = replTest.start( secondary, { startClean: true, setParameter: { 'numInitialSyncAttempts': 2, // Fail point to force the first attempt to fail and hang before starting the second // attempt. 'failpoint.failAndHangInitialSync': tojson({mode: 'alwaysOn'}), 'failpoint.initialSyncHangDuringCollectionClone': tojson( {mode: 'alwaysOn', data: {namespace: testColl.getFullName(), numDocsToClone: 0}}), 'logComponentVerbosity': tojson({'replication': {'initialSync': 2}}) } }, true /* wait */); // Wait for failpoint to be reached so we know that collection cloning is paused. assert.commandWorked(secondary.adminCommand({ waitForFailPoint: "initialSyncHangDuringCollectionClone", timesEntered: 1, maxTimeMS: kDefaultWaitForFailPointTimeout })); jsTestLog("Running operations while collection cloning is paused"); // Perform writes while collection cloning is paused so that we know they must be applied during // the first attempt of initial sync. assert.commandWorked(testColl.insert({_id: 2})); jsTestLog("Resuming initial sync"); // Resume initial sync. assert.commandWorked(secondary.adminCommand( {configureFailPoint: "initialSyncHangDuringCollectionClone", mode: "off"})); // Wait for failpoint to be reached so we know that first attempt is finishing and is about to // fail. assert.commandWorked(secondary.adminCommand({ waitForFailPoint: "failAndHangInitialSync", timesEntered: 1, maxTimeMS: kDefaultWaitForFailPointTimeout })); jsTestLog("Preparing the transaction before the second attempt of initial sync"); session.startTransaction(); assert.commandWorked(sessionColl.update({_id: 1}, {_id: 1, a: 1})); const prepareTimestamp = PrepareHelpers.prepareTransaction(session, {w: 1}); jsTestLog("Resuming initial sync for the second attempt"); // Resume initial sync. assert.commandWorked( secondary.adminCommand({configureFailPoint: "failAndHangInitialSync", mode: "off"})); // Wait for the secondary to complete initial sync. replTest.awaitSecondaryNodes(); PrepareHelpers.awaitMajorityCommitted(replTest, prepareTimestamp); jsTestLog("Initial sync completed"); secondary.setSecondaryOk(); const secondaryColl = secondary.getDB(dbName).getCollection(collName); jsTestLog("Checking that the transaction is properly prepared"); // Make sure that we can't read changes to the document from the prepared transaction after // initial sync. assert.eq(secondaryColl.findOne({_id: 1}), {_id: 1}); jsTestLog("Committing the transaction"); assert.commandWorked(PrepareHelpers.commitTransaction(session, prepareTimestamp)); replTest.awaitReplication(); // Make sure that we can see the data from the committed transaction on the secondary if it was // applied during secondary oplog application. assert.eq(secondaryColl.findOne({_id: 1}), {_id: 1, a: 1}); replTest.stopSet(); })();