summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/kill_pinned_cursor.js
blob: 01e18393d69a87b9af5eba6e73e281050d73616d (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
// @tags: [
//   does_not_support_stepdowns,
//   requires_getmore,
//   requires_replication,
//    # TODO SERVER-64007: Support yielding in CQF plans.
//   cqf_incompatible,
// ]
//
// Uses getMore to pin an open cursor.
//
// Does not support stepdowns because if a stepdown were to occur between running find() and
// calling killCursors on the cursor ID returned by find(), the killCursors might be sent to
// different node than the one which has the cursor. This would result in the node returning
// "CursorNotFound."
//
// Test killing a pinned cursor. Since cursors are generally pinned for short periods while result
// batches are generated, this requires some special machinery to keep a cursor permanently pinned.

(function() {
"use strict";

// This test runs manual getMores using different connections, which will not inherit the
// implicit session of the cursor establishing command.
TestData.disableImplicitSessions = true;

load("jstests/libs/fixture_helpers.js");     // For "isMongos".
load("jstests/libs/pin_getmore_cursor.js");  // For "withPinnedCursor".
const st = new ShardingTest({shards: 2});

// Enables the specified 'failPointName', executes 'runGetMoreFunc' function in a parallel
// shell, waits for the the failpoint to be hit, then kills the cursor and confirms that the
// kill was successful.
function runPinnedCursorKillTest({conn, failPointName, runGetMoreFunc}) {
    function assertFunction(cursorId, coll) {
        const db = coll.getDB();
        // Kill the cursor associated with the command and assert that the kill succeeded.
        let cmdRes = db.runCommand({killCursors: coll.getName(), cursors: [cursorId]});
        assert.commandWorked(cmdRes);
        assert.eq(cmdRes.cursorsKilled, [cursorId]);
        assert.eq(cmdRes.cursorsAlive, []);
        assert.eq(cmdRes.cursorsNotFound, []);
        assert.eq(cmdRes.cursorsUnknown, []);
    }
    withPinnedCursor({
        conn: conn,
        sessionId: null,
        db: conn.getDB("test"),
        assertFunction: assertFunction,
        runGetMoreFunc: runGetMoreFunc,
        failPointName: failPointName,
        assertEndCounts: true
    });
}

// Test that killing the pinned cursor before it starts building the batch results in a
// CursorKilled exception on a replica set.
const rs0Conn = st.rs0.getPrimary();
const testParameters = {
    conn: rs0Conn,
    failPointName: "waitAfterPinningCursorBeforeGetMoreBatch",
    runGetMoreFunc: function(collName, cursorId) {
        const response = db.runCommand({getMore: cursorId, collection: collName});
        // We expect that the operation will get interrupted and fail.
        assert.commandFailedWithCode(response, ErrorCodes.CursorKilled);
    }
};
runPinnedCursorKillTest(testParameters);

// Check the case where a killCursor is run as we're building a getMore batch on mongod.
(function() {
testParameters.conn = rs0Conn;
testParameters.failPointName = "waitWithPinnedCursorDuringGetMoreBatch";

// Force yield to occur on every PlanExecutor iteration, so that the getMore is guaranteed
// to check for interrupts.
assert.commandWorked(testParameters.conn.getDB("admin").runCommand(
    {setParameter: 1, internalQueryExecYieldIterations: 1}));
runPinnedCursorKillTest(testParameters);
})();

(function() {
// Run the equivalent test on the mongos. This time, we will force the shards to hang as
// well. This is so that we can guarantee that the mongos is checking for interruption at
// the appropriate time, and not just propagating an error it receives from the mongods.
testParameters.failPointName = "waitAfterPinningCursorBeforeGetMoreBatch";
FixtureHelpers.runCommandOnEachPrimary({
    db: st.s.getDB("admin"),
    cmdObj: {configureFailPoint: "waitAfterPinningCursorBeforeGetMoreBatch", mode: "alwaysOn"}
});
testParameters.conn = st.s;
runPinnedCursorKillTest(testParameters);
FixtureHelpers.runCommandOnEachPrimary({
    db: st.s.getDB("admin"),
    cmdObj: {configureFailPoint: "waitAfterPinningCursorBeforeGetMoreBatch", mode: "off"}
});
})();

// Check this case where the interrupt comes in after the batch has been built, and is about to
// be returned. This is relevant for both mongod and mongos.
const connsToRunOn = [st.s, rs0Conn];
for (let conn of connsToRunOn) {
    jsTestLog("Running on conn: " + tojson(conn));

    // Test that, if the pinned cursor is killed after it has finished building a batch, that
    // batch is returned to the client but a subsequent getMore will fail with a
    // 'CursorNotFound' error.
    testParameters.failPointName = "waitBeforeUnpinningOrDeletingCursorAfterGetMoreBatch";
    testParameters.runGetMoreFunc = function(collName, cursorId) {
        const getMoreCmd = {getMore: cursorId, collection: collName, batchSize: 2};
        // We expect that the first getMore will succeed, while the second fails because the
        // cursor has been killed.
        assert.commandWorked(db.runCommand(getMoreCmd));
        assert.commandFailedWithCode(db.runCommand(getMoreCmd), ErrorCodes.CursorNotFound);
    };

    runPinnedCursorKillTest(testParameters);
}

st.stop();
})();