diff options
author | Cheahuychou Mao <cheahuychou.mao@mongodb.com> | 2018-07-03 14:23:27 -0400 |
---|---|---|
committer | Cheahuychou Mao <cheahuychou.mao@mongodb.com> | 2018-07-09 10:31:57 -0400 |
commit | ffbaee1a4dcb47a307986cb696807fe979f4a39d (patch) | |
tree | f42b803e6942b8bb7106597c3c80776c0aa2c0c6 | |
parent | ceae29c2fc64445dcf832d2f82c44c1920d91933 (diff) | |
download | mongo-ffbaee1a4dcb47a307986cb696807fe979f4a39d.tar.gz |
SERVER-33697 Provide sanity check on the number of cached sessions
-rw-r--r-- | jstests/noPassthrough/logical_session_cache_find_getmore.js | 28 | ||||
-rw-r--r-- | jstests/noPassthrough/refresh_sessions_command.js | 7 | ||||
-rw-r--r-- | jstests/noPassthrough/refresh_sessions_internal_command.js | 35 | ||||
-rw-r--r-- | jstests/noPassthrough/start_session_command.js | 4 | ||||
-rw-r--r-- | src/mongo/base/error_codes.err | 1 | ||||
-rw-r--r-- | src/mongo/db/commands/start_session_command.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/cursor_manager.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/initialize_operation_session_info.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/logical_session_cache.h | 4 | ||||
-rw-r--r-- | src/mongo/db/logical_session_cache_impl.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/logical_session_cache_impl.h | 6 | ||||
-rw-r--r-- | src/mongo/db/logical_session_cache_noop.h | 8 | ||||
-rw-r--r-- | src/mongo/db/logical_session_cache_test.cpp | 16 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_cursor_manager.cpp | 6 |
14 files changed, 124 insertions, 28 deletions
diff --git a/jstests/noPassthrough/logical_session_cache_find_getmore.js b/jstests/noPassthrough/logical_session_cache_find_getmore.js new file mode 100644 index 00000000000..a005b1c0ef5 --- /dev/null +++ b/jstests/noPassthrough/logical_session_cache_find_getmore.js @@ -0,0 +1,28 @@ +(function() { + 'use strict'; + + TestData.disableImplicitSessions = true; + + var conn = MongoRunner.runMongod({setParameter: {maxSessions: 2}}); + var testDB = conn.getDB("test"); + + assert.writeOK(testDB.foo.insert({data: 1})); + assert.writeOK(testDB.foo.insert({data: 2})); + + for (var i = 0; i < 2; i++) { + var session = conn.startSession(); + var db = session.getDatabase("test"); + var res = assert.commandWorked(db.runCommand({find: "foo", batchSize: 1}), + "unable to run find when the cache is not full"); + var cursorId = res.cursor.id; + assert.commandWorked(db.runCommand({getMore: cursorId, collection: "foo"}), + "unable to run getMore when the cache is not full"); + } + + var session3 = conn.startSession(); + var db = session3.getDatabase("test"); + assert.commandFailed(db.runCommand({find: "foo", batchSize: 1}), + "able to run find when the cache is full"); + + MongoRunner.stopMongod(conn); +})(); diff --git a/jstests/noPassthrough/refresh_sessions_command.js b/jstests/noPassthrough/refresh_sessions_command.js index ef8a6645c27..4386b61429e 100644 --- a/jstests/noPassthrough/refresh_sessions_command.js +++ b/jstests/noPassthrough/refresh_sessions_command.js @@ -33,7 +33,7 @@ // Turn on auth for further testing. MongoRunner.stopMongod(conn); - conn = MongoRunner.runMongod({auth: "", nojournal: ""}); + conn = MongoRunner.runMongod({auth: "", nojournal: "", setParameter: {maxSessions: 3}}); admin = conn.getDB("admin"); admin.createUser( @@ -78,6 +78,11 @@ result = admin.runCommand({refreshSessions: []}); assert.commandWorked(result, "unable to refresh empty set of lsids"); + // Test that we cannot run refreshSessions when the cache is full. + var lsid4 = {"id": UUID()}; + result = admin.runCommand({refreshSessions: [lsid4]}); + assert.commandFailed(result, "able to run refreshSessions when the cache is full"); + // Test that once we force a refresh, all of these sessions are in the sessions collection. admin.logout(); admin.auth("readSessionsCollection", "pwd"); diff --git a/jstests/noPassthrough/refresh_sessions_internal_command.js b/jstests/noPassthrough/refresh_sessions_internal_command.js index 39e515f6095..dc093bbc95f 100644 --- a/jstests/noPassthrough/refresh_sessions_internal_command.js +++ b/jstests/noPassthrough/refresh_sessions_internal_command.js @@ -33,6 +33,41 @@ admin.auth("internal", "pwd"); result = admin.runCommand({refreshSessionsInternal: []}); assert.commandWorked(result, "unable to run command with impersonate privileges"); + admin.logout(); + + MongoRunner.stopMongod(conn); + + TestData.disableImplicitSessions = true; + + // Start sessions and save the logical session record objects + var refresh = {refreshLogicalSessionCacheNow: 1}; + var startSession = {startSession: 1}; + + conn = MongoRunner.runMongod(); + admin = conn.getDB("admin"); + var config = conn.getDB("config"); + + for (var i = 0; i < 3; i++) { + result = admin.runCommand(startSession); + assert.commandWorked(result, "unable to start session"); + } + result = admin.runCommand(refresh); + assert.commandWorked(result, "failed to refresh"); + var sessions = config.system.sessions.find().toArray(); + assert.eq(sessions.length, 3, "refresh should have written three session records"); + + MongoRunner.stopMongod(conn); + + // Test that we can run refreshSessionsInternal with logical session record objects + conn = MongoRunner.runMongod({setParameter: {maxSessions: 2}}); + admin = conn.getDB("admin"); + + result = admin.runCommand({refreshSessionsInternal: sessions.slice(0, 2)}); + assert.commandWorked(result, + "unable to run refreshSessionsInternal when the cache is not full"); + + result = admin.runCommand({refreshSessionsInternal: sessions.slice(2)}); + assert.commandFailed(result, "able to run refreshSessionsInternal when the cache is full"); MongoRunner.stopMongod(conn); })(); diff --git a/jstests/noPassthrough/start_session_command.js b/jstests/noPassthrough/start_session_command.js index 55e227cd2a0..bb542e255fc 100644 --- a/jstests/noPassthrough/start_session_command.js +++ b/jstests/noPassthrough/start_session_command.js @@ -11,7 +11,7 @@ var result; const request = {startSession: 1}; - conn = MongoRunner.runMongod(); + conn = MongoRunner.runMongod({setParameter: {maxSessions: 2}}); admin = conn.getDB("admin"); // ensure that the cache is empty @@ -45,6 +45,8 @@ assert.eq( result.timeoutMinutes, 30, "failed test that our session record has the correct timeout"); + assert.commandFailed(admin.runCommand(request), + "failed test that we can't run startSession when the cache is full"); MongoRunner.stopMongod(conn); // diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err index 44fd6c15a8c..00ee3fa697c 100644 --- a/src/mongo/base/error_codes.err +++ b/src/mongo/base/error_codes.err @@ -261,6 +261,7 @@ error_code("InvalidResumeToken", 260); error_code("TooManyFilesOpen", 261); error_code("FailPointSetFailed", 262) error_code("OperationNotSupportedInTransaction", 263) +error_code("TooManyLogicalSessions", 264); # Error codes 4000-8999 are reserved. diff --git a/src/mongo/db/commands/start_session_command.cpp b/src/mongo/db/commands/start_session_command.cpp index 01ce7e0be6d..09652aacf42 100644 --- a/src/mongo/db/commands/start_session_command.cpp +++ b/src/mongo/db/commands/start_session_command.cpp @@ -79,7 +79,7 @@ public: auto lsCache = LogicalSessionCache::get(serviceContext); boost::optional<LogicalSessionRecord> record = makeLogicalSessionRecord(opCtx, lsCache->now()); - lsCache->startSession(opCtx, record.get()); + uassertStatusOK(lsCache->startSession(opCtx, record.get())); makeLogicalSessionToClient(record->getId()).serialize(&result); diff --git a/src/mongo/db/cursor_manager.cpp b/src/mongo/db/cursor_manager.cpp index cac5d43de1d..6cfc442ed67 100644 --- a/src/mongo/db/cursor_manager.cpp +++ b/src/mongo/db/cursor_manager.cpp @@ -593,7 +593,11 @@ StatusWith<ClientCursorPin> CursorManager::pinCursor(OperationContext* opCtx, // We use pinning of a cursor as a proxy for active, user-initiated use of a cursor. Therefor, // we pass down to the logical session cache and vivify the record (updating last use). if (cursor->getSessionId()) { - LogicalSessionCache::get(opCtx)->vivify(opCtx, cursor->getSessionId().get()); + auto vivifyCursorStatus = + LogicalSessionCache::get(opCtx)->vivify(opCtx, cursor->getSessionId().get()); + if (!vivifyCursorStatus.isOK()) { + return vivifyCursorStatus; + } } return ClientCursorPin(opCtx, cursor); diff --git a/src/mongo/db/initialize_operation_session_info.cpp b/src/mongo/db/initialize_operation_session_info.cpp index 137fba1196c..965dc0f1e53 100644 --- a/src/mongo/db/initialize_operation_session_info.cpp +++ b/src/mongo/db/initialize_operation_session_info.cpp @@ -73,7 +73,7 @@ boost::optional<OperationSessionInfoFromClient> initializeOperationSessionInfo( } opCtx->setLogicalSessionId(makeLogicalSessionId(osi.getSessionId().get(), opCtx)); - lsc->vivify(opCtx, opCtx->getLogicalSessionId().get()); + uassertStatusOK(lsc->vivify(opCtx, opCtx->getLogicalSessionId().get())); } else { uassert(ErrorCodes::InvalidOptions, "Transaction number requires a session ID to also be specified", diff --git a/src/mongo/db/logical_session_cache.h b/src/mongo/db/logical_session_cache.h index ece611339ba..2827fd8cd98 100644 --- a/src/mongo/db/logical_session_cache.h +++ b/src/mongo/db/logical_session_cache.h @@ -69,7 +69,7 @@ public: * should only be used when starting new sessions and should not be used to * insert records for existing sessions. */ - virtual void startSession(OperationContext* opCtx, LogicalSessionRecord record) = 0; + virtual Status startSession(OperationContext* opCtx, LogicalSessionRecord record) = 0; /** * Refresh the given sessions. Updates the timestamps of these records in @@ -84,7 +84,7 @@ public: * Vivifies the session in the cache. I.e. creates it if it isn't there, updates last use if it * is. */ - virtual void vivify(OperationContext* opCtx, const LogicalSessionId& lsid) = 0; + virtual Status vivify(OperationContext* opCtx, const LogicalSessionId& lsid) = 0; /** * enqueues LogicalSessionIds for removal during the next _refresh() diff --git a/src/mongo/db/logical_session_cache_impl.cpp b/src/mongo/db/logical_session_cache_impl.cpp index 8b46cbb4638..fa7e533cad5 100644 --- a/src/mongo/db/logical_session_cache_impl.cpp +++ b/src/mongo/db/logical_session_cache_impl.cpp @@ -52,6 +52,8 @@ MONGO_EXPORT_STARTUP_SERVER_PARAMETER( MONGO_EXPORT_STARTUP_SERVER_PARAMETER(disableLogicalSessionCacheRefresh, bool, false); +MONGO_EXPORT_STARTUP_SERVER_PARAMETER(maxSessions, int, 1'000'000); + constexpr Minutes LogicalSessionCacheImpl::kLogicalSessionDefaultRefresh; LogicalSessionCacheImpl::LogicalSessionCacheImpl( @@ -99,11 +101,11 @@ Status LogicalSessionCacheImpl::promote(LogicalSessionId lsid) { return Status::OK(); } -void LogicalSessionCacheImpl::startSession(OperationContext* opCtx, LogicalSessionRecord record) { +Status LogicalSessionCacheImpl::startSession(OperationContext* opCtx, LogicalSessionRecord record) { // Add the new record to our local cache. We will insert it into the sessions collection // the next time _refresh is called. If there is already a record in the cache for this // session, we'll just write over it with our newer, more recent one. - _addToCache(record); + return _addToCache(record); } Status LogicalSessionCacheImpl::refreshSessions(OperationContext* opCtx, @@ -113,7 +115,10 @@ Status LogicalSessionCacheImpl::refreshSessions(OperationContext* opCtx, for (const auto& lsid : sessions) { if (!promote(lsid).isOK()) { // This is a new record, insert it. - _addToCache(makeLogicalSessionRecord(opCtx, lsid, now())); + auto addToCacheStatus = _addToCache(makeLogicalSessionRecord(opCtx, lsid, now())); + if (!addToCacheStatus.isOK()) { + return addToCacheStatus; + } } } @@ -127,17 +132,21 @@ Status LogicalSessionCacheImpl::refreshSessions(OperationContext* opCtx, for (const auto& record : records) { if (!promote(record.getId()).isOK()) { // This is a new record, insert it. - _addToCache(record); + auto addToCacheStatus = _addToCache(record); + if (!addToCacheStatus.isOK()) { + return addToCacheStatus; + } } } return Status::OK(); } -void LogicalSessionCacheImpl::vivify(OperationContext* opCtx, const LogicalSessionId& lsid) { +Status LogicalSessionCacheImpl::vivify(OperationContext* opCtx, const LogicalSessionId& lsid) { if (!promote(lsid).isOK()) { - startSession(opCtx, makeLogicalSessionRecord(opCtx, lsid, now())); + return startSession(opCtx, makeLogicalSessionRecord(opCtx, lsid, now())); } + return Status::OK(); } Status LogicalSessionCacheImpl::refreshNow(Client* client) { @@ -401,9 +410,13 @@ LogicalSessionCacheStats LogicalSessionCacheImpl::getStats() { return _stats; } -void LogicalSessionCacheImpl::_addToCache(LogicalSessionRecord record) { +Status LogicalSessionCacheImpl::_addToCache(LogicalSessionRecord record) { stdx::lock_guard<stdx::mutex> lk(_cacheMutex); + if (_activeSessions.size() >= static_cast<size_t>(maxSessions)) { + return {ErrorCodes::TooManyLogicalSessions, "cannot add session into the cache"}; + } _activeSessions.insert(std::make_pair(record.getId(), record)); + return Status::OK(); } std::vector<LogicalSessionId> LogicalSessionCacheImpl::listIds() const { diff --git a/src/mongo/db/logical_session_cache_impl.h b/src/mongo/db/logical_session_cache_impl.h index 399753e4cba..1703ab05cad 100644 --- a/src/mongo/db/logical_session_cache_impl.h +++ b/src/mongo/db/logical_session_cache_impl.h @@ -99,14 +99,14 @@ public: Status promote(LogicalSessionId lsid) override; - void startSession(OperationContext* opCtx, LogicalSessionRecord record) override; + Status startSession(OperationContext* opCtx, LogicalSessionRecord record) override; Status refreshSessions(OperationContext* opCtx, const RefreshSessionsCmdFromClient& cmd) override; Status refreshSessions(OperationContext* opCtx, const RefreshSessionsCmdFromClusterMember& cmd) override; - void vivify(OperationContext* opCtx, const LogicalSessionId& lsid) override; + Status vivify(OperationContext* opCtx, const LogicalSessionId& lsid) override; Status refreshNow(Client* client) override; @@ -146,7 +146,7 @@ private: /** * Takes the lock and inserts the given record into the cache. */ - void _addToCache(LogicalSessionRecord record); + Status _addToCache(LogicalSessionRecord record); const Minutes _refreshInterval; const Minutes _sessionTimeout; diff --git a/src/mongo/db/logical_session_cache_noop.h b/src/mongo/db/logical_session_cache_noop.h index acb6531a388..7d0c5d1051e 100644 --- a/src/mongo/db/logical_session_cache_noop.h +++ b/src/mongo/db/logical_session_cache_noop.h @@ -45,7 +45,9 @@ public: return Status::OK(); } - void startSession(OperationContext* opCtx, LogicalSessionRecord record) override {} + Status startSession(OperationContext* opCtx, LogicalSessionRecord record) override { + return Status::OK(); + } Status refreshSessions(OperationContext* opCtx, const RefreshSessionsCmdFromClient& cmd) override { @@ -56,7 +58,9 @@ public: return Status::OK(); } - void vivify(OperationContext* opCtx, const LogicalSessionId& lsid) override {} + Status vivify(OperationContext* opCtx, const LogicalSessionId& lsid) override { + return Status::OK(); + } Status refreshNow(Client* client) override { return Status::OK(); diff --git a/src/mongo/db/logical_session_cache_test.cpp b/src/mongo/db/logical_session_cache_test.cpp index 37256c26670..b8ec36827fe 100644 --- a/src/mongo/db/logical_session_cache_test.cpp +++ b/src/mongo/db/logical_session_cache_test.cpp @@ -142,7 +142,7 @@ TEST_F(LogicalSessionCacheTest, PromoteUpdatesLastUse) { auto start = service()->now(); // Insert the record into the sessions collection with 'start' - cache()->startSession(opCtx(), makeLogicalSessionRecord(lsid, start)); + ASSERT_OK(cache()->startSession(opCtx(), makeLogicalSessionRecord(lsid, start))); // Fast forward time and promote service()->fastForward(Milliseconds(500)); @@ -182,7 +182,7 @@ TEST_F(LogicalSessionCacheTest, StartSession) { auto lsid = record.getId(); // Test starting a new session - cache()->startSession(opCtx(), record); + ASSERT_OK(cache()->startSession(opCtx(), record)); // Record will not be in the collection yet; refresh must happen first. ASSERT(!sessions()->has(lsid)); @@ -193,27 +193,27 @@ TEST_F(LogicalSessionCacheTest, StartSession) { ASSERT(sessions()->has(lsid)); // Try to start the same session again, should succeed. - cache()->startSession(opCtx(), record); + ASSERT_OK(cache()->startSession(opCtx(), record)); // Try to start a session that is already in the sessions collection but // is not in our local cache, should succeed. auto record2 = makeLogicalSessionRecord(makeLogicalSessionIdForTest(), service()->now()); sessions()->add(record2); - cache()->startSession(opCtx(), record2); + ASSERT_OK(cache()->startSession(opCtx(), record2)); // Try to start a session that has expired from our cache, and is no // longer in the sessions collection, should succeed service()->fastForward(Milliseconds(kSessionTimeout.count() + 5)); sessions()->remove(lsid); ASSERT(!sessions()->has(lsid)); - cache()->startSession(opCtx(), record); + ASSERT_OK(cache()->startSession(opCtx(), record)); } // Test that session cache properly expires lsids after 30 minutes of no use TEST_F(LogicalSessionCacheTest, BasicSessionExpiration) { // Insert a lsid auto record = makeLogicalSessionRecordForTest(); - cache()->startSession(opCtx(), record); + ASSERT_OK(cache()->startSession(opCtx(), record)); auto res = cache()->promote(record.getId()); ASSERT(res.isOK()); @@ -232,7 +232,7 @@ TEST_F(LogicalSessionCacheTest, ManySignedLsidsInCacheRefresh) { int count = 10000; for (int i = 0; i < count; i++) { auto record = makeLogicalSessionRecordForTest(); - cache()->startSession(opCtx(), record); + ASSERT_OK(cache()->startSession(opCtx(), record)); } // Check that all signedLsids refresh @@ -344,7 +344,7 @@ TEST_F(LogicalSessionCacheTest, RefreshMatrixSessionState) { service()->add(lsid); } if (active) { - cache()->startSession(opCtx(), lsRecord); + ASSERT_OK(cache()->startSession(opCtx(), lsRecord)); } if (!expired) { sessions()->add(lsRecord); diff --git a/src/mongo/s/query/cluster_cursor_manager.cpp b/src/mongo/s/query/cluster_cursor_manager.cpp index f42465e845d..cbfebdb5d80 100644 --- a/src/mongo/s/query/cluster_cursor_manager.cpp +++ b/src/mongo/s/query/cluster_cursor_manager.cpp @@ -319,7 +319,11 @@ StatusWith<ClusterCursorManager::PinnedCursor> ClusterCursorManager::checkOutCur // We use pinning of a cursor as a proxy for active, user-initiated use of a cursor. Therefore, // we pass down to the logical session cache and vivify the record (updating last use). if (cursor->getLsid()) { - LogicalSessionCache::get(opCtx)->vivify(opCtx, cursor->getLsid().get()); + auto vivifyCursorStatus = + LogicalSessionCache::get(opCtx)->vivify(opCtx, cursor->getLsid().get()); + if (!vivifyCursorStatus.isOK()) { + return vivifyCursorStatus; + } } cursor->reattachToOperationContext(opCtx); |