/** * Test that transaction operations wait for write concern (or don't) correctly on noop writes. * * We run most commands on a different connection. If the commands were run on the same * connection, then the client last op for the noop writes would be set by the previous operation. * By using a fresh connection the client last op begins as null. This test explicitly tests that * write concern for noop writes works when the client last op has not already been set by a * duplicate operation. * @tags: [uses_transactions, uses_prepare_transaction] */ (function() { "use strict"; load("jstests/libs/write_concern_util.js"); load("jstests/core/txns/libs/prepare_helpers.js"); const dbName = "test"; const collNameBase = "coll"; const rst = new ReplSetTest({ nodes: [{}, {rsConfig: {priority: 0}}], }); rst.startSet(); rst.initiate(); const primary = rst.getPrimary(); const primaryDB = primary.getDB(dbName); const failTimeoutMS = 1000; const successTimeoutMS = ReplSetTest.kDefaultTimeoutMS; function runTest(readConcernLevel) { jsTestLog("Testing " + readConcernLevel); const collName = `${collNameBase}_${readConcernLevel}`; assert.commandWorked(primaryDB[collName].insert( [{x: 1}, {x: 2}, {x: 3}, {x: 4}, {x: 5}, {x: 6}], {writeConcern: {w: "majority"}})); jsTestLog("Unprepared Abort Setup"); const mongo1 = new Mongo(primary.host); const session1 = mongo1.startSession(); const sessionDB1 = session1.getDatabase(dbName); session1.startTransaction({ writeConcern: {w: "majority", wtimeout: successTimeoutMS}, readConcern: {level: readConcernLevel} }); const fruitlessUpdate1 = {update: collName, updates: [{q: {x: 1}, u: {$set: {x: 1}}}]}; printjson(assert.commandWorked(sessionDB1.runCommand(fruitlessUpdate1))); jsTestLog("Prepared Abort Setup"); const mongo2 = new Mongo(primary.host); const session2 = mongo2.startSession(); const sessionDB2 = session2.getDatabase(dbName); session2.startTransaction({ writeConcern: {w: "majority", wtimeout: failTimeoutMS}, readConcern: {level: readConcernLevel} }); const fruitlessUpdate2 = {update: collName, updates: [{q: {x: 2}, u: {$set: {x: 2}}}]}; printjson(assert.commandWorked(sessionDB2.runCommand(fruitlessUpdate2))); PrepareHelpers.prepareTransaction(session2); jsTestLog("Prepare Setup"); const mongo3 = new Mongo(primary.host); const session3 = mongo3.startSession(); const sessionDB3 = session3.getDatabase(dbName); session3.startTransaction({ writeConcern: {w: "majority", wtimeout: failTimeoutMS}, readConcern: {level: readConcernLevel} }); const fruitlessUpdate3 = {update: collName, updates: [{q: {x: 3}, u: {$set: {x: 3}}}]}; printjson(assert.commandWorked(sessionDB3.runCommand(fruitlessUpdate3))); jsTestLog("Unprepared Commit Setup"); const mongo4 = new Mongo(primary.host); const session4 = mongo4.startSession(); const sessionDB4 = session4.getDatabase(dbName); session4.startTransaction({ writeConcern: {w: "majority", wtimeout: failTimeoutMS}, readConcern: {level: readConcernLevel} }); const fruitlessUpdate4 = {update: collName, updates: [{q: {x: 4}, u: {$set: {x: 4}}}]}; printjson(assert.commandWorked(sessionDB4.runCommand(fruitlessUpdate4))); jsTestLog("Prepared Commit Setup"); const mongo5 = new Mongo(primary.host); const session5 = mongo5.startSession(); const sessionDB5 = session5.getDatabase(dbName); session5.startTransaction({ writeConcern: {w: "majority", wtimeout: failTimeoutMS}, readConcern: {level: readConcernLevel} }); const fruitlessUpdate5 = {update: collName, updates: [{q: {x: 5}, u: {$set: {x: 5}}}]}; printjson(assert.commandWorked(sessionDB5.runCommand(fruitlessUpdate5))); let prepareTS5 = PrepareHelpers.prepareTransaction(session5); jsTestLog("Stop replication"); stopReplicationOnSecondaries(rst); jsTestLog("Advance OpTime on primary, with replication stopped"); printjson(assert.commandWorked(primaryDB.runCommand({insert: collName, documents: [{}]}))); jsTestLog("Run test commands, with replication stopped"); jsTestLog("Unprepared Abort Test"); assert.commandWorked(session1.abortTransaction_forTesting()); jsTestLog("Prepared Abort Test"); assert.commandFailedWithCode(session2.abortTransaction_forTesting(), ErrorCodes.WriteConcernFailed); jsTestLog("Prepare Test"); assert.commandFailedWithCode( session3.getDatabase('admin').adminCommand( {prepareTransaction: 1, writeConcern: {w: "majority", wtimeout: failTimeoutMS}}), ErrorCodes.WriteConcernFailed); assert.commandFailedWithCode(session3.abortTransaction_forTesting(), ErrorCodes.WriteConcernFailed); jsTestLog("Unprepared Commit Test"); assert.commandFailedWithCode(session4.commitTransaction_forTesting(), ErrorCodes.WriteConcernFailed); jsTestLog("Prepared Commit Test"); assert.commandFailedWithCode(session5.getDatabase('admin').adminCommand({ commitTransaction: 1, commitTimestamp: prepareTS5, writeConcern: {w: "majority", wtimeout: failTimeoutMS} }), ErrorCodes.WriteConcernFailed); // Send commit with the shell helper to reset the shell's state. assert.commandFailedWithCode(session5.commitTransaction_forTesting(), ErrorCodes.WriteConcernFailed); jsTestLog("Restart replication"); restartReplicationOnSecondaries(rst); jsTestLog("Try transaction with replication enabled"); // Unprepared Abort. session1.startTransaction({ writeConcern: {w: "majority", wtimeout: successTimeoutMS}, readConcern: {level: readConcernLevel} }); assert.commandWorked(sessionDB1.runCommand(fruitlessUpdate1)); assert.commandWorked(session1.abortTransaction_forTesting()); // Prepared Abort. session2.startTransaction({ writeConcern: {w: "majority", wtimeout: successTimeoutMS}, readConcern: {level: readConcernLevel} }); assert.commandWorked(sessionDB2.runCommand(fruitlessUpdate2)); PrepareHelpers.prepareTransaction(session2); assert.commandWorked(session2.abortTransaction_forTesting()); // Testing prepare is no different then prepared abort or prepared commit. // Unprepared Commit. session4.startTransaction({ writeConcern: {w: "majority", wtimeout: successTimeoutMS}, readConcern: {level: readConcernLevel} }); assert.commandWorked(sessionDB4.runCommand(fruitlessUpdate4)); assert.commandWorked(session4.commitTransaction_forTesting()); // Prepared Commit. session5.startTransaction({ writeConcern: {w: "majority", wtimeout: successTimeoutMS}, readConcern: {level: readConcernLevel} }); assert.commandWorked(sessionDB5.runCommand(fruitlessUpdate5)); prepareTS5 = PrepareHelpers.prepareTransaction(session5); assert.commandWorked(session5.getDatabase('admin').adminCommand({ commitTransaction: 1, commitTimestamp: prepareTS5, writeConcern: {w: "majority", wtimeout: successTimeoutMS} })); // Send commit with the shell helper to reset the shell's state. assert.commandWorked(session5.commitTransaction_forTesting()); // Unprepared abort already is using a "used connection" for this success test. } runTest("local"); runTest("majority"); runTest("snapshot"); rst.stopSet(); }());