summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernard Gorman <bernard.gorman@gmail.com>2018-03-28 19:00:18 +0100
committerBernard Gorman <bernard.gorman@gmail.com>2018-04-05 03:05:56 +0100
commit75160508e02cb5051f554baff3e67f2adb316057 (patch)
treefdf97c30fdcd0ec172470f43f4cc4aa6b66dcdb1
parentf00b908d2bf6cca4c2527eaa88b0ae79d745fd0b (diff)
downloadmongo-75160508e02cb5051f554baff3e67f2adb316057.tar.gz
SERVER-33294 Report stashed in-use locks for idle sessions in currentOp
-rw-r--r--jstests/sharding/aggregation_currentop.js101
-rw-r--r--src/mongo/db/SConscript2
-rw-r--r--src/mongo/db/curop.cpp8
-rw-r--r--src/mongo/db/kill_sessions.cpp14
-rw-r--r--src/mongo/db/kill_sessions.h6
-rw-r--r--src/mongo/db/pipeline/document_source_current_op.cpp37
-rw-r--r--src/mongo/db/pipeline/document_source_current_op.h13
-rw-r--r--src/mongo/db/pipeline/document_source_current_op_test.cpp1
-rw-r--r--src/mongo/db/pipeline/mongo_process_common.cpp6
-rw-r--r--src/mongo/db/pipeline/mongo_process_common.h9
-rw-r--r--src/mongo/db/pipeline/mongo_process_interface.h2
-rw-r--r--src/mongo/db/pipeline/pipeline_d.cpp31
-rw-r--r--src/mongo/db/pipeline/pipeline_d.h4
-rw-r--r--src/mongo/db/pipeline/stub_mongo_process_interface.h1
-rw-r--r--src/mongo/db/session.cpp28
-rw-r--r--src/mongo/db/session.h19
-rw-r--r--src/mongo/db/session_test.cpp73
-rw-r--r--src/mongo/s/commands/pipeline_s.h7
18 files changed, 344 insertions, 18 deletions
diff --git a/jstests/sharding/aggregation_currentop.js b/jstests/sharding/aggregation_currentop.js
index 5254619d2d7..ec7e92cb779 100644
--- a/jstests/sharding/aggregation_currentop.js
+++ b/jstests/sharding/aggregation_currentop.js
@@ -54,6 +54,7 @@
let clusterTestDB = mongosConn.getDB(jsTestName());
let clusterAdminDB = mongosConn.getDB("admin");
shardConn.waitForClusterTime(60);
+ let shardTestDB = shardConn.getDB(jsTestName());
let shardAdminDB = shardConn.getDB("admin");
function createUsers(conn) {
@@ -70,11 +71,14 @@
privileges: [{resource: {cluster: true}, actions: ["inprog"]}]
}));
- assert.commandWorked(
- adminDB.runCommand({createUser: "user_inprog", pwd: "pwd", roles: ["role_inprog"]}));
+ assert.commandWorked(adminDB.runCommand({
+ createUser: "user_inprog",
+ pwd: "pwd",
+ roles: ["readWriteAnyDatabase", "role_inprog"]
+ }));
assert.commandWorked(adminDB.runCommand(
- {createUser: "user_no_inprog", pwd: "pwd", roles: ["readAnyDatabase"]}));
+ {createUser: "user_no_inprog", pwd: "pwd", roles: ["readWriteAnyDatabase"]}));
}
// Create necessary users at both cluster and shard-local level.
@@ -115,6 +119,7 @@
shardRS = st.rs0;
clusterTestDB = mongosConn.getDB(jsTestName());
clusterAdminDB = mongosConn.getDB("admin");
+ shardTestDB = shardConn.getDB(jsTestName());
shardAdminDB = shardConn.getDB("admin");
}
@@ -602,6 +607,95 @@
runLocalOpsTests(shardConn);
//
+ // Stashed transactions tests.
+ //
+
+ // Test that $currentOp will display stashed transaction locks if 'idleSessions' is true, and
+ // will only permit a user to view other users' sessions if the caller possesses the 'inprog'
+ // privilege and 'allUsers' is true.
+ const userNames = ["user_inprog", "admin", "user_no_inprog"];
+ const sessionDBs = [];
+ const sessions = [];
+
+ // Returns a set of predicates that filter $currentOp for all stashed transactions.
+ function sessionFilter() {
+ return {
+ active: false,
+ opid: {$exists: false},
+ desc: "inactive transaction",
+ "lsid.id": {$in: sessions.map((session) => session.getSessionId().id)},
+ txnNumber: {$gte: 0, $lt: sessions.length}
+ };
+ }
+
+ for (let i in userNames) {
+ shardAdminDB.logout();
+ assert(shardAdminDB.auth(userNames[i], "pwd"));
+
+ // Create a session for this user.
+ const session = shardAdminDB.getMongo().startSession();
+
+ // For each session, start but do not complete a transaction.
+ const sessionDB = session.getDatabase(shardTestDB.getName());
+ assert.commandWorked(sessionDB.runCommand({
+ insert: "test",
+ documents: [{_id: `txn-insert-${userNames[i]}-${i}`}],
+ readConcern: {level: "snapshot"},
+ txnNumber: NumberLong(i),
+ startTransaction: true,
+ autocommit: false
+ }));
+ sessionDBs.push(sessionDB);
+ sessions.push(session);
+
+ // Use $currentOp to confirm that the incomplete transactions have stashed their locks while
+ // inactive, and that each user can only view their own sessions with 'allUsers:false'.
+ assert.eq(shardAdminDB
+ .aggregate([
+ {$currentOp: {allUsers: false, idleSessions: true}},
+ {$match: sessionFilter()}
+ ])
+ .itcount(),
+ 1);
+ }
+
+ // Log in as 'user_no_inprog' to verify that the user cannot view other users' sessions via
+ // 'allUsers:true'.
+ shardAdminDB.logout();
+ assert(shardAdminDB.auth("user_no_inprog", "pwd"));
+
+ assert.commandFailedWithCode(shardAdminDB.runCommand({
+ aggregate: 1,
+ cursor: {},
+ pipeline: [{$currentOp: {allUsers: true, idleSessions: true}}, {$match: sessionFilter()}]
+ }),
+ ErrorCodes.Unauthorized);
+
+ // Log in as 'user_inprog' to confirm that a user with the 'inprog' privilege can see all three
+ // stashed transactions with 'allUsers:true'.
+ shardAdminDB.logout();
+ assert(shardAdminDB.auth("user_inprog", "pwd"));
+
+ assert.eq(
+ shardAdminDB
+ .aggregate(
+ [{$currentOp: {allUsers: true, idleSessions: true}}, {$match: sessionFilter()}])
+ .itcount(),
+ 3);
+
+ // Allow all transactions to complete and close the associated sessions.
+ for (let i in userNames) {
+ assert(shardAdminDB.auth(userNames[i], "pwd"));
+ assert.commandWorked(sessionDBs[i].adminCommand({
+ commitTransaction: 1,
+ txnNumber: NumberLong(i),
+ autocommit: false,
+ writeConcern: {w: 'majority'}
+ }));
+ sessions[i].endSession();
+ }
+
+ //
// No-auth tests.
//
@@ -655,6 +749,7 @@
// Take the replica set out of the cluster.
shardConn = restartReplSet(st.rs0, {shardsvr: null});
+ shardTestDB = shardConn.getDB(jsTestName());
shardAdminDB = shardConn.getDB("admin");
// Test that the host field is present and the shard field is absent when run on mongoD.
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index 7c0d43768ef..17add6e4bf7 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -606,6 +606,7 @@ env.Library(
env.Idlc('transactions_stats.idl')[0],
],
LIBDEPS=[
+ '$BUILD_DIR/mongo/db/stats/fill_locker_info',
'$BUILD_DIR/mongo/idl/idl_parser',
'catalog/collection',
'catalog/database',
@@ -636,6 +637,7 @@ env.CppUnitTest(
'catalog/database_holder_mock',
'catalog_raii',
'service_context_noop_init',
+ 'stats/fill_locker_info',
],
)
diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp
index 1381b76cf29..9e5fd5582af 100644
--- a/src/mongo/db/curop.cpp
+++ b/src/mongo/db/curop.cpp
@@ -260,9 +260,13 @@ void CurOp::reportCurrentOpForClient(OperationContext* opCtx,
infoBuilder->append("killPending", true);
}
- if (clientOpCtx->getLogicalSessionId()) {
+ if (auto lsid = clientOpCtx->getLogicalSessionId()) {
BSONObjBuilder bob(infoBuilder->subobjStart("lsid"));
- clientOpCtx->getLogicalSessionId()->serialize(&bob);
+ lsid->serialize(&bob);
+ }
+
+ if (auto txnNumber = clientOpCtx->getTxnNumber()) {
+ infoBuilder->append("txnNumber", *txnNumber);
}
CurOp::get(clientOpCtx)->reportState(infoBuilder, truncateOps);
diff --git a/src/mongo/db/kill_sessions.cpp b/src/mongo/db/kill_sessions.cpp
index f16202c343e..1ac38112c3d 100644
--- a/src/mongo/db/kill_sessions.cpp
+++ b/src/mongo/db/kill_sessions.cpp
@@ -120,6 +120,20 @@ KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx,
return kasbp;
}
+KillAllSessionsByPatternSet makeSessionFilterForAuthenticatedUsers(OperationContext* opCtx) {
+ AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
+ KillAllSessionsByPatternSet patterns;
+
+ for (auto it = authSession->getAuthenticatedUserNames(); it.more(); it.next()) {
+ if (auto user = authSession->lookupUser(*it)) {
+ KillAllSessionsByPattern pattern;
+ pattern.setUid(user->getDigest());
+ patterns.emplace(std::move(pattern));
+ }
+ }
+ return patterns;
+}
+
KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx,
const LogicalSessionId& lsid) {
KillAllSessionsByPattern kasbp = makeKillAllSessionsByPattern(opCtx);
diff --git a/src/mongo/db/kill_sessions.h b/src/mongo/db/kill_sessions.h
index 547c2d038a3..40f6fabcc4b 100644
--- a/src/mongo/db/kill_sessions.h
+++ b/src/mongo/db/kill_sessions.h
@@ -94,6 +94,12 @@ KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx,
const KillAllSessionsUser& user);
/**
+ * Constructs a KillAllSessionsByPatternSet, each element of which matches the UID of a user that is
+ * currently authenticated on the given connection.
+ */
+KillAllSessionsByPatternSet makeSessionFilterForAuthenticatedUsers(OperationContext* opCtx);
+
+/**
* Constructs a kill sessions pattern for a particular logical session
*/
KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx,
diff --git a/src/mongo/db/pipeline/document_source_current_op.cpp b/src/mongo/db/pipeline/document_source_current_op.cpp
index 5d9eb824adf..3c982d53487 100644
--- a/src/mongo/db/pipeline/document_source_current_op.cpp
+++ b/src/mongo/db/pipeline/document_source_current_op.cpp
@@ -37,6 +37,7 @@ namespace mongo {
namespace {
const StringData kAllUsersFieldName = "allUsers"_sd;
const StringData kIdleConnectionsFieldName = "idleConnections"_sd;
+const StringData kIdleSessionsFieldName = "idleSessions"_sd;
const StringData kLocalOpsFieldName = "localOps"_sd;
const StringData kTruncateOpsFieldName = "truncateOps"_sd;
@@ -108,8 +109,11 @@ DocumentSource::GetNextResult DocumentSourceCurrentOp::getNext() {
pExpCtx->checkForInterrupt();
if (_ops.empty()) {
- _ops = pExpCtx->mongoProcessInterface->getCurrentOps(
- pExpCtx->opCtx, _includeIdleConnections, _includeOpsFromAllUsers, _truncateOps);
+ _ops = pExpCtx->mongoProcessInterface->getCurrentOps(pExpCtx->opCtx,
+ _includeIdleConnections,
+ _includeIdleSessions,
+ _includeOpsFromAllUsers,
+ _truncateOps);
_opsIter = _ops.begin();
@@ -180,6 +184,7 @@ intrusive_ptr<DocumentSource> DocumentSourceCurrentOp::createFromBson(
nss.db() == NamespaceString::kAdminDb && nss.isCollectionlessAggregateNS());
ConnMode includeIdleConnections = ConnMode::kExcludeIdle;
+ SessionMode includeIdleSessions = SessionMode::kIncludeIdle;
UserMode includeOpsFromAllUsers = UserMode::kExcludeOthers;
LocalOpsMode showLocalOpsOnMongoS = LocalOpsMode::kRemoteShardOps;
TruncationMode truncateOps = TruncationMode::kNoTruncation;
@@ -195,6 +200,15 @@ intrusive_ptr<DocumentSource> DocumentSourceCurrentOp::createFromBson(
elem.type() == BSONType::Bool);
includeIdleConnections =
(elem.boolean() ? ConnMode::kIncludeIdle : ConnMode::kExcludeIdle);
+ } else if (fieldName == kIdleSessionsFieldName) {
+ uassert(
+ ErrorCodes::FailedToParse,
+ str::stream() << "The 'idleSessions' parameter of the $currentOp stage must be a "
+ "boolean value, but found: "
+ << typeName(elem.type()),
+ elem.type() == BSONType::Bool);
+ includeIdleSessions =
+ (elem.boolean() ? SessionMode::kIncludeIdle : SessionMode::kExcludeIdle);
} else if (fieldName == kAllUsersFieldName) {
uassert(ErrorCodes::FailedToParse,
str::stream() << "The 'allUsers' parameter of the $currentOp stage must be a "
@@ -226,18 +240,27 @@ intrusive_ptr<DocumentSource> DocumentSourceCurrentOp::createFromBson(
}
}
- return new DocumentSourceCurrentOp(
- pExpCtx, includeIdleConnections, includeOpsFromAllUsers, showLocalOpsOnMongoS, truncateOps);
+ return new DocumentSourceCurrentOp(pExpCtx,
+ includeIdleConnections,
+ includeIdleSessions,
+ includeOpsFromAllUsers,
+ showLocalOpsOnMongoS,
+ truncateOps);
}
intrusive_ptr<DocumentSourceCurrentOp> DocumentSourceCurrentOp::create(
const boost::intrusive_ptr<ExpressionContext>& pExpCtx,
ConnMode includeIdleConnections,
+ SessionMode includeIdleSessions,
UserMode includeOpsFromAllUsers,
LocalOpsMode showLocalOpsOnMongoS,
TruncationMode truncateOps) {
- return new DocumentSourceCurrentOp(
- pExpCtx, includeIdleConnections, includeOpsFromAllUsers, showLocalOpsOnMongoS, truncateOps);
+ return new DocumentSourceCurrentOp(pExpCtx,
+ includeIdleConnections,
+ includeIdleSessions,
+ includeOpsFromAllUsers,
+ showLocalOpsOnMongoS,
+ truncateOps);
}
Value DocumentSourceCurrentOp::serialize(boost::optional<ExplainOptions::Verbosity> explain) const {
@@ -245,6 +268,8 @@ Value DocumentSourceCurrentOp::serialize(boost::optional<ExplainOptions::Verbosi
{getSourceName(),
Document{{kIdleConnectionsFieldName,
_includeIdleConnections == ConnMode::kIncludeIdle ? Value(true) : Value()},
+ {kIdleSessionsFieldName,
+ _includeIdleSessions == SessionMode::kExcludeIdle ? Value(false) : Value()},
{kAllUsersFieldName,
_includeOpsFromAllUsers == UserMode::kIncludeAll ? Value(true) : Value()},
{kLocalOpsFieldName,
diff --git a/src/mongo/db/pipeline/document_source_current_op.h b/src/mongo/db/pipeline/document_source_current_op.h
index c1f3e4654c1..0533ea09418 100644
--- a/src/mongo/db/pipeline/document_source_current_op.h
+++ b/src/mongo/db/pipeline/document_source_current_op.h
@@ -37,6 +37,7 @@ public:
using TruncationMode = MongoProcessInterface::CurrentOpTruncateMode;
using ConnMode = MongoProcessInterface::CurrentOpConnectionsMode;
using LocalOpsMode = MongoProcessInterface::CurrentOpLocalOpsMode;
+ using SessionMode = MongoProcessInterface::CurrentOpSessionsMode;
using UserMode = MongoProcessInterface::CurrentOpUserMode;
static constexpr StringData kStageName = "$currentOp"_sd;
@@ -95,6 +96,7 @@ public:
static boost::intrusive_ptr<DocumentSourceCurrentOp> create(
const boost::intrusive_ptr<ExpressionContext>& pExpCtx,
ConnMode includeIdleConnections = ConnMode::kExcludeIdle,
+ SessionMode includeIdleSessions = SessionMode::kIncludeIdle,
UserMode includeOpsFromAllUsers = UserMode::kExcludeOthers,
LocalOpsMode showLocalOpsOnMongoS = LocalOpsMode::kRemoteShardOps,
TruncationMode truncateOps = TruncationMode::kNoTruncation);
@@ -125,17 +127,20 @@ public:
private:
DocumentSourceCurrentOp(const boost::intrusive_ptr<ExpressionContext>& pExpCtx,
- ConnMode includeIdleConnections = ConnMode::kExcludeIdle,
- UserMode includeOpsFromAllUsers = UserMode::kExcludeOthers,
- LocalOpsMode showLocalOpsOnMongoS = LocalOpsMode::kRemoteShardOps,
- TruncationMode truncateOps = TruncationMode::kNoTruncation)
+ ConnMode includeIdleConnections,
+ SessionMode includeIdleSessions,
+ UserMode includeOpsFromAllUsers,
+ LocalOpsMode showLocalOpsOnMongoS,
+ TruncationMode truncateOps)
: DocumentSource(pExpCtx),
_includeIdleConnections(includeIdleConnections),
+ _includeIdleSessions(includeIdleSessions),
_includeOpsFromAllUsers(includeOpsFromAllUsers),
_showLocalOpsOnMongoS(showLocalOpsOnMongoS),
_truncateOps(truncateOps) {}
ConnMode _includeIdleConnections = ConnMode::kExcludeIdle;
+ SessionMode _includeIdleSessions = SessionMode::kIncludeIdle;
UserMode _includeOpsFromAllUsers = UserMode::kExcludeOthers;
LocalOpsMode _showLocalOpsOnMongoS = LocalOpsMode::kRemoteShardOps;
TruncationMode _truncateOps = TruncationMode::kNoTruncation;
diff --git a/src/mongo/db/pipeline/document_source_current_op_test.cpp b/src/mongo/db/pipeline/document_source_current_op_test.cpp
index 127dd1f4ee8..c98c80f77eb 100644
--- a/src/mongo/db/pipeline/document_source_current_op_test.cpp
+++ b/src/mongo/db/pipeline/document_source_current_op_test.cpp
@@ -67,6 +67,7 @@ public:
std::vector<BSONObj> getCurrentOps(OperationContext* opCtx,
CurrentOpConnectionsMode connMode,
+ CurrentOpSessionsMode sessionMode,
CurrentOpUserMode userMode,
CurrentOpTruncateMode truncateMode) const {
return _ops;
diff --git a/src/mongo/db/pipeline/mongo_process_common.cpp b/src/mongo/db/pipeline/mongo_process_common.cpp
index 634f9c71231..ff84540cd01 100644
--- a/src/mongo/db/pipeline/mongo_process_common.cpp
+++ b/src/mongo/db/pipeline/mongo_process_common.cpp
@@ -40,6 +40,7 @@ namespace mongo {
std::vector<BSONObj> MongoProcessCommon::getCurrentOps(OperationContext* opCtx,
CurrentOpConnectionsMode connMode,
+ CurrentOpSessionsMode sessionMode,
CurrentOpUserMode userMode,
CurrentOpTruncateMode truncateMode) const {
AuthorizationSession* ctxAuth = AuthorizationSession::get(opCtx->getClient());
@@ -68,6 +69,11 @@ std::vector<BSONObj> MongoProcessCommon::getCurrentOps(OperationContext* opCtx,
ops.emplace_back(_reportCurrentOpForClient(opCtx, client, truncateMode));
}
+ // If we need to report on idle Sessions, defer to the mongoD or mongoS implementations.
+ if (sessionMode == CurrentOpSessionsMode::kIncludeIdle) {
+ _reportCurrentOpsForIdleSessions(opCtx, userMode, &ops);
+ }
+
return ops;
}
diff --git a/src/mongo/db/pipeline/mongo_process_common.h b/src/mongo/db/pipeline/mongo_process_common.h
index 19596ade67a..2fcdf1e54d4 100644
--- a/src/mongo/db/pipeline/mongo_process_common.h
+++ b/src/mongo/db/pipeline/mongo_process_common.h
@@ -45,6 +45,7 @@ public:
std::vector<BSONObj> getCurrentOps(OperationContext* opCtx,
CurrentOpConnectionsMode connMode,
+ CurrentOpSessionsMode sessionMode,
CurrentOpUserMode userMode,
CurrentOpTruncateMode) const final;
@@ -57,6 +58,14 @@ protected:
virtual BSONObj _reportCurrentOpForClient(OperationContext* opCtx,
Client* client,
CurrentOpTruncateMode truncateOps) const = 0;
+
+ /**
+ * Iterates through all entries in the local SessionCatalog, and adds an entry to the 'ops'
+ * vector for each idle session that has stashed its transaction locks while sleeping.
+ */
+ virtual void _reportCurrentOpsForIdleSessions(OperationContext* opCtx,
+ CurrentOpUserMode userMode,
+ std::vector<BSONObj>* ops) const = 0;
};
} // namespace mongo
diff --git a/src/mongo/db/pipeline/mongo_process_interface.h b/src/mongo/db/pipeline/mongo_process_interface.h
index cdfae197c1f..3f63f7e66ff 100644
--- a/src/mongo/db/pipeline/mongo_process_interface.h
+++ b/src/mongo/db/pipeline/mongo_process_interface.h
@@ -63,6 +63,7 @@ public:
enum class CurrentOpUserMode { kIncludeAll, kExcludeOthers };
enum class CurrentOpTruncateMode { kNoTruncation, kTruncateOps };
enum class CurrentOpLocalOpsMode { kLocalMongosOps, kRemoteShardOps };
+ enum class CurrentOpSessionsMode { kIncludeIdle, kExcludeIdle };
struct MakePipelineOptions {
MakePipelineOptions(){};
@@ -172,6 +173,7 @@ public:
*/
virtual std::vector<BSONObj> getCurrentOps(OperationContext* opCtx,
CurrentOpConnectionsMode connMode,
+ CurrentOpSessionsMode sessionMode,
CurrentOpUserMode userMode,
CurrentOpTruncateMode) const = 0;
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp
index 9b4d7f6904c..2aa153f847d 100644
--- a/src/mongo/db/pipeline/pipeline_d.cpp
+++ b/src/mongo/db/pipeline/pipeline_d.cpp
@@ -51,6 +51,7 @@
#include "mongo/db/exec/shard_filter.h"
#include "mongo/db/exec/working_set.h"
#include "mongo/db/index/index_access_method.h"
+#include "mongo/db/kill_sessions.h"
#include "mongo/db/matcher/extensions_callback_real.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/document_source.h"
@@ -72,6 +73,7 @@
#include "mongo/db/s/metadata_manager.h"
#include "mongo/db/s/sharding_state.h"
#include "mongo/db/service_context.h"
+#include "mongo/db/session_catalog.h"
#include "mongo/db/stats/fill_locker_info.h"
#include "mongo/db/stats/storage_stats.h"
#include "mongo/db/stats/top.h"
@@ -823,14 +825,37 @@ BSONObj PipelineD::MongoDInterface::_reportCurrentOpForClient(
// Append lock stats before returning.
if (auto clientOpCtx = client->getOperationContext()) {
- Locker::LockerInfo lockerInfo;
- clientOpCtx->lockState()->getLockerInfo(&lockerInfo);
- fillLockerInfo(lockerInfo, builder);
+ if (auto lockerInfo = clientOpCtx->lockState()->getLockerInfo()) {
+ fillLockerInfo(*lockerInfo, builder);
+ }
}
return builder.obj();
}
+void PipelineD::MongoDInterface::_reportCurrentOpsForIdleSessions(OperationContext* opCtx,
+ CurrentOpUserMode userMode,
+ std::vector<BSONObj>* ops) const {
+ auto sessionCatalog = SessionCatalog::get(opCtx);
+
+ // If the user is listing only their own ops, we use makeSessionFilterForAuthenticatedUsers to
+ // create a pattern that will match against all authenticated usernames for the current client.
+ // If the user is listing ops for all users, we create an empty pattern; constructing an
+ // instance of SessionKiller::Matcher with this empty pattern will return all sessions.
+ auto sessionFilter = (userMode == CurrentOpUserMode::kExcludeOthers
+ ? makeSessionFilterForAuthenticatedUsers(opCtx)
+ : KillAllSessionsByPatternSet{{}});
+
+ sessionCatalog->scanSessions(opCtx,
+ {std::move(sessionFilter)},
+ [&](OperationContext* opCtx, Session* session) {
+ auto op = session->reportStashedState();
+ if (!op.isEmpty()) {
+ ops->emplace_back(op);
+ }
+ });
+}
+
std::unique_ptr<CollatorInterface> PipelineD::MongoDInterface::_getCollectionDefaultCollator(
OperationContext* opCtx, StringData dbName, UUID collectionUUID) {
auto it = _collatorCache.find(collectionUUID);
diff --git a/src/mongo/db/pipeline/pipeline_d.h b/src/mongo/db/pipeline/pipeline_d.h
index 630644413df..e9ab4fef8d2 100644
--- a/src/mongo/db/pipeline/pipeline_d.h
+++ b/src/mongo/db/pipeline/pipeline_d.h
@@ -115,6 +115,10 @@ public:
Client* client,
CurrentOpTruncateMode truncateOps) const final;
+ void _reportCurrentOpsForIdleSessions(OperationContext* opCtx,
+ CurrentOpUserMode userMode,
+ std::vector<BSONObj>* ops) const final;
+
private:
/**
* Looks up the collection default collator for the collection given by 'collectionUUID'. A
diff --git a/src/mongo/db/pipeline/stub_mongo_process_interface.h b/src/mongo/db/pipeline/stub_mongo_process_interface.h
index 0ec37b15eb4..9df0bb7baba 100644
--- a/src/mongo/db/pipeline/stub_mongo_process_interface.h
+++ b/src/mongo/db/pipeline/stub_mongo_process_interface.h
@@ -115,6 +115,7 @@ public:
std::vector<BSONObj> getCurrentOps(OperationContext* opCtx,
CurrentOpConnectionsMode connMode,
+ CurrentOpSessionsMode sessionMode,
CurrentOpUserMode userMode,
CurrentOpTruncateMode truncateMode) const override {
MONGO_UNREACHABLE;
diff --git a/src/mongo/db/session.cpp b/src/mongo/db/session.cpp
index 30bb3dfd51c..adfd71c0421 100644
--- a/src/mongo/db/session.cpp
+++ b/src/mongo/db/session.cpp
@@ -44,12 +44,14 @@
#include "mongo/db/query/get_executor.h"
#include "mongo/db/repl/read_concern_args.h"
#include "mongo/db/retryable_writes_stats.h"
+#include "mongo/db/stats/fill_locker_info.h"
#include "mongo/db/transaction_history_iterator.h"
#include "mongo/stdx/memory.h"
#include "mongo/transport/transport_layer.h"
#include "mongo/util/fail_point_service.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
+#include "mongo/util/net/sock.h"
namespace mongo {
namespace {
@@ -805,6 +807,32 @@ void Session::_commitTransaction(stdx::unique_lock<stdx::mutex> lk, OperationCon
_txnState = MultiDocumentTransactionState::kCommitted;
}
+BSONObj Session::reportStashedState() const {
+ BSONObjBuilder builder;
+ reportStashedState(&builder);
+ return builder.obj();
+}
+
+void Session::reportStashedState(BSONObjBuilder* builder) const {
+ stdx::lock_guard<stdx::mutex> ls(_mutex);
+
+ if (_txnResourceStash && _txnResourceStash->locker()) {
+ if (auto lockerInfo = _txnResourceStash->locker()->getLockerInfo()) {
+ invariant(_activeTxnNumber != kUninitializedTxnNumber);
+ builder->append("host", getHostNameCachedAndPort());
+ builder->append("desc", "inactive transaction");
+ {
+ BSONObjBuilder lsid(builder->subobjStart("lsid"));
+ getSessionId().serialize(&lsid);
+ }
+ builder->append("txnNumber", _activeTxnNumber);
+ builder->append("waitingForLock", false);
+ builder->append("active", false);
+ fillLockerInfo(*lockerInfo, *builder);
+ }
+ }
+}
+
void Session::_checkValid(WithLock) const {
uassert(ErrorCodes::ConflictingOperationInProgress,
str::stream() << "Session " << getSessionId()
diff --git a/src/mongo/db/session.h b/src/mongo/db/session.h
index 95e48ddc7a6..099c3ec043e 100644
--- a/src/mongo/db/session.h
+++ b/src/mongo/db/session.h
@@ -77,6 +77,13 @@ public:
TxnResources& operator=(TxnResources&&) = default;
/**
+ * Returns a const pointer to the stashed lock state, or nullptr if no stashed locks exist.
+ */
+ const Locker* locker() const {
+ return _locker.get();
+ }
+
+ /**
* Releases stashed transaction state onto 'opCtx'. Must only be called once.
*/
void release(OperationContext* opCtx);
@@ -301,6 +308,18 @@ public:
}
/**
+ * If this session is holding stashed locks in _txnResourceStash, reports the current state of
+ * the session using the provided builder. Locks the session object's mutex while running.
+ */
+ void reportStashedState(BSONObjBuilder* builder) const;
+
+ /**
+ * Convenience method which creates and populates a BSONObj containing the stashed state.
+ * Returns an empty BSONObj if this session has no stashed resources.
+ */
+ BSONObj reportStashedState() const;
+
+ /**
* Scan through the list of operations and add new oplog entries for updating
* config.transactions if needed.
*/
diff --git a/src/mongo/db/session_test.cpp b/src/mongo/db/session_test.cpp
index 89b4cd773b3..c14900f7a11 100644
--- a/src/mongo/db/session_test.cpp
+++ b/src/mongo/db/session_test.cpp
@@ -38,9 +38,11 @@
#include "mongo/db/repl/optime.h"
#include "mongo/db/service_context.h"
#include "mongo/db/session_catalog.h"
+#include "mongo/db/stats/fill_locker_info.h"
#include "mongo/stdx/future.h"
#include "mongo/stdx/memory.h"
#include "mongo/unittest/unittest.h"
+#include "mongo/util/net/sock.h"
namespace mongo {
namespace {
@@ -622,6 +624,77 @@ TEST_F(SessionTest, StashAndUnstashResources) {
session.commitTransaction(opCtx());
}
+TEST_F(SessionTest, ReportStashedResources) {
+ const auto sessionId = makeLogicalSessionIdForTest();
+ const TxnNumber txnNum = 20;
+ opCtx()->setLogicalSessionId(sessionId);
+ opCtx()->setTxnNumber(txnNum);
+
+ ASSERT(opCtx()->lockState());
+ ASSERT(opCtx()->recoveryUnit());
+
+ Session session(sessionId);
+ session.refreshFromStorageIfNeeded(opCtx());
+
+ session.beginOrContinueTxn(opCtx(), txnNum, boost::none, boost::none);
+
+ repl::ReadConcernArgs readConcernArgs;
+ ASSERT_OK(readConcernArgs.initialize(BSON("find"
+ << "test"
+ << repl::ReadConcernArgs::kReadConcernFieldName
+ << BSON(repl::ReadConcernArgs::kLevelFieldName
+ << "snapshot"))));
+ repl::ReadConcernArgs::get(opCtx()) = readConcernArgs;
+
+ // Perform initial unstash which sets up a WriteUnitOfWork.
+ session.unstashTransactionResources(opCtx());
+ ASSERT(opCtx()->getWriteUnitOfWork());
+
+ // Take a lock. This is expected in order to stash resources.
+ Lock::GlobalRead lk(opCtx(), Date_t::now());
+ ASSERT(lk.isLocked());
+
+ // Build a BSONObj containing the details which we expect to see reported when we call
+ // Session::reportStashedState.
+ const auto lockerInfo = opCtx()->lockState()->getLockerInfo();
+ ASSERT(lockerInfo);
+
+ auto reportBuilder =
+ std::move(BSONObjBuilder() << "host" << getHostNameCachedAndPort() << "desc"
+ << "inactive transaction"
+ << "lsid"
+ << sessionId.toBSON()
+ << "txnNumber"
+ << txnNum
+ << "waitingForLock"
+ << false
+ << "active"
+ << false);
+ fillLockerInfo(*lockerInfo, reportBuilder);
+
+ // Stash resources. The original Locker and RecoveryUnit now belong to the stash.
+ opCtx()->setStashedCursor();
+ session.stashTransactionResources(opCtx());
+ ASSERT(!opCtx()->getWriteUnitOfWork());
+
+ // Verify that the Session's report of its own stashed state aligns with our expectations.
+ ASSERT_BSONOBJ_EQ(session.reportStashedState(), reportBuilder.obj());
+
+ // Unset the read concern on the OperationContext. This is needed to unstash.
+ repl::ReadConcernArgs::get(opCtx()) = repl::ReadConcernArgs();
+
+ // Unstash the stashed resources. This restores the original Locker and RecoveryUnit to the
+ // OperationContext.
+ session.unstashTransactionResources(opCtx());
+ ASSERT(opCtx()->getWriteUnitOfWork());
+
+ // With the resources unstashed, verify that the Session reports an empty stashed state.
+ ASSERT(session.reportStashedState().isEmpty());
+
+ // Commit the transaction. This allows us to release locks.
+ session.commitTransaction(opCtx());
+}
+
TEST_F(SessionTest, StartTransactionRequiredToStartTxn) {
const auto sessionId = makeLogicalSessionIdForTest();
Session session(sessionId);
diff --git a/src/mongo/s/commands/pipeline_s.h b/src/mongo/s/commands/pipeline_s.h
index c0955d491d1..dcbcda6a747 100644
--- a/src/mongo/s/commands/pipeline_s.h
+++ b/src/mongo/s/commands/pipeline_s.h
@@ -139,6 +139,13 @@ public:
BSONObj _reportCurrentOpForClient(OperationContext* opCtx,
Client* client,
CurrentOpTruncateMode truncateOps) const final;
+
+ void _reportCurrentOpsForIdleSessions(OperationContext* opCtx,
+ CurrentOpUserMode userMode,
+ std::vector<BSONObj>* ops) const final {
+ // This implementation is a no-op, since mongoS does not maintain a SessionCatalog or
+ // hold stashed locks for idle sessions.
+ }
};
private: