summaryrefslogtreecommitdiff
path: root/jstests/sharding/query/cursor_timeout.js
blob: 595175b32ae852f58c1759ea232606aacf87ca69 (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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// 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, not attached to a session.
// 2. A no-timeout cursor through mongod, not attached to a session.
// 3. A normal cursor through mongos, not attached to a session.
// 4. A normal cursor through mongod, not attached to a session.
// 5. A normal cursor through mongos, attached to a session.
// 6. A normal cursor through mongod, attached to a session.
//
// After a period of inactivity, the test asserts that cursors #1 and #2 are still alive, cursors #3
// and #4 have been killed, and cursors #5 and #6 are still alive (as of SERVER-6036, only cursors
// not opened as part of a session should be killed by the cursor-timeout mechanism). It then kills
// the session cursors #5 and #6 are attached to to simulate that session timing out, and ensures
// that cursors #5 and #6 are killed as a result.
//
// @tags: [
//   requires_sharding, requires_fcv_51
// ]
(function() {
'use strict';

// This test manually simulates a session, which is not compatible with implicit sessions.
TestData.disableImplicitSessions = true;

// 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 mongosDB = st.s.getDB('test');
const routerColl = mongosDB.user;

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

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.commandWorked(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 a session on mongos.
let routerSession = mongosDB.getMongo().startSession();
let routerSessionDB = routerSession.getDatabase(mongosDB.getName());
let routerSessionCursor = routerSessionDB.user.find().batchSize(1);

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

// Open a session on mongod.
let shardSession = shardDB.getMongo().startSession();
let shardSessionDB = shardSession.getDatabase(shardDB.getName());
let shardSessionCursor = shardSessionDB.user.find().batchSize(1);

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

// Wait until the idle cursor background job has killed the session-unattached 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 four open cursors on it (routerCursorWithNoTimeout,
// routerSessionCursor, shardCursorWithNoTimeout, and shardSessionCursor).  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 == 4;
}, "cursor failed to time out");

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

// Kill the session that routerSessionCursor and shardSessionCursor are attached to, to simulate
// that session's expiration. The cursors should be killed and cleaned up once the session is.
assert.commandWorked(mongosDB.runCommand({killSessions: [routerSession.getSessionId()]}));
assert.commandWorked(shardDB.runCommand({killSessions: [shardSession.getSessionId()]}));

// Wait for the shard to have two open cursors on it (routerCursorWithNoTimeout,
// shardCursorWithNoTimeout).
assert.soon(function() {
    return shardColl.getDB().serverStatus().metrics.cursor.open.total == 2;
}, "session cursor failed to time out");

assert.throws(function() {
    routerSessionCursor.itcount();
});
assert.throws(function() {
    shardSessionCursor.itcount();
});

// Verify that the session cursors are really gone by running a killCursors command, and checking
// that the cursorors are reported as "not found". Credit to kill_pinned_cursor_js_test for this
// idea.
let killRes = mongosDB.runCommand({
    killCursors: routerColl.getName(),
    cursors: [routerSessionCursor.getId(), shardSessionCursor.getId()]
});
assert.commandWorked(killRes);
assert.eq(killRes.cursorsAlive, []);
assert.eq(killRes.cursorsNotFound, [routerSessionCursor.getId(), shardSessionCursor.getId()]);
assert.eq(killRes.cursorsUnknown, []);

// +1 because we already advanced one. Ensure that the session-unattached cursors are still valid.
assert.eq(routerColl.count(), routerCursorWithNoTimeout.itcount() + 1);
assert.eq(shardColl.count(), shardCursorWithNoTimeout.itcount() + 1);

// Confirm that cursors opened within a session will timeout when the
// 'enableTimeoutOfInactiveSessionCursors' setParameter has been enabled.
(function() {
assert.commandWorked(
    mongosDB.adminCommand({setParameter: 1, enableTimeoutOfInactiveSessionCursors: true}));
assert.commandWorked(
    shardDB.adminCommand({setParameter: 1, enableTimeoutOfInactiveSessionCursors: true}));

// Open a session on mongos.
routerSession = mongosDB.getMongo().startSession();
routerSessionDB = routerSession.getDatabase(mongosDB.getName());
routerSessionCursor = routerSessionDB.user.find().batchSize(1);
const numRouterCursorsTimedOut = routerColl.getDB().serverStatus().metrics.cursor.timedOut;

// Open a session on mongod.
shardSession = shardDB.getMongo().startSession();
shardSessionDB = shardSession.getDatabase(shardDB.getName());
shardSessionCursor = shardSessionDB.user.find().batchSize(1);
const numShardCursorsTimedOut = routerColl.getDB().serverStatus().metrics.cursor.timedOut;

// Execute initial find on each cursor.
routerSessionCursor.next();
shardSessionCursor.next();

// Wait until mongos reflects the newly timed out cursors.
assert.soon(function() {
    return shardColl.getDB().serverStatus().metrics.cursor.timedOut >=
        (numRouterCursorsTimedOut + 1);
}, "sharded cursor failed to time out");

// Wait until mongod reflects the newly timed out cursors.
assert.soon(function() {
    return routerColl.getDB().serverStatus().metrics.cursor.timedOut >=
        (numShardCursorsTimedOut + 1);
}, "router cursor failed to time out");

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

assert.commandWorked(
    mongosDB.adminCommand({setParameter: 1, enableTimeoutOfInactiveSessionCursors: false}));
assert.commandWorked(
    shardDB.adminCommand({setParameter: 1, enableTimeoutOfInactiveSessionCursors: false}));
})();

st.stop();
})();