summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/child_op_numyields.js
blob: 04c79d308ba4623eca795478baa81b200b4d946c (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
112
113
114
115
116
117
118
119
120
/**
 * 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";

    // 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;

    // Queries current operations until a single matching operation is found.
    function awaitMatchingCurrentOp(match) {
        let currentOp = null;
        assert.soon(() => {
            currentOp = adminDB.aggregate([{$currentOp: {}}, match]).toArray();
            return (currentOp.length === 1);
        });
        return currentOp[0];
    }

    // 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 = awaitMatchingCurrentOp(
                {$match: {ns: testColl.getFullName(), msg: "hangBeforeChildRemoveOpFinishes"}});

            // 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"}));
            awaitMatchingCurrentOp(
                {$match: {ns: testColl.getFullName(), msg: "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 = awaitMatchingCurrentOp(
            {$match: {opid: childOpId, op: "command", msg: "hangAfterAllChildRemoveOpsArePopped"}});

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