summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/agg_cursor_timeout.js
diff options
context:
space:
mode:
authorNick Zolnierz <nicholas.zolnierz@mongodb.com>2020-03-18 15:17:56 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-03-25 17:40:43 +0000
commitfe79c4ee1dfc8d49ae06c94a927267712b32b011 (patch)
treea5ddce887802b62fb028c5d707186ebb64effe66 /jstests/noPassthrough/agg_cursor_timeout.js
parentb36c69c5930d25a8f5ae348a2b2fb24f27f925e6 (diff)
downloadmongo-fe79c4ee1dfc8d49ae06c94a927267712b32b011.tar.gz
SERVER-46700 Update tests in aggregation suite to avoid spawning mongod/sharded clusters
Diffstat (limited to 'jstests/noPassthrough/agg_cursor_timeout.js')
-rw-r--r--jstests/noPassthrough/agg_cursor_timeout.js126
1 files changed, 126 insertions, 0 deletions
diff --git a/jstests/noPassthrough/agg_cursor_timeout.js b/jstests/noPassthrough/agg_cursor_timeout.js
new file mode 100644
index 00000000000..ff5d7001374
--- /dev/null
+++ b/jstests/noPassthrough/agg_cursor_timeout.js
@@ -0,0 +1,126 @@
+/**
+ * Tests that an aggregation cursor is killed when it is timed out by the ClientCursorMonitor.
+ *
+ * This test was designed to reproduce SERVER-25585.
+ */
+(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 2 seconds with a monitor frequency of 1 second means an effective timeout period
+// of 1 to 2 seconds.
+const cursorTimeoutMs = 2000;
+const cursorMonitorFrequencySecs = 1;
+
+const options = {
+ setParameter: {
+ internalDocumentSourceCursorBatchSizeBytes: 1,
+ // We use the "cursorTimeoutMillis" server parameter to decrease how long it takes for a
+ // non-exhausted cursor to time out. We use the "clientCursorMonitorFrequencySecs"
+ // server parameter to make the ClientCursorMonitor that cleans up the timed out cursors
+ // run more often. The combination of these server parameters reduces the amount of time
+ // we need to wait within this test.
+ cursorTimeoutMillis: cursorTimeoutMs,
+ clientCursorMonitorFrequencySecs: cursorMonitorFrequencySecs,
+ }
+};
+const conn = MongoRunner.runMongod(options);
+assert.neq(null, conn, 'mongod was unable to start up with options: ' + tojson(options));
+
+const testDB = conn.getDB('test');
+
+// We use a batch size of 2 to ensure that the mongo shell does not exhaust the cursor on its
+// first batch.
+const batchSize = 2;
+const numMatches = 5;
+
+function assertCursorTimesOut(collName, pipeline) {
+ const res = assert.commandWorked(testDB.runCommand({
+ aggregate: collName,
+ pipeline: pipeline,
+ cursor: {
+ batchSize: batchSize,
+ },
+ }));
+
+ let serverStatus = assert.commandWorked(testDB.serverStatus());
+ const expectedNumTimedOutCursors = serverStatus.metrics.cursor.timedOut + 1;
+
+ const cursor = new DBCommandCursor(testDB, res, batchSize);
+
+ // Wait until the idle cursor background job has killed the aggregation cursor.
+ assert.soon(
+ function() {
+ serverStatus = assert.commandWorked(testDB.serverStatus());
+ return +serverStatus.metrics.cursor.timedOut === expectedNumTimedOutCursors;
+ },
+ function() {
+ return "aggregation cursor failed to time out: " + tojson(serverStatus.metrics.cursor);
+ });
+
+ assert.eq(0, serverStatus.metrics.cursor.open.total, tojson(serverStatus));
+
+ // We attempt to exhaust the aggregation cursor to verify that sending a getMore returns an
+ // error due to the cursor being killed.
+ let err = assert.throws(function() {
+ cursor.itcount();
+ });
+ assert.eq(ErrorCodes.CursorNotFound, err.code, tojson(err));
+}
+
+assert.commandWorked(testDB.source.insert({local: 1}));
+for (let i = 0; i < numMatches; ++i) {
+ assert.commandWorked(testDB.dest.insert({foreign: 1}));
+}
+
+// Test that a regular aggregation cursor is killed when the timeout is reached.
+assertCursorTimesOut('dest', []);
+
+// Test that an aggregation cursor with a $lookup stage is killed when the timeout is reached.
+assertCursorTimesOut('source', [
+ {
+ $lookup: {
+ from: 'dest',
+ localField: 'local',
+ foreignField: 'foreign',
+ as: 'matches',
+ }
+ },
+ {
+ $unwind: "$matches",
+ },
+ ]);
+
+// Test that an aggregation cursor with nested $lookup stages is killed when the timeout is
+// reached.
+assertCursorTimesOut('source', [
+ {
+ $lookup: {
+ from: 'dest',
+ let : {local1: "$local"},
+ pipeline: [
+ {$match: {$expr: {$eq: ["$foreign", "$$local1"]}}},
+ {
+ $lookup: {
+ from: 'source',
+ let : {foreign1: "$foreign"},
+ pipeline: [{$match: {$expr: {$eq: ["$local", "$$foreign1"]}}}],
+ as: 'matches2'
+ }
+ },
+ {
+ $unwind: "$matches2",
+ },
+ ],
+ as: 'matches1',
+ }
+ },
+ {
+ $unwind: "$matches1",
+ },
+ ]);
+
+MongoRunner.stopMongod(conn);
+})();