// Test that the read concern level 'snapshot' exhibits the correct yielding behavior. That is,
|
// operations performed at read concern level snapshot check for interrupt but do not yield locks or
|
// storage engine resources.
|
// @tags: [
|
// uses_transactions,
|
// ]
|
import {waitForCurOpByFailPoint} from "jstests/libs/curop_helpers.js";
|
|
const dbName = "test";
|
const collName = "coll";
|
|
const rst = new ReplSetTest({nodes: 1});
|
rst.startSet();
|
rst.initiate();
|
const db = rst.getPrimary().getDB(dbName);
|
const adminDB = db.getSiblingDB("admin");
|
const coll = db.coll;
|
TestData.numDocs = 4;
|
|
// Set 'internalQueryExecYieldIterations' to 2 to ensure that commands yield on the second try
|
// (i.e. after they have established a snapshot but before they have returned any documents).
|
assert.commandWorked(db.adminCommand({setParameter: 1, internalQueryExecYieldIterations: 2}));
|
|
// Set 'internalQueryExecYieldPeriodMS' to 24 hours to significantly reduce a probability of a
|
// situation occuring where the execution threads do not receive enough CPU time and commands yield
|
// on timeout (i.e. yield period expiration) instead of on the second try as expected by setting
|
// parameter 'internalQueryExecYieldIterations' to 2.
|
assert.commandWorked(db.adminCommand({setParameter: 1, internalQueryExecYieldPeriodMS: 86400000}));
|
|
function assertKillPending(opId) {
|
const res =
|
adminDB.aggregate([{$currentOp: {}}, {$match: {ns: coll.getFullName(), opid: opId}}])
|
.toArray();
|
assert.eq(
|
res.length,
|
1,
|
tojson(
|
adminDB.aggregate([{$currentOp: {}}, {$match: {ns: coll.getFullName()}}]).toArray()));
|
assert(res[0].hasOwnProperty("killPending"), tojson(res));
|
assert.eq(true, res[0].killPending, tojson(res));
|
}
|
|
function populateCollection() {
|
db.coll.drop({writeConcern: {w: "majority"}});
|
for (let i = 0; i < TestData.numDocs; i++) {
|
assert.commandWorked(
|
db.coll.insert({_id: i, x: 1, location: [0, 0]}, {writeConcern: {w: "majority"}}));
|
}
|
|
assert.commandWorked(db.runCommand({
|
createIndexes: "coll",
|
indexes: [{key: {location: "2d"}, name: "geo_2d"}],
|
writeConcern: {w: "majority"}
|
}));
|
}
|
|
function testCommand(awaitCommandFn, curOpFilter, testWriteConflict, setYieldIterations) {
|
//
|
// Test that the command does not yield locks.
|
//
|
|
TestData.txnNumber++;
|
populateCollection();
|
|
// Start a command that hangs before checking for interrupt.
|
assert.commandWorked(db.adminCommand(
|
{configureFailPoint: "setInterruptOnlyPlansCheckForInterruptHang", mode: "alwaysOn"}));
|
let awaitCommand = startParallelShell(awaitCommandFn, rst.ports[0]);
|
waitForCurOpByFailPoint(
|
db, coll.getFullName(), "setInterruptOnlyPlansCheckForInterruptHang", curOpFilter);
|
|
// Start a drop. This should block behind the command, since the command does not yield
|
// locks.
|
let awaitDrop = startParallelShell(function() {
|
db.getSiblingDB("test").coll.drop({writeConcern: {w: "majority"}});
|
}, rst.ports[0]);
|
|
// Remove the hang. The command should complete successfully.
|
assert.commandWorked(db.adminCommand(
|
{configureFailPoint: "setInterruptOnlyPlansCheckForInterruptHang", mode: "off"}));
|
awaitCommand();
|
|
// Now the drop can complete.
|
awaitDrop();
|
|
//
|
// Test that the command does not read data that is inserted during its execution.
|
// 'awaitCommandFn' should fail if it reads the following document:
|
// {_id: <numDocs>, x: 1, new: 1, location: [0, 0]}
|
//
|
|
TestData.txnNumber++;
|
populateCollection();
|
|
// Start a command that hangs before checking for interrupt.
|
assert.commandWorked(db.adminCommand(
|
{configureFailPoint: "setInterruptOnlyPlansCheckForInterruptHang", mode: "alwaysOn"}));
|
awaitCommand = startParallelShell(awaitCommandFn, rst.ports[0]);
|
waitForCurOpByFailPoint(
|
db, coll.getFullName(), "setInterruptOnlyPlansCheckForInterruptHang", curOpFilter);
|
|
// Insert data that should not be read by the command.
|
assert.commandWorked(db.coll.insert({_id: TestData.numDocs, x: 1, new: 1, location: [0, 0]},
|
{writeConcern: {w: "majority"}}));
|
|
// Remove the hang. The command should complete successfully.
|
assert.commandWorked(db.adminCommand(
|
{configureFailPoint: "setInterruptOnlyPlansCheckForInterruptHang", mode: "off"}));
|
awaitCommand();
|
}
|
|
// Test delete.
|
testCommand(function() {
|
const session = db.getMongo().startSession({causalConsistency: false});
|
const sessionDb = session.getDatabase("test");
|
session.startTransaction({readConcern: {level: "snapshot"}});
|
const res = assert.commandWorked(sessionDb.runCommand(
|
{delete: "coll", deletes: [{q: {}, limit: 1}, {q: {new: 1}, limit: 1}]}));
|
assert.commandWorked(session.commitTransaction_forTesting());
|
// Only remove one existing doc committed before the transaction.
|
assert.eq(res.n, 1, tojson(res));
|
}, {op: "remove"}, true);
|
|
rst.stopSet();
|