diff options
Diffstat (limited to 'jstests/replsets/linearizable_read_concern.js')
-rw-r--r-- | jstests/replsets/linearizable_read_concern.js | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/jstests/replsets/linearizable_read_concern.js b/jstests/replsets/linearizable_read_concern.js new file mode 100644 index 00000000000..a8e707fb556 --- /dev/null +++ b/jstests/replsets/linearizable_read_concern.js @@ -0,0 +1,136 @@ +/* + * This test creates a 3 node replica set and then performs a write + * with write concern majority to create a committed snapshot. The test first sends + * a regular linearizable read command which should succeed. Then the test + * examines linearizable read parsing abilities by sending a linearizable + * read command to a secondary and then to the primary with an 'afterOpTime' + * field, both of which should fail. The test then starts to test the actual + * functionality of linearizable reads by creating a network partition between the primary + * and the other two nodes and then sending in a linearizable read command. + * Finally we test whether the linearizable read command will block forever + * by issuing a linearizable read command in another thread on the still + * partitioned primary and then making the primary step down in the main + * thread after finding the issued noop. The secondary thread should throw + * an exception and exit. + */ +load('jstests/replsets/rslib.js'); +load('jstests/libs/parallelTester.js'); +load('jstests/libs/write_concern_util.js'); +(function() { + 'use strict'; + var send_linearizable_read = function() { + // The primary will step down and throw an exception, which is expected. + var coll = db.getSiblingDB("test").foo; + jsTestLog('Sending in linearizable read in secondary thread'); + // 'isMaster' ensures that the following command fails (and returns a response rather than + // an exception) before its connection is cut because of the primary step down. Refer to + // SERVER-24574. + assert.commandWorked(coll.runCommand({isMaster: 1, hangUpOnStepDown: false})); + assert.commandFailedWithCode( + coll.runCommand( + {'find': 'foo', readConcern: {level: "linearizable"}, maxTimeMS: 60000}), + ErrorCodes.InterruptedDueToReplStateChange); + }; + + var num_nodes = 3; + var name = 'linearizable_read_concern'; + var replTest = new ReplSetTest({ + name: name, + nodes: num_nodes, + useBridge: true, + nodeOptions: {enableMajorityReadConcern: ''} + }); + if (!startSetIfSupportsReadMajority(replTest)) { + jsTest.log("skipping test since storage engine doesn't support committed reads"); + return; + } + var config = replTest.getReplSetConfig(); + + // Increased election timeout to avoid having the primary step down while we are + // testing linearizable functionality on an isolated primary. + config.settings = {electionTimeoutMillis: 60000}; + updateConfigIfNotDurable(config); + replTest.initiate(config); + + replTest.awaitReplication(); + var primary = replTest.getPrimary(); + var secondaries = replTest.getSecondaries(); + + // We should have at least one successful write with write concern majority + // to get a committed snapshot. + assert.writeOK(primary.getDB("test").foo.insert( + {"number": 7}, {"writeConcern": {"w": "majority", "wtimeout": 60000}})); + + jsTestLog("Testing linearizable readConcern parsing"); + // This command is sent to the primary, and the primary is fully connected so it should work. + var goodRead = assert.writeOK(primary.getDB("test").runCommand( + {'find': 'foo', readConcern: {level: "linearizable"}, "maxTimeMS": 60000})); + assert.eq(goodRead.cursor.firstBatch[0].number, 7); + + // This fails because you cannot have a linearizable read command sent to a secondary. + var badCmd = assert.commandFailed(secondaries[0].getDB("test").runCommand( + {"find": "foo", readConcern: {level: "linearizable"}, "maxTimeMS": 60000})); + + assert.eq(badCmd.errmsg, "cannot satisfy linearizable read concern on non-primary node"); + assert.eq(badCmd.code, ErrorCodes.NotMaster); + + // This fails because you cannot specify 'afterOpTime' for linearizable read. + var opTimeCmd = assert.commandFailed(primary.getDB("test").runCommand({ + "find": "foo", + readConcern: {level: "linearizable", "afterOpTime": {ts: Timestamp(1, 2), t: 1}}, + "maxTimeMS": 60000 + })); + assert.eq(opTimeCmd.errmsg, "afterOpTime not compatible with linearizable read concern"); + assert.eq(opTimeCmd.code, ErrorCodes.FailedToParse); + + primary = replTest.getPrimary(); + + jsTestLog("Starting linearizablility testing"); + jsTestLog( + "Setting up partitions such that the primary is isolated: [Secondary-Secondary] [Primary]"); + secondaries[0].disconnect(primary); + secondaries[1].disconnect(primary); + + jsTestLog("Read with readConcern majority should still work when sent to the old primary"); + var res = assert.writeOK(primary.getDB("test").runCommand( + {"find": "foo", readConcern: {level: "majority"}, "maxTimeMS": 60000})); + assert.eq(res.cursor.firstBatch[0].number, 7); + + var result = primary.getDB("test").runCommand( + {"find": "foo", "readConcern": {level: "linearizable"}, "maxTimeMS": 3000}); + assert.commandFailedWithCode(result, ErrorCodes.ExceededTimeLimit); + + jsTestLog("Testing to make sure linearizable read command does not block forever."); + + // Get last noop Optime before sending the linearizable read command + // to ensure that we are waiting for the most recent noop write. + var lastOpTimestamp = getLatestOp(primary).ts; + + var parallelShell = startParallelShell(send_linearizable_read, primary.port); + // Sending a linearizable read implicitly replicates a noop to the secondaries. We need to find + // the most recently issued noop to ensure that we call stepdown during the recently + // issued linearizable read and not before the read (in the separate thread) has been called. + jsTestLog("Checking end of oplog for noop"); + assert.soon(function() { + var isEarlierTimestamp = function(ts1, ts2) { + if (ts1.getTime() == ts2.getTime()) { + return ts1.getInc() < ts2.getInc(); + } + return ts1.getTime() < ts2.getTime(); + }; + var latestOp = getLatestOp(primary); + if (latestOp.op == "n" && isEarlierTimestamp(lastOpTimestamp, latestOp.ts)) { + return true; + } + + return false; + }); + assert.eq(primary, replTest.getPrimary(), "Primary unexpectedly changed mid test."); + jsTestLog("Making Primary step down"); + var stepDownException = assert.throws(function() { + var result = primary.adminCommand( + {"replSetStepDown": 100, secondaryCatchUpPeriodSecs: 0, "force": true}); + print('replSetStepDown did not throw exception but returned: ' + tojson(result)); + }); + parallelShell(); +}());
\ No newline at end of file |