diff options
Diffstat (limited to 'jstests/replsets/read_committed.js')
-rw-r--r-- | jstests/replsets/read_committed.js | 318 |
1 files changed, 160 insertions, 158 deletions
diff --git a/jstests/replsets/read_committed.js b/jstests/replsets/read_committed.js index f76ea6488a1..79a9cd3b0fa 100644 --- a/jstests/replsets/read_committed.js +++ b/jstests/replsets/read_committed.js @@ -9,169 +9,171 @@ load("jstests/replsets/rslib.js"); // For startSetIfSupportsReadMajority. (function() { - "use strict"; - - const majorityWriteConcern = {writeConcern: {w: "majority", wtimeout: 60 * 1000}}; - - // Each test case includes a 'prepareCollection' method that sets up the initial state starting - // with an empty collection, a 'write' method that does some write, and two arrays, - // 'expectedBefore' and 'expectedAfter' that describe the expected contents of the collection - // before and after the write. The 'prepareCollection' and 'write' methods should leave the - // collection either empty or with a single document with _id: 1. - const testCases = { - insert: { - prepareCollection: function(coll) {}, // No-op - write: function(coll, writeConcern) { - assert.writeOK(coll.insert({_id: 1}, writeConcern)); - }, - expectedBefore: [], - expectedAfter: [{_id: 1}], +"use strict"; + +const majorityWriteConcern = { + writeConcern: {w: "majority", wtimeout: 60 * 1000} +}; + +// Each test case includes a 'prepareCollection' method that sets up the initial state starting +// with an empty collection, a 'write' method that does some write, and two arrays, +// 'expectedBefore' and 'expectedAfter' that describe the expected contents of the collection +// before and after the write. The 'prepareCollection' and 'write' methods should leave the +// collection either empty or with a single document with _id: 1. +const testCases = { + insert: { + prepareCollection: function(coll) {}, // No-op + write: function(coll, writeConcern) { + assert.writeOK(coll.insert({_id: 1}, writeConcern)); }, - update: { - prepareCollection: function(coll) { - assert.writeOK(coll.insert({_id: 1, state: 'before'}, majorityWriteConcern)); - }, - write: function(coll, writeConcern) { - assert.writeOK(coll.update({_id: 1}, {$set: {state: 'after'}}, writeConcern)); - }, - expectedBefore: [{_id: 1, state: 'before'}], - expectedAfter: [{_id: 1, state: 'after'}], + expectedBefore: [], + expectedAfter: [{_id: 1}], + }, + update: { + prepareCollection: function(coll) { + assert.writeOK(coll.insert({_id: 1, state: 'before'}, majorityWriteConcern)); }, - remove: { - prepareCollection: function(coll) { - assert.writeOK(coll.insert({_id: 1}, majorityWriteConcern)); - }, - write: function(coll, writeConcern) { - assert.writeOK(coll.remove({_id: 1}, writeConcern)); - }, - expectedBefore: [{_id: 1}], - expectedAfter: [], + write: function(coll, writeConcern) { + assert.writeOK(coll.update({_id: 1}, {$set: {state: 'after'}}, writeConcern)); }, + expectedBefore: [{_id: 1, state: 'before'}], + expectedAfter: [{_id: 1, state: 'after'}], + }, + remove: { + prepareCollection: function(coll) { + assert.writeOK(coll.insert({_id: 1}, majorityWriteConcern)); + }, + write: function(coll, writeConcern) { + assert.writeOK(coll.remove({_id: 1}, writeConcern)); + }, + expectedBefore: [{_id: 1}], + expectedAfter: [], + }, +}; + +// Set up a set and grab things for later. +var name = "read_committed"; +var replTest = + new ReplSetTest({name: name, nodes: 3, nodeOptions: {enableMajorityReadConcern: ''}}); + +if (!startSetIfSupportsReadMajority(replTest)) { + jsTest.log("skipping test since storage engine doesn't support committed reads"); + replTest.stopSet(); + return; +} + +var nodes = replTest.nodeList(); +var config = { + "_id": name, + "members": [ + {"_id": 0, "host": nodes[0]}, + {"_id": 1, "host": nodes[1], priority: 0}, + {"_id": 2, "host": nodes[2], arbiterOnly: true} + ] +}; + +replTest.initiate(config); + +// Get connections and collection. +var primary = replTest.getPrimary(); +var secondary = replTest._slaves[0]; +var coll = primary.getDB(name)[name]; +var secondaryColl = secondary.getDB(name)[name]; + +function log(arg) { + jsTest.log(tojson(arg)); +} + +function doRead(coll, readConcern) { + readConcern.maxTimeMS = 3000; + var res = assert.commandWorked(coll.runCommand('find', readConcern)); + return new DBCommandCursor(coll.getDB(), res).toArray(); +} + +function doDirtyRead(coll) { + log("doing dirty read"); + var ret = doRead(coll, {"readConcern": {"level": "local"}}); + log("done doing dirty read."); + return ret; +} + +function doCommittedRead(coll) { + log("doing committed read"); + var ret = doRead(coll, {"readConcern": {"level": "majority"}}); + log("done doing committed read."); + return ret; +} + +function readLatestOplogEntry(readConcernLevel) { + var oplog = primary.getDB('local').oplog.rs; + var res = oplog.runCommand('find', { + "readConcern": {"level": readConcernLevel}, + "maxTimeMS": 3000, + sort: {$natural: -1}, + limit: 1, + }); + assert.commandWorked(res); + return new DBCommandCursor(coll.getDB(), res).toArray()[0]; +} + +for (var testName in testCases) { + jsTestLog('Running test ' + testName); + var test = testCases[testName]; + + const setUpInitialState = function setUpInitialState() { + assert.writeOK(coll.remove({}, majorityWriteConcern)); + test.prepareCollection(coll); + // Do some sanity checks. + assert.eq(doDirtyRead(coll), test.expectedBefore); + assert.eq(doCommittedRead(coll), test.expectedBefore); }; - // Set up a set and grab things for later. - var name = "read_committed"; - var replTest = - new ReplSetTest({name: name, nodes: 3, nodeOptions: {enableMajorityReadConcern: ''}}); - - if (!startSetIfSupportsReadMajority(replTest)) { - jsTest.log("skipping test since storage engine doesn't support committed reads"); - replTest.stopSet(); - return; - } - - var nodes = replTest.nodeList(); - var config = { - "_id": name, - "members": [ - {"_id": 0, "host": nodes[0]}, - {"_id": 1, "host": nodes[1], priority: 0}, - {"_id": 2, "host": nodes[2], arbiterOnly: true} - ] - }; - - replTest.initiate(config); - - // Get connections and collection. - var primary = replTest.getPrimary(); - var secondary = replTest._slaves[0]; - var coll = primary.getDB(name)[name]; - var secondaryColl = secondary.getDB(name)[name]; - - function log(arg) { - jsTest.log(tojson(arg)); - } - - function doRead(coll, readConcern) { - readConcern.maxTimeMS = 3000; - var res = assert.commandWorked(coll.runCommand('find', readConcern)); - return new DBCommandCursor(coll.getDB(), res).toArray(); - } - - function doDirtyRead(coll) { - log("doing dirty read"); - var ret = doRead(coll, {"readConcern": {"level": "local"}}); - log("done doing dirty read."); - return ret; - } - - function doCommittedRead(coll) { - log("doing committed read"); - var ret = doRead(coll, {"readConcern": {"level": "majority"}}); - log("done doing committed read."); - return ret; - } - - function readLatestOplogEntry(readConcernLevel) { - var oplog = primary.getDB('local').oplog.rs; - var res = oplog.runCommand('find', { - "readConcern": {"level": readConcernLevel}, - "maxTimeMS": 3000, - sort: {$natural: -1}, - limit: 1, + // Writes done with majority write concern must be immediately visible to both dirty and + // committed reads. + setUpInitialState(); + test.write(coll, majorityWriteConcern); + assert.eq(doDirtyRead(coll), test.expectedAfter); + assert.eq(doCommittedRead(coll), test.expectedAfter); + + // Return to the initial state, then stop the secondary from applying new writes to prevent + // them from becoming committed. + setUpInitialState(); + assert.commandWorked( + secondary.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "alwaysOn"})); + const initialOplogTs = readLatestOplogEntry('local').ts; + + // Writes done without majority write concern must be immediately visible to dirty read + // and hidden from committed reads until they have been replicated. The rules for seeing + // an oplog entry for a write are the same as for the write itself. + test.write(coll, {}); + assert.eq(doDirtyRead(coll), test.expectedAfter); + assert.neq(readLatestOplogEntry('local').ts, initialOplogTs); + assert.eq(doCommittedRead(coll), test.expectedBefore); + assert.eq(readLatestOplogEntry('majority').ts, initialOplogTs); + + // Try the committed read again after sleeping to ensure it doesn't only work for + // queries immediately after the write. + sleep(1000); + assert.eq(doCommittedRead(coll), test.expectedBefore); + assert.eq(readLatestOplogEntry('majority').ts, initialOplogTs); + + // Restart oplog application on the secondary and ensure the committed view is updated. + assert.commandWorked( + secondary.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "off"})); + coll.getDB().getLastError("majority", 60 * 1000); + assert.eq(doCommittedRead(coll), test.expectedAfter); + assert.neq(readLatestOplogEntry('majority').ts, initialOplogTs); + + // The secondary will be able to make the write committed soon after the primary, but there + // is no way to block until it does. + try { + assert.soon(function() { + return friendlyEqual(doCommittedRead(secondaryColl), test.expectedAfter); }); - assert.commandWorked(res); - return new DBCommandCursor(coll.getDB(), res).toArray()[0]; + } catch (e) { + // generate useful error messages on failures. + assert.eq(doCommittedRead(secondaryColl), test.expectedAfter); } - - for (var testName in testCases) { - jsTestLog('Running test ' + testName); - var test = testCases[testName]; - - const setUpInitialState = function setUpInitialState() { - assert.writeOK(coll.remove({}, majorityWriteConcern)); - test.prepareCollection(coll); - // Do some sanity checks. - assert.eq(doDirtyRead(coll), test.expectedBefore); - assert.eq(doCommittedRead(coll), test.expectedBefore); - }; - - // Writes done with majority write concern must be immediately visible to both dirty and - // committed reads. - setUpInitialState(); - test.write(coll, majorityWriteConcern); - assert.eq(doDirtyRead(coll), test.expectedAfter); - assert.eq(doCommittedRead(coll), test.expectedAfter); - - // Return to the initial state, then stop the secondary from applying new writes to prevent - // them from becoming committed. - setUpInitialState(); - assert.commandWorked( - secondary.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "alwaysOn"})); - const initialOplogTs = readLatestOplogEntry('local').ts; - - // Writes done without majority write concern must be immediately visible to dirty read - // and hidden from committed reads until they have been replicated. The rules for seeing - // an oplog entry for a write are the same as for the write itself. - test.write(coll, {}); - assert.eq(doDirtyRead(coll), test.expectedAfter); - assert.neq(readLatestOplogEntry('local').ts, initialOplogTs); - assert.eq(doCommittedRead(coll), test.expectedBefore); - assert.eq(readLatestOplogEntry('majority').ts, initialOplogTs); - - // Try the committed read again after sleeping to ensure it doesn't only work for - // queries immediately after the write. - sleep(1000); - assert.eq(doCommittedRead(coll), test.expectedBefore); - assert.eq(readLatestOplogEntry('majority').ts, initialOplogTs); - - // Restart oplog application on the secondary and ensure the committed view is updated. - assert.commandWorked( - secondary.adminCommand({configureFailPoint: "rsSyncApplyStop", mode: "off"})); - coll.getDB().getLastError("majority", 60 * 1000); - assert.eq(doCommittedRead(coll), test.expectedAfter); - assert.neq(readLatestOplogEntry('majority').ts, initialOplogTs); - - // The secondary will be able to make the write committed soon after the primary, but there - // is no way to block until it does. - try { - assert.soon(function() { - return friendlyEqual(doCommittedRead(secondaryColl), test.expectedAfter); - }); - } catch (e) { - // generate useful error messages on failures. - assert.eq(doCommittedRead(secondaryColl), test.expectedAfter); - } - } - replTest.stopSet(); +} +replTest.stopSet(); }()); |