mongo/jstests/replsets/transactions_wait_for_write...

191 lines
7.5 KiB
JavaScript

/**
* 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();
}());