summaryrefslogtreecommitdiff
path: root/jstests/core/mr_killop.js
blob: b2afe735e01c650bd8d27318f6fe76c0c8172fe4 (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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// Cannot implicitly shard accessed collections because the "command" field in the currentOp()
// output is reported as {"mapreduce.shardedfinish": { mapreduce: "jstests_mr_killop", ... }, ... }
// when the "finalize" option to the "mapReduce" command is used on a sharded collection.
// @tags: [assumes_unsharded_collection, does_not_support_stepdowns]

// Test killop applied to m/r operations and child ops of m/r operations.

t = db.jstests_mr_killop;
t.drop();
t2 = db.jstests_mr_killop_out;
t2.drop();
db.adminCommand({"configureFailPoint": 'mr_killop_test_fp', "mode": 'alwaysOn'});
function debug(x) {
    //        printjson( x );
}

/** @return op code for map reduce op created by spawned shell, or that op's child */
function op(childLoop) {
    p = db.currentOp().inprog;
    debug(p);

    let isMapReduce = function(op) {
        if (!op.command) {
            return false;
        }
        let cmdBody = op.command;
        if (cmdBody.$truncated) {
            let stringifiedCmd = cmdBody.$truncated;
            print('str: ' + tojson(stringifiedCmd));
            return stringifiedCmd.search('mapreduce') >= 0 &&
                stringifiedCmd.search('jstests_mr_killop') >= 0;
        }
        return cmdBody.mapreduce && cmdBody.mapreduce == "jstests_mr_killop";
    };

    for (var i in p) {
        var o = p[i];
        // Identify a map/reduce or where distinct operation by its collection, whether or not
        // it is currently active.
        if (childLoop) {
            if ((o.active || o.waitingForLock) && o.command && o.command.query &&
                o.command.query.$where && o.command.distinct == "jstests_mr_killop") {
                return o.opid;
            }
        } else {
            if ((o.active || o.waitingForLock) && isMapReduce(o)) {
                return o.opid;
            }
        }
    }
    return -1;
}

/**
* Run one map reduce with the specified parameters in a parallel shell, kill the
* map reduce op or its child op with killOp, and wait for the map reduce op to
* terminate.
* @param childLoop - if true, a distinct $where op is killed rather than the map reduce op.
* This is necessay for a child distinct $where of a map reduce op because child
* ops currently mask parent ops in currentOp.
*/
function testOne(map, reduce, finalize, scope, childLoop, wait) {
    debug("testOne - map = " + tojson(map) + "; reduce = " + tojson(reduce) + "; finalize = " +
          tojson(finalize) + "; scope = " + tojson(scope) + "; childLoop = " + childLoop +
          "; wait = " + wait);

    t.drop();
    t2.drop();
    // Ensure we have 2 documents for the reduce to run
    t.save({a: 1});
    t.save({a: 1});

    spec = {mapreduce: "jstests_mr_killop", out: "jstests_mr_killop_out", map: map, reduce: reduce};
    if (finalize) {
        spec["finalize"] = finalize;
    }
    if (scope) {
        spec["scope"] = scope;
    }

    // Windows shell strips all double quotes from command line, so use
    // single quotes.
    stringifiedSpec = tojson(spec).toString().replace(/\n/g, ' ').replace(/\"/g, "\'");

    // The assert below won't be caught by this test script, but it will cause error messages
    // to be printed.
    var awaitShell =
        startParallelShell("assert.commandWorked( db.runCommand( " + stringifiedSpec + " ) );");

    if (wait) {
        sleep(2000);
    }

    o = null;
    assert.soon(function() {
        o = op(childLoop);
        return o != -1;
    });

    res = db.killOp(o);
    debug("did kill : " + tojson(res));

    // When the map reduce op is killed, the spawned shell will exit
    var exitCode = awaitShell({checkExitSuccess: false});
    assert.neq(0,
               exitCode,
               "expected shell to exit abnormally due to map-reduce execution being terminated");
    debug("parallel shell completed");

    assert.eq(-1, op(childLoop));
}

/** Test using wait and non wait modes */
function test(map, reduce, finalize, scope, childLoop) {
    debug(" Non wait mode");
    testOne(map, reduce, finalize, scope, childLoop, false);

    debug(" Wait mode");
    testOne(map, reduce, finalize, scope, childLoop, true);
}

/** Test looping in map and reduce functions */
function runMRTests(loop, childLoop) {
    debug(" Running MR test - loop map function. no scope ");
    test(loop,  // map
         function(k, v) {
             return v[0];
         },     // reduce
         null,  // finalize
         null,  // scope
         childLoop);

    debug(" Running MR test - loop reduce function ");
    test(
        function() {
            emit(this.a, 1);
        },     // map
        loop,  // reduce
        null,  // finalize
        null,  // scope
        childLoop);

    debug(" Running finalization test - loop map function. with scope ");
    test(
        function() {
            loop();
        },  // map
        function(k, v) {
            return v[0];
        },             // reduce
        null,          // finalize
        {loop: loop},  // scope
        childLoop);
}

/** Test looping in finalize function */
function runFinalizeTests(loop, childLoop) {
    debug(" Running finalization test - no scope ");
    test(
        function() {
            emit(this.a, 1);
        },  // map
        function(k, v) {
            return v[0];
        },     // reduce
        loop,  // finalize
        null,  // scope
        childLoop);

    debug(" Running finalization test - with scope ");
    test(
        function() {
            emit(this.a, 1);
        },  // map
        function(k, v) {
            return v[0];
        },  // reduce
        function(a, b) {
            loop();
        },             // finalize
        {loop: loop},  // scope
        childLoop);
}

// Run inside server. No access to debug().
var loop = function() {
    while (1) {
        sleep(1000);
    }
};
runMRTests(loop, false);
runFinalizeTests(loop, false);
db.adminCommand({"configureFailPoint": 'mr_killop_test_fp', "mode": 'off'});