summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Carey <jcarey@argv.me>2017-07-31 18:33:20 -0400
committerJason Carey <jcarey@argv.me>2017-08-22 12:08:57 -0400
commit5fa946faad4ec2817f6b83684811333270f45c78 (patch)
treeaff3a3345fc2d9594854f387a53755f65750a1a9
parent53a831fd5d462fbd5bc050d0b4eaf5875a41400b (diff)
downloadmongo-5fa946faad4ec2817f6b83684811333270f45c78.tar.gz
SERVER-28342 Ensure session bookkeeping happens
Ensure we properly vivify session records on ingress.
-rw-r--r--jstests/noPassthrough/verify_session_cache_updates.js68
-rw-r--r--src/mongo/db/SConscript55
-rw-r--r--src/mongo/db/commands/get_last_error.cpp8
-rw-r--r--src/mongo/db/cursor_manager.cpp8
-rw-r--r--src/mongo/db/initialize_operation_session_info.cpp67
-rw-r--r--src/mongo/db/initialize_operation_session_info.h49
-rw-r--r--src/mongo/db/logical_session_cache.cpp215
-rw-r--r--src/mongo/db/logical_session_cache.h116
-rw-r--r--src/mongo/db/logical_session_cache_factory_mongod.cpp5
-rw-r--r--src/mongo/db/logical_session_cache_factory_mongos.cpp5
-rw-r--r--src/mongo/db/logical_session_cache_impl.cpp266
-rw-r--r--src/mongo/db/logical_session_cache_impl.h154
-rw-r--r--src/mongo/db/logical_session_cache_noop.h78
-rw-r--r--src/mongo/db/logical_session_cache_test.cpp13
-rw-r--r--src/mongo/db/logical_session_id_helpers.cpp27
-rw-r--r--src/mongo/db/logical_session_id_helpers.h14
-rw-r--r--src/mongo/db/logical_session_id_test.cpp15
-rw-r--r--src/mongo/db/service_entry_point_mongod.cpp1
-rw-r--r--src/mongo/s/commands/SConscript1
-rw-r--r--src/mongo/s/commands/cluster_get_last_error_cmd.cpp4
-rw-r--r--src/mongo/s/commands/strategy.cpp1
-rw-r--r--src/mongo/s/query/SConscript1
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.cpp7
-rw-r--r--src/mongo/s/query/cluster_cursor_manager_test.cpp26
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);