diff options
-rw-r--r-- | jstests/sharding/cursor_timeout.js | 88 | ||||
-rw-r--r-- | src/mongo/db/cursor_manager.cpp | 2 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_cursor_manager.cpp | 2 |
3 files changed, 73 insertions, 19 deletions
diff --git a/jstests/sharding/cursor_timeout.js b/jstests/sharding/cursor_timeout.js index 8eb368c1aa4..85e45d41bde 100644 --- a/jstests/sharding/cursor_timeout.js +++ b/jstests/sharding/cursor_timeout.js @@ -1,17 +1,26 @@ // 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. +// 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. // -// After a period of inactivity, the test asserts that cursors #1 and #2 are still alive, and that -// #3 and #4 have been killed. // @tags: [requires_sharding] (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 @@ -49,11 +58,13 @@ const st = new ShardingTest({ }); const adminDB = st.admin; -const routerColl = st.s.getDB('test').user; +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); @@ -77,32 +88,45 @@ 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 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. +// 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 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. +// 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 == 2; + return shardColl.getDB().serverStatus().metrics.cursor.open.total == 4; }, "cursor failed to time out"); assert.throws(function() { @@ -112,7 +136,37 @@ assert.throws(function() { shardCursorWithTimeout.itcount(); }); -// +1 because we already advanced once +// 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); diff --git a/src/mongo/db/cursor_manager.cpp b/src/mongo/db/cursor_manager.cpp index 790f91472b4..bd2800964fb 100644 --- a/src/mongo/db/cursor_manager.cpp +++ b/src/mongo/db/cursor_manager.cpp @@ -120,7 +120,7 @@ CursorManager::~CursorManager() { } bool CursorManager::cursorShouldTimeout_inlock(const ClientCursor* cursor, Date_t now) { - if (cursor->isNoTimeout() || cursor->_operationUsingCursor) { + if (cursor->isNoTimeout() || cursor->_operationUsingCursor || cursor->getSessionId()) { return false; } return (now - cursor->_lastUseDate) >= Milliseconds(getCursorTimeoutMillis()); diff --git a/src/mongo/s/query/cluster_cursor_manager.cpp b/src/mongo/s/query/cluster_cursor_manager.cpp index 73ae523f978..64fcb8e4d87 100644 --- a/src/mongo/s/query/cluster_cursor_manager.cpp +++ b/src/mongo/s/query/cluster_cursor_manager.cpp @@ -411,7 +411,7 @@ std::size_t ClusterCursorManager::killMortalCursorsInactiveSince(OperationContex stdx::unique_lock<Latch> lk(_mutex); auto pred = [cutoff](CursorId cursorId, const CursorEntry& entry) -> bool { - bool res = entry.getLifetimeType() == CursorLifetime::Mortal && + bool res = entry.getLifetimeType() == CursorLifetime::Mortal && !entry.getLsid() && !entry.getOperationUsingCursor() && entry.getLastActive() <= cutoff; if (res) { |