summaryrefslogtreecommitdiff
path: root/jstests/libs/pin_getmore_cursor.js
blob: 01bc0bab9f30e3b2f2266596c987e8f13bc42d81 (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
/**
 * Creates a cursor, then pins it in a parallel shell by using the provided 'failPointName' and
 * 'runGetMoreFunc'. Runs 'assertFunction' while the cursor is pinned, then unpins it.
 *
 * 'conn': a connection to an instance of a mongod or mongos.
 * 'sessionId': The id present if the database is currently in a session.
 * 'dbName': the database to use with the cursor.
 *
 * 'assertFunction(cursorId, coll)':
 *   A function containing the test to be run while the cursor is pinned.
 *
 * 'runGetMoreFunc':
 *   A function to be executed in the parallel shell. It is expected to hit the fail point, defined
 *   in 'failPointName' by calling 'db.runCommand({getMore: cursorId, collection: collName})' but
 *   it can do additional validation on the result of the command or run other commands.
 *
 * 'failPointName': name of the failpoint where 'runGetMoreFunc' is expected to hang.
 *
 * 'assertEndCounts': whether to assert zero pinned cursors an the end.
 */

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

function withPinnedCursor(
    {conn, sessionId, db, assertFunction, runGetMoreFunc, failPointName, assertEndCounts}) {
    // This test runs manual getMores using different connections, which will not inherit the
    // implicit session of the cursor establishing command.
    TestData.disableImplicitSessions = true;

    const coll = db.jstest_with_pinned_cursor;
    coll.drop();
    db.active_cursor_sentinel.drop();
    for (let i = 0; i < 100; ++i) {
        assert.commandWorked(coll.insert({value: i}));
    }
    let cleanup = null;
    try {
        // Issue an initial find in order to create a cursor and obtain its cursorID.
        let cmdRes = db.runCommand({find: coll.getName(), batchSize: 2});
        assert.commandWorked(cmdRes);
        const cursorId = cmdRes.cursor.id;
        assert.neq(cursorId, NumberLong(0));

        // Enable the specified failpoint.
        assert.commandWorked(
            db.adminCommand({configureFailPoint: failPointName, mode: "alwaysOn"}));

        // In a different shell pin the cursor by calling 'getMore' on it that would be blocked by
        // the failpoint.
        cleanup =
            startParallelShell(funWithArgs(function(runGetMoreFunc, collName, cursorId, sessionId) {
                                   runGetMoreFunc(collName, cursorId, sessionId);
                                   db.active_cursor_sentinel.insert({});
                               }, runGetMoreFunc, coll.getName(), cursorId, sessionId), conn.port);

        // Wait until we know the failpoint has been reached.
        waitForCurOpByFailPointNoNS(db, failPointName, {}, {localOps: true, allUsers: true});

        // The assert function might initiate killing of the cursor. Because the cursor is pinned,
        // it actually won't be killed until the pin is removed but it will interrupt 'getMore' in
        // the parallel shell after the failpoint is unset.
        assertFunction(cursorId, coll);

        // Unsetting the failpoint allows getMore in the parallel shell to proceed and unpins the
        // cursor, which will either exhaust or detect interrupt (if 'assertFunction' killed the
        // cursor).
        assert.commandWorked(db.adminCommand({configureFailPoint: failPointName, mode: "off"}));

        // Wait for the parallel shell to be done with 'getMore' command. We'd know when it moves on
        // to inserting the sentinel object.
        assert.soon(() => db.active_cursor_sentinel.find().itcount() > 0);

        // Give the server up to 5 sec to dispose of the cursor.
        if (assertEndCounts) {
            assert.retry(
                () => {
                    return db.serverStatus().metrics.cursor.open.pinned == 0;
                },
                "Expected 0 pinned cursors, but have " + tojson(db.serverStatus().metrics.cursor),
                10 /* num_attempts */,
                500 /* intervalMS */);
        }

        // By now either getMore in the parallel shell has exhausted the cursor, or the cursor has
        // been killed by 'assertFunction'. In both cases, an attempt to kill the cursor again
        // should report it as not found.
        cmdRes = db.runCommand({killCursors: coll.getName(), cursors: [cursorId]});
        assert.commandWorked(cmdRes);
        assert.eq(cmdRes.cursorsKilled, []);
        assert.eq(cmdRes.cursorsAlive, []);
        assert.eq(cmdRes.cursorsNotFound, [cursorId]);
        assert.eq(cmdRes.cursorsUnknown, []);
    } finally {
        if (cleanup) {
            cleanup();
        }
    }
}