summaryrefslogtreecommitdiff
path: root/jstests/sharding/cursor_timeout.js
blob: a6be1762245c4c56c90766002fad07cdaa17b85f (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
// Basic integration tests for the background job that periodically kills idle cursors, in both
// mongod and mongos.  This test creates the following four cursors:
//
// 1. A no-timeout cursor through mongos.
// 2. A no-timeout cursor through mongod.
// 3. A normal cursor through mongos.
// 4. A normal cursor through mongod.
//
// After a period of inactivity, the test asserts that cursors #1 and #2 are still alive, and that
// #3 and #4 have been killed.
(function() {
    'use strict';

    // Cursor timeout on mongod is handled by a single thread/timer that will sleep for
    // "clientCursorMonitorFrequencySecs" and add the sleep value to each operation's duration when
    // it wakes up, timing out those whose "now() - last accessed since" time exceeds. A cursor
    // timeout of 5 seconds with a monitor frequency of 1 second means an effective timeout period
    // of 4 to 5 seconds.
    const mongodCursorTimeoutMs = 5000;

    // Cursor timeout on mongos is handled by checking whether the "last accessed" cursor time stamp
    // is older than "now() - cursorTimeoutMillis" and is checked every
    // "clientCursorMonitorFrequencySecs" by a global thread/timer. A timeout of 4 seconds with a
    // monitor frequency of 1 second means an effective timeout period of 4 to 5 seconds.
    const mongosCursorTimeoutMs = 4000;

    const cursorMonitorFrequencySecs = 1;

    const st = new ShardingTest({
        shards: 2,
        other: {
            shardOptions: {
                verbose: 1,
                setParameter: {
                    cursorTimeoutMillis: mongodCursorTimeoutMs,
                    clientCursorMonitorFrequencySecs: cursorMonitorFrequencySecs
                }
            },
            mongosOptions: {
                verbose: 1,
                setParameter: {
                    cursorTimeoutMillis: mongosCursorTimeoutMs,
                    clientCursorMonitorFrequencySecs: cursorMonitorFrequencySecs
                }
            },
        },
        enableBalancer: false
    });

    const adminDB = st.admin;
    const routerColl = st.s.getDB('test').user;

    const shardHost = st.config.shards.findOne({_id: st.shard1.shardName}).host;
    const mongod = new Mongo(shardHost);
    const shardColl = mongod.getCollection(routerColl.getFullName());

    assert.commandWorked(adminDB.runCommand({enableSharding: routerColl.getDB().getName()}));
    st.ensurePrimaryShard(routerColl.getDB().getName(), st.shard0.shardName);

    assert.commandWorked(
        adminDB.runCommand({shardCollection: routerColl.getFullName(), key: {x: 1}}));
    assert.commandWorked(adminDB.runCommand({split: routerColl.getFullName(), middle: {x: 10}}));
    assert.commandWorked(adminDB.runCommand({
        moveChunk: routerColl.getFullName(),
        find: {x: 11},
        to: st.shard1.shardName,
        _waitForDelete: true
    }));

    for (let x = 0; x < 20; x++) {
        assert.writeOK(routerColl.insert({x: x}));
    }

    // Open both a normal and a no-timeout cursor on mongos. Batch size is 1 to ensure that
    // cursor.next() performs only a single operation.
    const routerCursorWithTimeout = routerColl.find().batchSize(1);
    const routerCursorWithNoTimeout = routerColl.find().batchSize(1);
    routerCursorWithNoTimeout.addOption(DBQuery.Option.noTimeout);

    // Open both a normal and a no-timeout cursor on mongod. Batch size is 1 to ensure that
    // cursor.next() performs only a single operation.
    const shardCursorWithTimeout = shardColl.find().batchSize(1);
    const shardCursorWithNoTimeout = shardColl.find().batchSize(1);
    shardCursorWithNoTimeout.addOption(DBQuery.Option.noTimeout);

    // Execute initial find on each cursor.
    routerCursorWithTimeout.next();
    routerCursorWithNoTimeout.next();
    shardCursorWithTimeout.next();
    shardCursorWithNoTimeout.next();

    // Wait until the idle cursor background job has killed the cursors that do not have the "no
    // timeout" flag set.  We use the "cursorTimeoutMillis" and "clientCursorMonitorFrequencySecs"
    // setParameters above to reduce the amount of time we need to wait here.
    assert.soon(function() {
        return routerColl.getDB().serverStatus().metrics.cursor.timedOut > 0;
    }, "sharded cursor failed to time out");

    // Wait for the shard to have two open cursors on it (routerCursorWithNoTimeout and
    // shardCursorWithNoTimeout).
    // We cannot reliably use metrics.cursor.timedOut here, because this will be 2 if
    // routerCursorWithTimeout is killed for timing out on the shard, and 1 if
    // routerCursorWithTimeout is killed by a killCursors command from the mongos.
    assert.soon(function() {
        return shardColl.getDB().serverStatus().metrics.cursor.open.total == 2;
    }, "cursor failed to time out");

    assert.throws(function() {
        routerCursorWithTimeout.itcount();
    });
    assert.throws(function() {
        shardCursorWithTimeout.itcount();
    });

    // +1 because we already advanced once
    assert.eq(routerColl.count(), routerCursorWithNoTimeout.itcount() + 1);
    assert.eq(shardColl.count(), shardCursorWithNoTimeout.itcount() + 1);

    st.stop();
})();