summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/child_op_numyields.js
blob: 94e2e0b14c1206ee0904655ad71f3191ef0078eb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
 * Confirms that a parent operation correctly inherits 'numYields' from each of its child operations
 * as the latter are popped off the CurOp stack.
 */
(function() {
"use strict";

load("jstests/libs/curop_helpers.js");  // For waitForCurOpByFailPoint().

// Start a single mongoD using MongoRunner.
const conn = MongoRunner.runMongod({});
assert.neq(null, conn, "mongod was unable to start up");

// Create the test DB and collection.
const testDB = conn.getDB("currentop_yield");
const adminDB = conn.getDB("admin");
const testColl = testDB.test;

// Executes a bulk remove using the specified 'docsToRemove' array, captures the 'numYields'
// metrics from each child op, and confirms that the parent op's 'numYields' total is equivalent
// to the sum of the child ops.
function runYieldTest(docsToRemove) {
    // Sets parameters such that all operations will yield & the operation hangs on the server
    // when we need to test.
    assert.commandWorked(
        testDB.adminCommand({setParameter: 1, internalQueryExecYieldIterations: 1}));
    assert.commandWorked(testDB.adminCommand(
        {configureFailPoint: "hangBeforeChildRemoveOpFinishes", mode: "alwaysOn"}));
    assert.commandWorked(testDB.adminCommand(
        {configureFailPoint: "hangAfterAllChildRemoveOpsArePopped", mode: "alwaysOn"}));

    // Starts parallel shell to run the command that will hang.
    const awaitShell = startParallelShell(`{
            const testDB = db.getSiblingDB("currentop_yield");
            const bulkRemove = testDB.test.initializeOrderedBulkOp();
            for(let doc of ${tojsononeline(docsToRemove)}) {
                bulkRemove.find(doc).removeOne();
            }
            bulkRemove.execute();
        }`,
                                              testDB.getMongo().port);

    let childOpId = null;
    let childYields = 0;

    // Get child operations and sum yields. Each child op encounters two failpoints while
    // running: 'hangBeforeChildRemoveOpFinishes' followed by 'hangBeforeChildRemoveOpIsPopped'.
    // We use these two failpoints as an 'airlock', hanging at the first while we enable the
    // second, then hanging at the second while we enable the first, to ensure that each child
    // op is caught and their individual 'numYields' recorded.
    for (let childCount = 0; childCount < docsToRemove.length; childCount++) {
        // Wait for the child op to hit the first of two failpoints.
        let childCurOp = waitForCurOpByFailPoint(
            testDB, testColl.getFullName(), "hangBeforeChildRemoveOpFinishes")[0];

        // Add the child's yield count to the running total, and record the opid.
        assert(childOpId === null || childOpId === childCurOp.opid);
        assert.gt(childCurOp.numYields, 0);
        childYields += childCurOp.numYields;
        childOpId = childCurOp.opid;

        // Enable the subsequent 'hangBeforeChildRemoveOpIsPopped' failpoint, just after the
        // child op finishes but before it is popped from the stack.
        assert.commandWorked(testDB.adminCommand(
            {configureFailPoint: "hangBeforeChildRemoveOpIsPopped", mode: "alwaysOn"}));

        // Let the operation proceed to the 'hangBeforeChildRemoveOpIsPopped' failpoint.
        assert.commandWorked(testDB.adminCommand(
            {configureFailPoint: "hangBeforeChildRemoveOpFinishes", mode: "off"}));
        waitForCurOpByFailPoint(testDB, testColl.getFullName(), "hangBeforeChildRemoveOpIsPopped");

        // If this is not the final child op, re-enable the 'hangBeforeChildRemoveOpFinishes'
        // failpoint from earlier so that we don't miss the next child.
        if (childCount + 1 < docsToRemove.length) {
            assert.commandWorked(testDB.adminCommand(
                {configureFailPoint: "hangBeforeChildRemoveOpFinishes", mode: "alwaysOn"}));
        }

        // Finally, allow the operation to continue.
        assert.commandWorked(testDB.adminCommand(
            {configureFailPoint: "hangBeforeChildRemoveOpIsPopped", mode: "off"}));
    }

    // Wait for the operation to hit the 'hangAfterAllChildRemoveOpsArePopped' failpoint, then
    // take the total number of yields recorded by the parent op.
    const parentCurOp = waitForCurOpByFailPointNoNS(
        testDB, "hangAfterAllChildRemoveOpsArePopped", {opid: childOpId})[0];

    // Verify that the parent's yield count equals the sum of the child ops' yields.
    assert.eq(parentCurOp.numYields, childYields);
    assert.eq(parentCurOp.opid, childOpId);

    // Allow the parent operation to complete.
    assert.commandWorked(testDB.adminCommand(
        {configureFailPoint: "hangAfterAllChildRemoveOpsArePopped", mode: "off"}));

    // Wait for the parallel shell to complete.
    awaitShell();
}

// Test that a parent remove op inherits the sum of its children's yields for a single remove.
assert.commandWorked(testDB.test.insert({a: 2}));
runYieldTest([{a: 2}]);

// Test that a parent remove op inherits the sum of its children's yields for multiple removes.
const docsToTest = [{a: 1}, {a: 2}, {a: 3}, {a: 4}, {a: 5}];
assert.commandWorked(testDB.test.insert(docsToTest));
runYieldTest(docsToTest);

MongoRunner.stopMongod(conn);
})();