diff options
24 files changed, 824 insertions, 380 deletions
diff --git a/jstests/noPassthrough/verify_session_cache_updates.js b/jstests/noPassthrough/verify_session_cache_updates.js new file mode 100644 index 00000000000..e9912144e6f --- /dev/null +++ b/jstests/noPassthrough/verify_session_cache_updates.js @@ -0,0 +1,68 @@ +(function() { + 'use strict'; + + function runTest(conn) { + for (var i = 0; i < 10; ++i) { + conn.getDB("test").test.save({a: i}); + } + + function verify(conn, nRecords) { + conn.getDB("admin").runCommand({refreshLogicalSessionCacheNow: 1}); + assert.eq(nRecords, conn.getDB("admin").system.sessions.find({}).count()); + } + + function getLastUse(conn) { + conn.getDB("admin").runCommand({refreshLogicalSessionCacheNow: 1}); + return conn.getDB("admin").system.sessions.findOne({}).lastUse; + } + + // initially we have no sessions + verify(conn, 0); + + // Calling startSession in the shell doesn't initiate the session + var session = conn.startSession(); + verify(conn, 0); + + // running a non-session updating command doesn't touch + session.getDatabase("admin").runCommand("getLastError"); + verify(conn, 0); + + // running a session updating command does touch + session.getDatabase("admin").runCommand({serverStatus: 1}); + verify(conn, 1); + + // running a session updating command updates last use + { + var lastUse = getLastUse(conn); + sleep(200); + session.getDatabase("admin").runCommand({serverStatus: 1}); + verify(conn, 1); + assert.gt(getLastUse(conn), lastUse); + } + + // verify that reading from a cursor updates last use + { + var cursor = session.getDatabase("test").test.find({}).batchSize(1); + cursor.next(); + var lastUse = getLastUse(conn); + sleep(200); + verify(conn, 1); + cursor.next(); + assert.gt(getLastUse(conn), lastUse); + } + + session.endSession(); + } + + { + var mongod = MongoRunner.runMongod({nojournal: ""}); + runTest(mongod); + MongoRunner.stopMongod(mongod); + } + + { + var st = new ShardingTest({shards: 1, mongos: 1, config: 1}); + runTest(st.s0); + st.stop(); + } +})(); diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 85ac0195bb1..b74534df190 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -633,6 +633,7 @@ env.Library( ], LIBDEPS=[ "$BUILD_DIR/mongo/base", + "$BUILD_DIR/mongo/db/logical_session_cache", "$BUILD_DIR/mongo/db/logical_session_id", "$BUILD_DIR/mongo/util/background_job", "query/query", @@ -895,23 +896,6 @@ env.Library( ], ) -env.CppUnitTest( - target='logical_session_id_test', - source=[ - 'logical_session_id_test.cpp', - ], - LIBDEPS=[ - '$BUILD_DIR/mongo/base', - '$BUILD_DIR/mongo/db/service_context_noop_init', - '$BUILD_DIR/mongo/transport/transport_layer_mock', - '$BUILD_DIR/mongo/db/auth/authcore', - '$BUILD_DIR/mongo/db/auth/authmocks', - '$BUILD_DIR/mongo/db/auth/authorization_session_for_test', - 'logical_session_id', - 'logical_session_id_helpers', - ], -) - env.Library( target='service_liason', source=[ @@ -929,6 +913,27 @@ env.Library( envWithAsio = env.Clone() envWithAsio.InjectThirdPartyIncludePaths(libraries=['asio']) +envWithAsio.CppUnitTest( + target='logical_session_id_test', + source=[ + 'logical_session_id_test.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/db/auth/authcore', + '$BUILD_DIR/mongo/db/auth/authmocks', + '$BUILD_DIR/mongo/db/auth/authorization_session_for_test', + '$BUILD_DIR/mongo/db/service_context_noop_init', + '$BUILD_DIR/mongo/transport/transport_layer_mock', + 'logical_session_cache', + 'logical_session_cache_impl', + 'logical_session_id', + 'logical_session_id_helpers', + 'service_liason_mock', + 'sessions_collection_mock', + ], +) + envWithAsio.Library( target='service_liason_mock', source=[ @@ -1043,10 +1048,23 @@ env.Library( target='logical_session_cache', source=[ 'logical_session_cache.cpp', + ], + LIBDEPS=[ + 'logical_session_id', + 'service_context', + ], +) + +env.Library( + target='logical_session_cache_impl', + source=[ + 'initialize_operation_session_info.cpp', + 'logical_session_cache_impl.cpp', 'logical_session_server_status_section.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/db/commands/server_status', + 'logical_session_cache', 'logical_session_id', 'logical_session_id_helpers', 'sessions_collection', @@ -1069,6 +1087,7 @@ envWithAsio.CppUnitTest( 'logical_session_id', 'logical_session_id_helpers', 'logical_session_cache', + 'logical_session_cache_impl', 'service_liason_mock', 'sessions_collection_mock', ], @@ -1081,6 +1100,7 @@ envWithAsio.Library( ], LIBDEPS=[ 'logical_session_cache', + 'logical_session_cache_impl', 'service_liason_mongod', 'sessions_collection_rs', 'sessions_collection_sharded', @@ -1095,6 +1115,7 @@ envWithAsio.Library( ], LIBDEPS=[ 'logical_session_cache', + 'logical_session_cache_impl', 'service_liason_mongos', 'sessions_collection_sharded', ], diff --git a/src/mongo/db/commands/get_last_error.cpp b/src/mongo/db/commands/get_last_error.cpp index bd71f6c5825..bcf4387d7c9 100644 --- a/src/mongo/db/commands/get_last_error.cpp +++ b/src/mongo/db/commands/get_last_error.cpp @@ -96,6 +96,11 @@ public: virtual void addRequiredPrivileges(const std::string& dbname, const BSONObj& cmdObj, std::vector<Privilege>* out) {} // No auth required + + bool requiresAuth() const override { + return false; + } + virtual void help(stringstream& help) const { help << "return error status of the last operation on this connection\n" << "options:\n" @@ -311,6 +316,9 @@ public: virtual bool slaveOk() const { return true; } + bool requiresAuth() const override { + return false; + } virtual void addRequiredPrivileges(const std::string& dbname, const BSONObj& cmdObj, std::vector<Privilege>* out) {} // No auth required diff --git a/src/mongo/db/cursor_manager.cpp b/src/mongo/db/cursor_manager.cpp index d2013c14f0f..e7192105867 100644 --- a/src/mongo/db/cursor_manager.cpp +++ b/src/mongo/db/cursor_manager.cpp @@ -41,6 +41,7 @@ #include "mongo/db/client.h" #include "mongo/db/db_raii.h" #include "mongo/db/kill_sessions_common.h" +#include "mongo/db/logical_session_cache.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/query/plan_executor.h" @@ -512,6 +513,13 @@ StatusWith<ClientCursorPin> CursorManager::pinCursor(OperationContext* opCtx, Cu return error; } cursor->_isPinned = true; + + // 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()); + } + return ClientCursorPin(opCtx, cursor); } diff --git a/src/mongo/db/initialize_operation_session_info.cpp b/src/mongo/db/initialize_operation_session_info.cpp new file mode 100644 index 00000000000..e448b23b145 --- /dev/null +++ b/src/mongo/db/initialize_operation_session_info.cpp @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/initialize_operation_session_info.h" + +#include "mongo/db/logical_session_cache.h" +#include "mongo/db/logical_session_id_helpers.h" +#include "mongo/db/operation_context.h" + +namespace mongo { + +void initializeOperationSessionInfo(OperationContext* opCtx, + const BSONObj& requestBody, + bool requiresAuth) { + if (!requiresAuth) { + return; + } + + auto osi = OperationSessionInfoFromClient::parse("OperationSessionInfo"_sd, requestBody); + + if (osi.getSessionId()) { + opCtx->setLogicalSessionId(makeLogicalSessionId(osi.getSessionId().get(), opCtx)); + + LogicalSessionCache* lsc = LogicalSessionCache::get(opCtx->getServiceContext()); + lsc->vivify(opCtx, opCtx->getLogicalSessionId().get()); + } + + if (osi.getTxnNumber()) { + uassert(ErrorCodes::IllegalOperation, + "Transaction number requires a sessionId to be specified", + opCtx->getLogicalSessionId()); + uassert(ErrorCodes::BadValue, + "Transaction number cannot be negative", + *osi.getTxnNumber() >= 0); + + opCtx->setTxnNumber(*osi.getTxnNumber()); + } +} + +} // namespace mongo diff --git a/src/mongo/db/initialize_operation_session_info.h b/src/mongo/db/initialize_operation_session_info.h new file mode 100644 index 00000000000..fddc162bfb6 --- /dev/null +++ b/src/mongo/db/initialize_operation_session_info.h @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/db/logical_session_id.h" + +namespace mongo { + +/** + * Parses the session information from the body of a request and installs it on the current + * operation context. Must only be called once per operation and should be done right in the + * beginning. + * + * Throws if the sessionId/txnNumber combination is not properly formatted. + * + * requiresAuth specifies if the command we're initializing operationSessionInfo for requires + * authorization or not. This can be determined by invoking ->requiresAuth() on the parsed command. + */ +void initializeOperationSessionInfo(OperationContext* opCtx, + const BSONObj& requestBody, + bool requiresAuth); + +} // namespace mongo diff --git a/src/mongo/db/logical_session_cache.cpp b/src/mongo/db/logical_session_cache.cpp index c026c2281dd..58760890e1f 100644 --- a/src/mongo/db/logical_session_cache.cpp +++ b/src/mongo/db/logical_session_cache.cpp @@ -26,21 +26,12 @@ * it in the license file. */ -#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kControl - #include "mongo/platform/basic.h" #include "mongo/db/logical_session_cache.h" -#include "mongo/db/logical_session_id.h" -#include "mongo/db/logical_session_id_helpers.h" #include "mongo/db/operation_context.h" -#include "mongo/db/server_parameters.h" #include "mongo/db/service_context.h" -#include "mongo/platform/atomic_word.h" -#include "mongo/util/duration.h" -#include "mongo/util/log.h" -#include "mongo/util/periodic_runner.h" namespace mongo { @@ -51,16 +42,7 @@ const auto getLogicalSessionCache = const auto getLogicalSessionCacheIsRegistered = ServiceContext::declareDecoration<AtomicBool>(); } // namespace -MONGO_EXPORT_STARTUP_SERVER_PARAMETER(logicalSessionRecordCacheSize, - int, - LogicalSessionCache::kLogicalSessionCacheDefaultCapacity); - -MONGO_EXPORT_STARTUP_SERVER_PARAMETER(logicalSessionRefreshMinutes, - int, - LogicalSessionCache::kLogicalSessionDefaultRefresh.count()); - -constexpr int LogicalSessionCache::kLogicalSessionCacheDefaultCapacity; -constexpr Minutes LogicalSessionCache::kLogicalSessionDefaultRefresh; +LogicalSessionCache::~LogicalSessionCache() = default; LogicalSessionCache* LogicalSessionCache::get(ServiceContext* service) { if (getLogicalSessionCacheIsRegistered(service).load()) { @@ -80,199 +62,4 @@ void LogicalSessionCache::set(ServiceContext* service, getLogicalSessionCacheIsRegistered(service).store(true); } -LogicalSessionCache::LogicalSessionCache(std::unique_ptr<ServiceLiason> service, - std::unique_ptr<SessionsCollection> collection, - Options options) - : _refreshInterval(options.refreshInterval), - _sessionTimeout(options.sessionTimeout), - _service(std::move(service)), - _sessionsColl(std::move(collection)), - _cache(options.capacity) { - PeriodicRunner::PeriodicJob job{[this](Client* client) { _periodicRefresh(client); }, - duration_cast<Milliseconds>(_refreshInterval)}; - _service->scheduleJob(std::move(job)); -} - -LogicalSessionCache::~LogicalSessionCache() { - try { - _service->join(); - } catch (...) { - // If we failed to join we might still be running a background thread, - // log but swallow the error since there is no good way to recover. - severe() << "Failed to join background service thread"; - } -} - -Status LogicalSessionCache::promote(LogicalSessionId lsid) { - stdx::unique_lock<stdx::mutex> lk(_cacheMutex); - auto it = _cache.find(lsid); - if (it == _cache.end()) { - return {ErrorCodes::NoSuchSession, "no matching session record found in the cache"}; - } - - // Update the last use time before returning. - it->second.setLastUse(now()); - return Status::OK(); -} - -Status LogicalSessionCache::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 Status::OK(); -} - -Status LogicalSessionCache::refreshSessions(OperationContext* opCtx, - const RefreshSessionsCmdFromClient& cmd) { - // Update the timestamps of all these records in our cache. - auto sessions = makeLogicalSessionIds(cmd.getRefreshSessions(), opCtx); - for (auto& lsid : sessions) { - if (!promote(lsid).isOK()) { - // This is a new record, insert it. - _addToCache(makeLogicalSessionRecord(opCtx, lsid, now())); - } - } - - return Status::OK(); -} - -Status LogicalSessionCache::refreshSessions(OperationContext* opCtx, - const RefreshSessionsCmdFromClusterMember& cmd) { - LogicalSessionRecordSet toRefresh{}; - - // Update the timestamps of all these records in our cache. - auto records = cmd.getRefreshSessionsInternal(); - for (auto& record : records) { - if (!promote(record.getId()).isOK()) { - // This is a new record, insert it. - _addToCache(record); - } - toRefresh.insert(record); - } - - // Write to the sessions collection now. - return _sessionsColl->refreshSessions(opCtx, toRefresh, now()); -} - -Status LogicalSessionCache::refreshNow(Client* client) { - return _refresh(client); -} - -Date_t LogicalSessionCache::now() { - return _service->now(); -} - -size_t LogicalSessionCache::size() { - stdx::lock_guard<stdx::mutex> lock(_cacheMutex); - return _cache.size(); -} - -void LogicalSessionCache::_periodicRefresh(Client* client) { - auto res = _refresh(client); - if (!res.isOK()) { - log() << "Failed to refresh session cache: " << res; - } - - return; -} - -Status LogicalSessionCache::_refresh(Client* client) { - LogicalSessionRecordSet activeSessions; - LogicalSessionRecordSet deadSessions; - - auto time = now(); - - // We should avoid situations where we have records in the cache - // that have been expired from the sessions collection. If they haven't been - // used in _sessionTimeout, we should just remove them. - - // Assemble a list of active session records in our cache - std::vector<decltype(_cache)::ListEntry> cacheCopy; - { - stdx::unique_lock<stdx::mutex> lk(_cacheMutex); - cacheCopy.assign(_cache.begin(), _cache.end()); - } - - for (auto& it : cacheCopy) { - auto record = it.second; - if (!_isDead(record, time)) { - activeSessions.insert(record); - } else { - deadSessions.insert(record); - } - } - - // Append any active sessions from the service. We should always have - // cache entries for active sessions. If we don't, then it is a sign that - // the cache needs to be larger, because active session records are being - // evicted. - - // Promote our cached entries for all active service sessions to be recently- - // used, and update their lastUse dates so we don't lose them to eviction. We - // do not need to do this with records from our own cache, which are being used - // regularly. Sessions for long-running queries, however, must be kept alive - // by us here. - auto serviceSessions = _service->getActiveSessions(); - { - stdx::unique_lock<stdx::mutex> lk(_cacheMutex); - for (auto lsid : serviceSessions) { - auto it = _cache.promote(lsid); - if (it != _cache.end()) { - // If we have not found our record, it may have been removed - // by another thread. - it->second.setLastUse(time); - activeSessions.insert(it->second); - } - - // TODO SERVER-29709: Rethink how active sessions interact with refreshes, - // and potentially move this block above the block where we separate - // dead sessions from live sessions, above. - activeSessions.insert(makeLogicalSessionRecord(lsid, time)); - } - } - - // Query into the sessions collection to do the refresh. If any sessions have - // failed to refresh, it means their authoritative records were removed, and - // we should remove such records from our cache as well. - { - boost::optional<ServiceContext::UniqueOperationContext> uniqueCtx; - auto* const opCtx = [&client, &uniqueCtx] { - if (client->getOperationContext()) { - return client->getOperationContext(); - } - - uniqueCtx.emplace(client->makeOperationContext()); - return uniqueCtx->get(); - }(); - - auto res = _sessionsColl->refreshSessions(opCtx, std::move(activeSessions), time); - if (!res.isOK()) { - // TODO SERVER-29709: handle network errors here. - return res; - } - } - - // Prune any dead records out of the cache. Dead records are ones that failed to - // refresh, or ones that have expired locally. We don't make an effort to check - // if the locally-expired records still have live authoritative records in the - // sessions collection. We also don't attempt to resurrect our expired records. - // However, we *do* keep records alive if they are active on the service. - { - // TODO SERVER-29709: handle expiration separately from failure to refresh. - } - - return Status::OK(); -} - -bool LogicalSessionCache::_isDead(const LogicalSessionRecord& record, Date_t now) const { - return record.getLastUse() + _sessionTimeout < now; -} - -boost::optional<LogicalSessionRecord> LogicalSessionCache::_addToCache( - LogicalSessionRecord record) { - stdx::unique_lock<stdx::mutex> lk(_cacheMutex); - return _cache.add(record.getId(), std::move(record)); -} - } // namespace mongo diff --git a/src/mongo/db/logical_session_cache.h b/src/mongo/db/logical_session_cache.h index 16fdfb78fb0..dfc1772013a 100644 --- a/src/mongo/db/logical_session_cache.h +++ b/src/mongo/db/logical_session_cache.h @@ -28,15 +28,9 @@ #pragma once -#include "mongo/base/status_with.h" +#include "mongo/base/status.h" #include "mongo/db/logical_session_id.h" #include "mongo/db/refresh_sessions_gen.h" -#include "mongo/db/service_liason.h" -#include "mongo/db/sessions_collection.h" -#include "mongo/db/time_proof_service.h" -#include "mongo/platform/atomic_word.h" -#include "mongo/stdx/thread.h" -#include "mongo/util/lru_cache.h" namespace mongo { @@ -44,14 +38,8 @@ class Client; class OperationContext; class ServiceContext; -extern int logicalSessionRecordCacheSize; -extern int logicalSessionRefreshMinutes; - /** - * A thread-safe cache structure for logical session records. - * - * The cache takes ownership of the passed-in ServiceLiason and - * SessionsCollection helper types. + * The interface for the logical session cache */ class LogicalSessionCache { public: @@ -62,61 +50,14 @@ public: static LogicalSessionCache* get(OperationContext* opCtx); static void set(ServiceContext* service, std::unique_ptr<LogicalSessionCache> sessionCache); - static constexpr int kLogicalSessionCacheDefaultCapacity = 10000; - static constexpr Minutes kLogicalSessionDefaultRefresh = Minutes(5); - - /** - * An Options type to support the LogicalSessionCache. - */ - struct Options { - Options(){}; - - /** - * The number of session records to keep in the cache. - * - * May be set with --setParameter logicalSessionRecordCacheSize=X. - */ - int capacity = logicalSessionRecordCacheSize; - - /** - * A timeout value to use for sessions in the cache, in minutes. - * - * By default, this is set to 30 minutes. - * - * May be set with --setParameter localLogicalSessionTimeoutMinutes=X. - */ - Minutes sessionTimeout = Minutes(localLogicalSessionTimeoutMinutes); - - /** - * The interval over which the cache will refresh session records. - * - * By default, this is set to every 5 minutes. If the caller is - * setting the sessionTimeout by hand, it is suggested that they - * consider also setting the refresh interval accordingly. - * - * May be set with --setParameter logicalSessionRefreshMinutes=X. - */ - Minutes refreshInterval = Minutes(logicalSessionRefreshMinutes); - }; - - /** - * Construct a new session cache. - */ - explicit LogicalSessionCache(std::unique_ptr<ServiceLiason> service, - std::unique_ptr<SessionsCollection> collection, - Options options = Options{}); - - LogicalSessionCache(const LogicalSessionCache&) = delete; - LogicalSessionCache operator=(const LogicalSessionCache&) = delete; - - ~LogicalSessionCache(); + virtual ~LogicalSessionCache() = 0; /** * If the cache contains a record for this LogicalSessionId, promotes that lsid * to be the most recently used and updates its lastUse date to be the current * time. Returns an error if the session was not found. */ - Status promote(LogicalSessionId lsid); + virtual Status promote(LogicalSessionId lsid) = 0; /** * Inserts a new authoritative session record into the cache. This method will @@ -124,63 +65,44 @@ public: * should only be used when starting new sessions and should not be used to * insert records for existing sessions. */ - Status startSession(OperationContext* opCtx, LogicalSessionRecord record); + virtual Status startSession(OperationContext* opCtx, LogicalSessionRecord record) = 0; /** * Refresh the given sessions. Updates the timestamps of these records in * the local cache. */ - Status refreshSessions(OperationContext* opCtx, const RefreshSessionsCmdFromClient& cmd); - Status refreshSessions(OperationContext* opCtx, const RefreshSessionsCmdFromClusterMember& cmd); + virtual Status refreshSessions(OperationContext* opCtx, + const RefreshSessionsCmdFromClient& cmd) = 0; + virtual Status refreshSessions(OperationContext* opCtx, + const RefreshSessionsCmdFromClusterMember& cmd) = 0; + + /** + * 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; /** * Removes all local records in this cache. Does not remove the corresponding * authoritative session records from the sessions collection. */ - void clear(); + virtual void clear() = 0; /** * Refreshes the cache synchronously. This flushes all pending refreshes and * inserts to the sessions collection. */ - Status refreshNow(Client* client); + virtual Status refreshNow(Client* client) = 0; /** * Returns the current time. */ - Date_t now(); + virtual Date_t now() = 0; /** * Returns the number of session records currently in the cache. */ - size_t size(); - -private: - /** - * Internal methods to handle scheduling and perform refreshes for active - * session records contained within the cache. - */ - void _periodicRefresh(Client* client); - Status _refresh(Client* client); - - /** - * Returns true if a record has passed its given expiration. - */ - bool _isDead(const LogicalSessionRecord& record, Date_t now) const; - - /** - * Takes the lock and inserts the given record into the cache. - */ - boost::optional<LogicalSessionRecord> _addToCache(LogicalSessionRecord record); - - const Minutes _refreshInterval; - const Minutes _sessionTimeout; - - std::unique_ptr<ServiceLiason> _service; - std::unique_ptr<SessionsCollection> _sessionsColl; - - stdx::mutex _cacheMutex; - LRUCache<LogicalSessionId, LogicalSessionRecord, LogicalSessionIdHash> _cache; + virtual size_t size() = 0; }; } // namespace mongo diff --git a/src/mongo/db/logical_session_cache_factory_mongod.cpp b/src/mongo/db/logical_session_cache_factory_mongod.cpp index ee77bae1aa3..7f958e547a7 100644 --- a/src/mongo/db/logical_session_cache_factory_mongod.cpp +++ b/src/mongo/db/logical_session_cache_factory_mongod.cpp @@ -32,6 +32,7 @@ #include "mongo/db/logical_session_cache_factory_mongod.h" +#include "mongo/db/logical_session_cache_impl.h" #include "mongo/db/service_liason_mongod.h" #include "mongo/db/sessions_collection_rs.h" #include "mongo/db/sessions_collection_sharded.h" @@ -62,8 +63,8 @@ std::unique_ptr<LogicalSessionCache> makeLogicalSessionCacheD(LogicalSessionCach // Set up the logical session cache auto sessionsColl = makeSessionsCollection(state); - return stdx::make_unique<LogicalSessionCache>( - std::move(liason), std::move(sessionsColl), LogicalSessionCache::Options{}); + return stdx::make_unique<LogicalSessionCacheImpl>( + std::move(liason), std::move(sessionsColl), LogicalSessionCacheImpl::Options{}); } } // namespace mongo diff --git a/src/mongo/db/logical_session_cache_factory_mongos.cpp b/src/mongo/db/logical_session_cache_factory_mongos.cpp index e96319f8c48..6ba42da89de 100644 --- a/src/mongo/db/logical_session_cache_factory_mongos.cpp +++ b/src/mongo/db/logical_session_cache_factory_mongos.cpp @@ -32,6 +32,7 @@ #include "mongo/db/logical_session_cache_factory_mongos.h" +#include "mongo/db/logical_session_cache_impl.h" #include "mongo/db/server_parameters.h" #include "mongo/db/service_liason_mongos.h" #include "mongo/db/sessions_collection_sharded.h" @@ -43,8 +44,8 @@ std::unique_ptr<LogicalSessionCache> makeLogicalSessionCacheS() { auto liason = stdx::make_unique<ServiceLiasonMongos>(); auto sessionsColl = stdx::make_unique<SessionsCollectionSharded>(); - return stdx::make_unique<LogicalSessionCache>( - std::move(liason), std::move(sessionsColl), LogicalSessionCache::Options{}); + return stdx::make_unique<LogicalSessionCacheImpl>( + std::move(liason), std::move(sessionsColl), LogicalSessionCacheImpl::Options{}); } } // namespace mongo diff --git a/src/mongo/db/logical_session_cache_impl.cpp b/src/mongo/db/logical_session_cache_impl.cpp new file mode 100644 index 00000000000..c6dd6df3630 --- /dev/null +++ b/src/mongo/db/logical_session_cache_impl.cpp @@ -0,0 +1,266 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kControl + +#include "mongo/platform/basic.h" + +#include "mongo/db/logical_session_cache_impl.h" + +#include "mongo/db/logical_session_id.h" +#include "mongo/db/logical_session_id_helpers.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/server_parameters.h" +#include "mongo/db/service_context.h" +#include "mongo/platform/atomic_word.h" +#include "mongo/util/duration.h" +#include "mongo/util/log.h" +#include "mongo/util/periodic_runner.h" + +namespace mongo { + +MONGO_EXPORT_STARTUP_SERVER_PARAMETER(logicalSessionRecordCacheSize, + int, + LogicalSessionCacheImpl::kLogicalSessionCacheDefaultCapacity); + +MONGO_EXPORT_STARTUP_SERVER_PARAMETER( + logicalSessionRefreshMinutes, + int, + LogicalSessionCacheImpl::kLogicalSessionDefaultRefresh.count()); + +constexpr int LogicalSessionCacheImpl::kLogicalSessionCacheDefaultCapacity; +constexpr Minutes LogicalSessionCacheImpl::kLogicalSessionDefaultRefresh; + +LogicalSessionCacheImpl::LogicalSessionCacheImpl(std::unique_ptr<ServiceLiason> service, + std::unique_ptr<SessionsCollection> collection, + Options options) + : _refreshInterval(options.refreshInterval), + _sessionTimeout(options.sessionTimeout), + _service(std::move(service)), + _sessionsColl(std::move(collection)), + _cache(options.capacity) { + PeriodicRunner::PeriodicJob job{[this](Client* client) { _periodicRefresh(client); }, + duration_cast<Milliseconds>(_refreshInterval)}; + _service->scheduleJob(std::move(job)); +} + +LogicalSessionCacheImpl::~LogicalSessionCacheImpl() { + try { + _service->join(); + } catch (...) { + // If we failed to join we might still be running a background thread, + // log but swallow the error since there is no good way to recover. + severe() << "Failed to join background service thread"; + } +} + +Status LogicalSessionCacheImpl::promote(LogicalSessionId lsid) { + stdx::unique_lock<stdx::mutex> lk(_cacheMutex); + auto it = _cache.find(lsid); + if (it == _cache.end()) { + return {ErrorCodes::NoSuchSession, "no matching session record found in the cache"}; + } + + // Update the last use time before returning. + it->second.setLastUse(now()); + return Status::OK(); +} + +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 Status::OK(); +} + +Status LogicalSessionCacheImpl::refreshSessions(OperationContext* opCtx, + const RefreshSessionsCmdFromClient& cmd) { + // Update the timestamps of all these records in our cache. + auto sessions = makeLogicalSessionIds(cmd.getRefreshSessions(), opCtx); + for (auto& lsid : sessions) { + if (!promote(lsid).isOK()) { + // This is a new record, insert it. + _addToCache(makeLogicalSessionRecord(opCtx, lsid, now())); + } + } + + return Status::OK(); +} + +Status LogicalSessionCacheImpl::refreshSessions(OperationContext* opCtx, + const RefreshSessionsCmdFromClusterMember& cmd) { + LogicalSessionRecordSet toRefresh{}; + + // Update the timestamps of all these records in our cache. + auto records = cmd.getRefreshSessionsInternal(); + for (auto& record : records) { + if (!promote(record.getId()).isOK()) { + // This is a new record, insert it. + _addToCache(record); + } + toRefresh.insert(record); + } + + // Write to the sessions collection now. + return _sessionsColl->refreshSessions(opCtx, toRefresh, now()); +} + +void LogicalSessionCacheImpl::vivify(OperationContext* opCtx, const LogicalSessionId& lsid) { + if (!promote(lsid).isOK()) { + startSession(opCtx, makeLogicalSessionRecord(opCtx, lsid, now())).ignore(); + } +} + +Status LogicalSessionCacheImpl::refreshNow(Client* client) { + return _refresh(client); +} + +Date_t LogicalSessionCacheImpl::now() { + return _service->now(); +} + +size_t LogicalSessionCacheImpl::size() { + stdx::lock_guard<stdx::mutex> lock(_cacheMutex); + return _cache.size(); +} + +void LogicalSessionCacheImpl::_periodicRefresh(Client* client) { + auto res = _refresh(client); + if (!res.isOK()) { + log() << "Failed to refresh session cache: " << res; + } + + return; +} + +Status LogicalSessionCacheImpl::_refresh(Client* client) { + LogicalSessionRecordSet activeSessions; + LogicalSessionRecordSet deadSessions; + + auto time = now(); + + // We should avoid situations where we have records in the cache + // that have been expired from the sessions collection. If they haven't been + // used in _sessionTimeout, we should just remove them. + + // Assemble a list of active session records in our cache + std::vector<decltype(_cache)::ListEntry> cacheCopy; + { + stdx::unique_lock<stdx::mutex> lk(_cacheMutex); + cacheCopy.assign(_cache.begin(), _cache.end()); + } + + for (auto& it : cacheCopy) { + auto record = it.second; + if (!_isDead(record, time)) { + activeSessions.insert(record); + } else { + deadSessions.insert(record); + } + } + + // Append any active sessions from the service. We should always have + // cache entries for active sessions. If we don't, then it is a sign that + // the cache needs to be larger, because active session records are being + // evicted. + + // Promote our cached entries for all active service sessions to be recently- + // used, and update their lastUse dates so we don't lose them to eviction. We + // do not need to do this with records from our own cache, which are being used + // regularly. Sessions for long-running queries, however, must be kept alive + // by us here. + auto serviceSessions = _service->getActiveSessions(); + { + stdx::unique_lock<stdx::mutex> lk(_cacheMutex); + for (auto lsid : serviceSessions) { + auto it = _cache.promote(lsid); + if (it != _cache.end()) { + // If we have not found our record, it may have been removed + // by another thread. + it->second.setLastUse(time); + activeSessions.insert(it->second); + } + + // TODO SERVER-29709: Rethink how active sessions interact with refreshes, + // and potentially move this block above the block where we separate + // dead sessions from live sessions, above. + activeSessions.insert(makeLogicalSessionRecord(lsid, time)); + } + } + + // Query into the sessions collection to do the refresh. If any sessions have + // failed to refresh, it means their authoritative records were removed, and + // we should remove such records from our cache as well. + { + boost::optional<ServiceContext::UniqueOperationContext> uniqueCtx; + auto* const opCtx = [&client, &uniqueCtx] { + if (client->getOperationContext()) { + return client->getOperationContext(); + } + + uniqueCtx.emplace(client->makeOperationContext()); + return uniqueCtx->get(); + }(); + + auto res = _sessionsColl->refreshSessions(opCtx, std::move(activeSessions), time); + if (!res.isOK()) { + // TODO SERVER-29709: handle network errors here. + return res; + } + } + + // Prune any dead records out of the cache. Dead records are ones that failed to + // refresh, or ones that have expired locally. We don't make an effort to check + // if the locally-expired records still have live authoritative records in the + // sessions collection. We also don't attempt to resurrect our expired records. + // However, we *do* keep records alive if they are active on the service. + { + // TODO SERVER-29709: handle expiration separately from failure to refresh. + } + + return Status::OK(); +} + + +void LogicalSessionCacheImpl::clear() { + // TODO: What should this do? Wasn't implemented before + MONGO_UNREACHABLE; +} + +bool LogicalSessionCacheImpl::_isDead(const LogicalSessionRecord& record, Date_t now) const { + return record.getLastUse() + _sessionTimeout < now; +} + +boost::optional<LogicalSessionRecord> LogicalSessionCacheImpl::_addToCache( + LogicalSessionRecord record) { + stdx::unique_lock<stdx::mutex> lk(_cacheMutex); + return _cache.add(record.getId(), std::move(record)); +} + +} // namespace mongo diff --git a/src/mongo/db/logical_session_cache_impl.h b/src/mongo/db/logical_session_cache_impl.h new file mode 100644 index 00000000000..92b8dd7301c --- /dev/null +++ b/src/mongo/db/logical_session_cache_impl.h @@ -0,0 +1,154 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/db/logical_session_cache.h" +#include "mongo/db/logical_session_id.h" +#include "mongo/db/refresh_sessions_gen.h" +#include "mongo/db/service_liason.h" +#include "mongo/db/sessions_collection.h" +#include "mongo/db/time_proof_service.h" +#include "mongo/platform/atomic_word.h" +#include "mongo/stdx/thread.h" +#include "mongo/util/lru_cache.h" + +namespace mongo { + +class Client; +class OperationContext; +class ServiceContext; + +extern int logicalSessionRecordCacheSize; +extern int logicalSessionRefreshMinutes; + +/** + * A thread-safe cache structure for logical session records. + * + * The cache takes ownership of the passed-in ServiceLiason and + * SessionsCollection helper types. + */ +class LogicalSessionCacheImpl final : public LogicalSessionCache { +public: + static constexpr int kLogicalSessionCacheDefaultCapacity = 10000; + static constexpr Minutes kLogicalSessionDefaultRefresh = Minutes(5); + + /** + * An Options type to support the LogicalSessionCacheImpl. + */ + struct Options { + Options(){}; + + /** + * The number of session records to keep in the cache. + * + * May be set with --setParameter logicalSessionRecordCacheSize=X. + */ + int capacity = logicalSessionRecordCacheSize; + + /** + * A timeout value to use for sessions in the cache, in minutes. + * + * By default, this is set to 30 minutes. + * + * May be set with --setParameter localLogicalSessionTimeoutMinutes=X. + */ + Minutes sessionTimeout = Minutes(localLogicalSessionTimeoutMinutes); + + /** + * The interval over which the cache will refresh session records. + * + * By default, this is set to every 5 minutes. If the caller is + * setting the sessionTimeout by hand, it is suggested that they + * consider also setting the refresh interval accordingly. + * + * May be set with --setParameter logicalSessionRefreshMinutes=X. + */ + Minutes refreshInterval = Minutes(logicalSessionRefreshMinutes); + }; + + /** + * Construct a new session cache. + */ + explicit LogicalSessionCacheImpl(std::unique_ptr<ServiceLiason> service, + std::unique_ptr<SessionsCollection> collection, + Options options = Options{}); + + LogicalSessionCacheImpl(const LogicalSessionCacheImpl&) = delete; + LogicalSessionCacheImpl& operator=(const LogicalSessionCacheImpl&) = delete; + + ~LogicalSessionCacheImpl(); + + Status promote(LogicalSessionId lsid) 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; + + void clear() override; + + Status refreshNow(Client* client) override; + + Date_t now() override; + + size_t size() override; + +private: + /** + * Internal methods to handle scheduling and perform refreshes for active + * session records contained within the cache. + */ + void _periodicRefresh(Client* client); + Status _refresh(Client* client); + + /** + * Returns true if a record has passed its given expiration. + */ + bool _isDead(const LogicalSessionRecord& record, Date_t now) const; + + /** + * Takes the lock and inserts the given record into the cache. + */ + boost::optional<LogicalSessionRecord> _addToCache(LogicalSessionRecord record); + + const Minutes _refreshInterval; + const Minutes _sessionTimeout; + + std::unique_ptr<ServiceLiason> _service; + std::unique_ptr<SessionsCollection> _sessionsColl; + + stdx::mutex _cacheMutex; + LRUCache<LogicalSessionId, LogicalSessionRecord, LogicalSessionIdHash> _cache; +}; + +} // namespace mongo diff --git a/src/mongo/db/logical_session_cache_noop.h b/src/mongo/db/logical_session_cache_noop.h new file mode 100644 index 00000000000..59785ff240e --- /dev/null +++ b/src/mongo/db/logical_session_cache_noop.h @@ -0,0 +1,78 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include "mongo/db/logical_session_cache.h" + +namespace mongo { + +class Client; +class OperationContext; +class ServiceContext; + +/** + * A noop logical session cache for use in tests + */ +class LogicalSessionCacheNoop : public LogicalSessionCache { +public: + Status promote(LogicalSessionId lsid) override { + return Status::OK(); + } + + Status startSession(OperationContext* opCtx, LogicalSessionRecord record) override { + return Status::OK(); + } + + Status refreshSessions(OperationContext* opCtx, + const RefreshSessionsCmdFromClient& cmd) override { + return Status::OK(); + } + Status refreshSessions(OperationContext* opCtx, + const RefreshSessionsCmdFromClusterMember& cmd) override { + return Status::OK(); + } + + void vivify(OperationContext* opCtx, const LogicalSessionId& lsid) override {} + + void clear() override {} + + Status refreshNow(Client* client) override { + return Status::OK(); + } + + Date_t now() override { + return Date_t::now(); + } + + size_t size() override { + return 0; + } +}; + +} // namespace mongo diff --git a/src/mongo/db/logical_session_cache_test.cpp b/src/mongo/db/logical_session_cache_test.cpp index 83b0278bb41..6198b61d233 100644 --- a/src/mongo/db/logical_session_cache_test.cpp +++ b/src/mongo/db/logical_session_cache_test.cpp @@ -31,6 +31,7 @@ #include "mongo/bson/oid.h" #include "mongo/db/auth/user_name.h" #include "mongo/db/logical_session_cache.h" +#include "mongo/db/logical_session_cache_impl.h" #include "mongo/db/logical_session_id.h" #include "mongo/db/logical_session_id_helpers.h" #include "mongo/db/operation_context_noop.h" @@ -46,7 +47,7 @@ namespace { const Milliseconds kSessionTimeout = duration_cast<Milliseconds>(kLogicalSessionDefaultTimeout); const Milliseconds kForceRefresh = - duration_cast<Milliseconds>(LogicalSessionCache::kLogicalSessionDefaultRefresh); + duration_cast<Milliseconds>(LogicalSessionCacheImpl::kLogicalSessionDefaultRefresh); using SessionList = std::list<LogicalSessionId>; @@ -68,8 +69,8 @@ public: auto mockService = stdx::make_unique<MockServiceLiason>(_service); auto mockSessions = stdx::make_unique<MockSessionsCollection>(_sessions); - _cache = - stdx::make_unique<LogicalSessionCache>(std::move(mockService), std::move(mockSessions)); + _cache = stdx::make_unique<LogicalSessionCacheImpl>(std::move(mockService), + std::move(mockSessions)); } void tearDown() override { @@ -344,7 +345,7 @@ TEST_F(LogicalSessionCacheTest, RefreshCachedAndServiceSignedLsidsTogether) { // Test large sets of cache-only session lsids TEST_F(LogicalSessionCacheTest, ManySignedLsidsInCacheRefresh) { - int count = LogicalSessionCache::kLogicalSessionCacheDefaultCapacity; + int count = LogicalSessionCacheImpl::kLogicalSessionCacheDefaultCapacity; for (int i = 0; i < count; i++) { auto record = makeLogicalSessionRecordForTest(); cache()->startSession(opCtx(), record).transitional_ignore(); @@ -364,7 +365,7 @@ TEST_F(LogicalSessionCacheTest, ManySignedLsidsInCacheRefresh) { // Test larger sets of service-only session lsids TEST_F(LogicalSessionCacheTest, ManyLongRunningSessionsRefresh) { - int count = LogicalSessionCache::kLogicalSessionCacheDefaultCapacity; + int count = LogicalSessionCacheImpl::kLogicalSessionCacheDefaultCapacity; for (int i = 0; i < count; i++) { auto lsid = makeLogicalSessionIdForTest(); service()->add(lsid); @@ -384,7 +385,7 @@ TEST_F(LogicalSessionCacheTest, ManyLongRunningSessionsRefresh) { // Test larger mixed sets of cache/service active sessions TEST_F(LogicalSessionCacheTest, ManySessionsRefreshComboDeluxe) { - int count = LogicalSessionCache::kLogicalSessionCacheDefaultCapacity; + int count = LogicalSessionCacheImpl::kLogicalSessionCacheDefaultCapacity; for (int i = 0; i < count; i++) { auto lsid = makeLogicalSessionIdForTest(); service()->add(lsid); diff --git a/src/mongo/db/logical_session_id_helpers.cpp b/src/mongo/db/logical_session_id_helpers.cpp index 9f90cc90ee0..6e47690a111 100644 --- a/src/mongo/db/logical_session_id_helpers.cpp +++ b/src/mongo/db/logical_session_id_helpers.cpp @@ -32,7 +32,6 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/user.h" -#include "mongo/db/logical_session_cache.h" #include "mongo/db/operation_context.h" namespace mongo { @@ -166,32 +165,6 @@ LogicalSessionToClient makeLogicalSessionToClient(const LogicalSessionId& lsid) return id; }; -void initializeOperationSessionInfo(OperationContext* opCtx, - const BSONObj& requestBody, - bool requiresAuth) { - if (!requiresAuth) { - return; - } - - auto osi = OperationSessionInfoFromClient::parse(IDLParserErrorContext("OperationSessionInfo"), - requestBody); - - if (osi.getSessionId()) { - opCtx->setLogicalSessionId(makeLogicalSessionId(*(osi.getSessionId()), opCtx)); - } - - if (osi.getTxnNumber()) { - uassert(ErrorCodes::IllegalOperation, - "Transaction number requires a sessionId to be specified", - opCtx->getLogicalSessionId()); - uassert(ErrorCodes::BadValue, - "Transaction number cannot be negative", - *osi.getTxnNumber() >= 0); - - opCtx->setTxnNumber(*osi.getTxnNumber()); - } -} - LogicalSessionIdSet makeLogicalSessionIds(const std::vector<LogicalSessionFromClient>& sessions, OperationContext* opCtx, std::initializer_list<Privilege> allowSpoof) { diff --git a/src/mongo/db/logical_session_id_helpers.h b/src/mongo/db/logical_session_id_helpers.h index e74b2df7e44..2b4c2e76e61 100644 --- a/src/mongo/db/logical_session_id_helpers.h +++ b/src/mongo/db/logical_session_id_helpers.h @@ -60,18 +60,4 @@ LogicalSessionIdSet makeLogicalSessionIds(const std::vector<LogicalSessionFromCl OperationContext* opCtx, std::initializer_list<Privilege> allowSpoof = {}); -/** - * Parses the session information from the body of a request and installs it on the current - * operation context. Must only be called once per operation and should be done right in the - * beginning. - * - * Throws if the sessionId/txnNumber combination is not properly formatted. - * - * requiresAuth specifies if the command we're initializing operationSessionInfo for requires - * authorization or not. This can be determined by invoking ->requiresAuth() on the parsed command. - */ -void initializeOperationSessionInfo(OperationContext* opCtx, - const BSONObj& requestBody, - bool requiresAuth); - } // namespace mongo diff --git a/src/mongo/db/logical_session_id_test.cpp b/src/mongo/db/logical_session_id_test.cpp index c10ce634cfc..72c51bc1952 100644 --- a/src/mongo/db/logical_session_id_test.cpp +++ b/src/mongo/db/logical_session_id_test.cpp @@ -40,11 +40,16 @@ #include "mongo/db/auth/authz_manager_external_state_mock.h" #include "mongo/db/auth/authz_session_external_state_mock.h" #include "mongo/db/auth/user.h" +#include "mongo/db/initialize_operation_session_info.h" #include "mongo/db/jsobj.h" +#include "mongo/db/logical_session_cache.h" +#include "mongo/db/logical_session_cache_impl.h" #include "mongo/db/logical_session_id_helpers.h" #include "mongo/db/operation_context_noop.h" #include "mongo/db/server_options.h" #include "mongo/db/service_context_noop.h" +#include "mongo/db/service_liason_mock.h" +#include "mongo/db/sessions_collection_mock.h" #include "mongo/transport/session.h" #include "mongo/transport/transport_layer_mock.h" #include "mongo/unittest/unittest.h" @@ -88,6 +93,16 @@ public: AuthorizationSession::set(client.get(), std::move(localauthzSession)); authzManager->setAuthEnabled(true); + + auto localServiceLiason = + stdx::make_unique<MockServiceLiason>(std::make_shared<MockServiceLiasonImpl>()); + auto localSessionsCollection = stdx::make_unique<MockSessionsCollection>( + std::make_shared<MockSessionsCollectionImpl>()); + + auto localLogicalSessionCache = stdx::make_unique<LogicalSessionCacheImpl>( + std::move(localServiceLiason), std::move(localSessionsCollection)); + + LogicalSessionCache::set(&serviceContext, std::move(localLogicalSessionCache)); } User* addSimpleUser(UserName un) { diff --git a/src/mongo/db/service_entry_point_mongod.cpp b/src/mongo/db/service_entry_point_mongod.cpp index 6c24eab6d3a..15cd5421554 100644 --- a/src/mongo/db/service_entry_point_mongod.cpp +++ b/src/mongo/db/service_entry_point_mongod.cpp @@ -44,6 +44,7 @@ #include "mongo/db/cursor_manager.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/diag_log.h" +#include "mongo/db/initialize_operation_session_info.h" #include "mongo/db/introspect.h" #include "mongo/db/jsobj.h" #include "mongo/db/lasterror.h" diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index d1eb29e7d4c..68660c0b42f 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -87,6 +87,7 @@ env.Library( '$BUILD_DIR/mongo/db/commands/killcursors_common', '$BUILD_DIR/mongo/db/ftdc/ftdc_server', '$BUILD_DIR/mongo/db/commands/write_commands_common', + '$BUILD_DIR/mongo/db/logical_session_cache_impl', '$BUILD_DIR/mongo/db/pipeline/aggregation', '$BUILD_DIR/mongo/db/views/views', '$BUILD_DIR/mongo/executor/async_multicaster', diff --git a/src/mongo/s/commands/cluster_get_last_error_cmd.cpp b/src/mongo/s/commands/cluster_get_last_error_cmd.cpp index e8ea9c593de..55bc8dbd124 100644 --- a/src/mongo/s/commands/cluster_get_last_error_cmd.cpp +++ b/src/mongo/s/commands/cluster_get_last_error_cmd.cpp @@ -206,6 +206,10 @@ public: // No auth required for getlasterror } + bool requiresAuth() const override { + return false; + } + virtual bool run(OperationContext* opCtx, const std::string& dbname, const BSONObj& cmdObj, diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp index 6fe90e7e79e..1e14c1cb99a 100644 --- a/src/mongo/s/commands/strategy.cpp +++ b/src/mongo/s/commands/strategy.cpp @@ -41,6 +41,7 @@ #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" +#include "mongo/db/initialize_operation_session_info.h" #include "mongo/db/logical_clock.h" #include "mongo/db/logical_session_id_helpers.h" #include "mongo/db/logical_time_validator.h" diff --git a/src/mongo/s/query/SConscript b/src/mongo/s/query/SConscript index 259e2d28244..311a5b47a01 100644 --- a/src/mongo/s/query/SConscript +++ b/src/mongo/s/query/SConscript @@ -135,6 +135,7 @@ env.Library( '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/auth/authcore', '$BUILD_DIR/mongo/db/kill_sessions', + '$BUILD_DIR/mongo/db/logical_session_cache', '$BUILD_DIR/mongo/db/logical_session_id', ], ) diff --git a/src/mongo/s/query/cluster_cursor_manager.cpp b/src/mongo/s/query/cluster_cursor_manager.cpp index 25e63a50d54..8585a6114d6 100644 --- a/src/mongo/s/query/cluster_cursor_manager.cpp +++ b/src/mongo/s/query/cluster_cursor_manager.cpp @@ -35,6 +35,7 @@ #include <set> #include "mongo/db/kill_sessions_common.h" +#include "mongo/db/logical_session_cache.h" #include "mongo/util/clock_source.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" @@ -282,6 +283,12 @@ StatusWith<ClusterCursorManager::PinnedCursor> ClusterCursorManager::checkOutCur return cursorInUseStatus(nss, cursorId); } + // 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->getLsid()) { + LogicalSessionCache::get(opCtx)->vivify(opCtx, cursor->getLsid().get()); + } + // Note that pinning a cursor transfers ownership of the underlying ClusterClientCursor object // to the pin; the CursorEntry is left with a null ClusterClientCursor. return PinnedCursor(this, std::move(cursor), nss, cursorId); diff --git a/src/mongo/s/query/cluster_cursor_manager_test.cpp b/src/mongo/s/query/cluster_cursor_manager_test.cpp index ab46f50e537..75695e1deb7 100644 --- a/src/mongo/s/query/cluster_cursor_manager_test.cpp +++ b/src/mongo/s/query/cluster_cursor_manager_test.cpp @@ -32,6 +32,10 @@ #include <vector> +#include "mongo/db/logical_session_cache.h" +#include "mongo/db/logical_session_cache_noop.h" +#include "mongo/db/operation_context_noop.h" +#include "mongo/db/service_context_noop.h" #include "mongo/s/query/cluster_client_cursor_mock.h" #include "mongo/stdx/memory.h" #include "mongo/unittest/unittest.h" @@ -48,6 +52,10 @@ class ClusterCursorManagerTest : public unittest::Test { protected: ClusterCursorManagerTest() : _manager(&_clockSourceMock) {} + ServiceContextNoop serviceContext; + ServiceContext::UniqueOperationContext _opCtx; + Client* _client; + /** * Returns an unowned pointer to the manager owned by this test fixture. */ @@ -89,9 +97,25 @@ protected: } private: + void setUp() final { + auto client = serviceContext.makeClient("testClient"); + _opCtx = client->makeOperationContext(); + _client = client.get(); + Client::setCurrent(std::move(client)); + + LogicalSessionCache::set(&serviceContext, stdx::make_unique<LogicalSessionCacheNoop>()); + } + void tearDown() final { _manager.killAllCursors(); _manager.reapZombieCursors(nullptr); + + if (_opCtx) { + _opCtx.reset(); + } + + Client::releaseCurrent(); + LogicalSessionCache::set(&serviceContext, nullptr); } // List of flags representing whether our allocated cursors have been killed yet. The value of @@ -1008,7 +1032,7 @@ TEST_F(ClusterCursorManagerTest, GetSessionIdsWhileCheckedOut) { ClusterCursorManager::CursorLifetime::Mortal)); // Check the cursor out, then try to append cursors, see that we get one. - auto res = getManager()->checkOutCursor(nss, cursorId, nullptr); + auto res = getManager()->checkOutCursor(nss, cursorId, _opCtx.get()); ASSERT(res.isOK()); auto cursors = getManager()->getCursorsForSession(lsid); |