summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsamantharitter <samantha.ritter@10gen.com>2017-06-06 13:49:35 -0400
committersamantharitter <samantha.ritter@10gen.com>2017-06-15 13:54:58 -0400
commiteddb3e4535a9bf9198b207bf155889bf4625cdf0 (patch)
treed6b941bb582d5abbfef293503727d5c340410c54 /src
parent1efbf03521e9c6d85df33ab6786f6c121d8b56c2 (diff)
downloadmongo-eddb3e4535a9bf9198b207bf155889bf4625cdf0.tar.gz
SERVER-29417 Attach logical session ids to cursors
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/SConscript1
-rw-r--r--src/mongo/db/clientcursor.cpp4
-rw-r--r--src/mongo/db/clientcursor.h13
-rw-r--r--src/mongo/db/cursor_manager.cpp69
-rw-r--r--src/mongo/db/cursor_manager.h17
-rw-r--r--src/mongo/db/db_raii.cpp8
-rw-r--r--src/mongo/db/db_raii.h43
-rw-r--r--src/mongo/db/logical_session_id.h1
-rw-r--r--src/mongo/db/query/SConscript1
-rw-r--r--src/mongo/db/query/query_test_service_context.cpp9
-rw-r--r--src/mongo/db/query/query_test_service_context.h5
-rw-r--r--src/mongo/dbtests/cursor_manager_test.cpp189
12 files changed, 354 insertions, 6 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index 5d2149b9e55..c7649153f78 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -613,6 +613,7 @@ env.Library(
],
LIBDEPS=[
"$BUILD_DIR/mongo/base",
+ "$BUILD_DIR/mongo/db/logical_session_id",
"$BUILD_DIR/mongo/util/background_job",
"query/query",
"background",
diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp
index b6a686ba717..889d9961607 100644
--- a/src/mongo/db/clientcursor.cpp
+++ b/src/mongo/db/clientcursor.cpp
@@ -77,13 +77,15 @@ long long ClientCursor::totalOpen() {
return cursorStatsOpen.get();
}
-ClientCursor::ClientCursor(ClientCursorParams&& params,
+ClientCursor::ClientCursor(ClientCursorParams params,
CursorManager* cursorManager,
CursorId cursorId,
+ boost::optional<LogicalSessionId> lsid,
Date_t now)
: _cursorid(cursorId),
_nss(std::move(params.nss)),
_authenticatedUsers(std::move(params.authenticatedUsers)),
+ _lsid(std::move(lsid)),
_isReadCommitted(params.isReadCommitted),
_cursorManager(cursorManager),
_originatingCommand(params.originatingCommandObj),
diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h
index d2f216b01e1..32fb018a8e1 100644
--- a/src/mongo/db/clientcursor.h
+++ b/src/mongo/db/clientcursor.h
@@ -28,10 +28,13 @@
#pragma once
+#include <boost/optional.hpp>
+
#include "mongo/client/dbclientinterface.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/cursor_id.h"
#include "mongo/db/jsobj.h"
+#include "mongo/db/logical_session_id.h"
#include "mongo/db/query/plan_executor.h"
#include "mongo/db/record_id.h"
#include "mongo/stdx/functional.h"
@@ -108,6 +111,10 @@ public:
return makeUserNameIterator(_authenticatedUsers.begin(), _authenticatedUsers.end());
}
+ boost::optional<LogicalSessionId> getSessionId() const {
+ return _lsid;
+ }
+
bool isReadCommitted() const {
return _isReadCommitted;
}
@@ -218,9 +225,10 @@ private:
* Constructs a ClientCursor. Since cursors must come into being registered and pinned, this is
* private. See cursor_manager.h for more details.
*/
- ClientCursor(ClientCursorParams&& params,
+ ClientCursor(ClientCursorParams params,
CursorManager* cursorManager,
CursorId cursorId,
+ boost::optional<LogicalSessionId> lsid,
Date_t now);
/**
@@ -257,6 +265,9 @@ private:
// The set of authenticated users when this cursor was created.
std::vector<UserName> _authenticatedUsers;
+ // A logical session id for this cursor, if it is running inside of a session.
+ const boost::optional<LogicalSessionId> _lsid;
+
const bool _isReadCommitted = false;
CursorManager* _cursorManager;
diff --git a/src/mongo/db/cursor_manager.cpp b/src/mongo/db/cursor_manager.cpp
index f1bdd8a9442..a2e9687f5b1 100644
--- a/src/mongo/db/cursor_manager.cpp
+++ b/src/mongo/db/cursor_manager.cpp
@@ -101,6 +101,8 @@ public:
std::size_t timeoutCursors(OperationContext* opCtx, Date_t now);
+ void appendActiveSessions(OperationContext* opCtx, LogicalSessionIdSet* lsids);
+
int64_t nextSeed();
private:
@@ -267,12 +269,47 @@ std::size_t GlobalCursorIdCache::timeoutCursors(OperationContext* opCtx, Date_t
}
} // namespace
+void GlobalCursorIdCache::appendActiveSessions(OperationContext* opCtx,
+ LogicalSessionIdSet* lsids) {
+ // Get active session ids from the global cursor manager
+ globalCursorManager->appendActiveSessions(lsids);
+
+ // Compute the set of collection names that we have to get sessions for
+ vector<NamespaceString> namespaces;
+ {
+ stdx::lock_guard<SimpleMutex> lk(_mutex);
+ for (auto&& entry : _idToNss) {
+ namespaces.push_back(entry.second);
+ }
+ }
+
+ // For each collection, get its sessions under the collection lock (to prevent the
+ // collection from going away during the erase).
+ for (auto&& ns : namespaces) {
+ AutoGetCollectionOrView ctx(opCtx, NamespaceString(ns), MODE_IS);
+ if (!ctx.getDb()) {
+ continue;
+ }
+
+ Collection* collection = ctx.getCollection();
+ if (!collection) {
+ continue;
+ }
+
+ collection->getCursorManager()->appendActiveSessions(lsids);
+ }
+}
+
// ---
CursorManager* CursorManager::getGlobalCursorManager() {
return globalCursorManager.get();
}
+void CursorManager::appendAllActiveSessions(OperationContext* opCtx, LogicalSessionIdSet* lsids) {
+ globalCursorIdCache->appendActiveSessions(opCtx, lsids);
+}
+
std::size_t CursorManager::timeoutCursorsGlobal(OperationContext* opCtx, Date_t now) {
return globalCursorIdCache->timeoutCursors(opCtx, now);
}
@@ -482,6 +519,34 @@ void CursorManager::getCursorIds(std::set<CursorId>* openCursors) const {
}
}
+void CursorManager::appendActiveSessions(LogicalSessionIdSet* lsids) const {
+ auto allPartitions = _cursorMap->lockAllPartitions();
+ for (auto&& partition : allPartitions) {
+ for (auto&& entry : partition) {
+ auto cursor = entry.second;
+ if (auto id = cursor->getSessionId()) {
+ lsids->insert(id.value());
+ }
+ }
+ }
+}
+
+stdx::unordered_set<CursorId> CursorManager::getCursorsForSession(LogicalSessionId lsid) const {
+ stdx::unordered_set<CursorId> cursors;
+
+ auto allPartitions = _cursorMap->lockAllPartitions();
+ for (auto&& partition : allPartitions) {
+ for (auto&& entry : partition) {
+ auto cursor = entry.second;
+ if (cursor->getSessionId() == lsid) {
+ cursors.insert(cursor->cursorid());
+ }
+ }
+ }
+
+ return cursors;
+}
+
size_t CursorManager::numCursors() const {
return _cursorMap->size();
}
@@ -526,8 +591,8 @@ ClientCursorPin CursorManager::registerCursor(OperationContext* opCtx,
// we don't insert two cursors with the same cursor id.
stdx::lock_guard<SimpleMutex> lock(_registrationLock);
CursorId cursorId = allocateCursorId_inlock();
- std::unique_ptr<ClientCursor, ClientCursor::Deleter> clientCursor(
- new ClientCursor(std::move(cursorParams), this, cursorId, now));
+ std::unique_ptr<ClientCursor, ClientCursor::Deleter> clientCursor(new ClientCursor(
+ std::move(cursorParams), this, cursorId, opCtx->getLogicalSessionId(), now));
// Transfer ownership of the cursor to '_cursorMap'.
auto partition = _cursorMap->lockOnePartition(cursorId);
diff --git a/src/mongo/db/cursor_manager.h b/src/mongo/db/cursor_manager.h
index 40408c48830..0b243e21996 100644
--- a/src/mongo/db/cursor_manager.h
+++ b/src/mongo/db/cursor_manager.h
@@ -36,6 +36,7 @@
#include "mongo/db/record_id.h"
#include "mongo/platform/unordered_map.h"
#include "mongo/platform/unordered_set.h"
+#include "mongo/stdx/unordered_set.h"
#include "mongo/util/concurrency/mutex.h"
#include "mongo/util/duration.h"
@@ -78,6 +79,12 @@ public:
static constexpr Minutes kDefaultCursorTimeoutMinutes{10};
using RegistrationToken = Partitioned<unordered_set<PlanExecutor*>>::PartitionId;
+ /**
+ * Appends the sessions that have open cursors on the global cursor manager and across
+ * all collection-level cursor managers to the given set of lsids.
+ */
+ static void appendAllActiveSessions(OperationContext* opCtx, LogicalSessionIdSet* lsids);
+
CursorManager(NamespaceString nss);
/**
@@ -159,6 +166,16 @@ public:
void getCursorIds(std::set<CursorId>* openCursors) const;
/**
+ * Appends sessions that have open cursors in this cursor manager to the given set of lsids.
+ */
+ void appendActiveSessions(LogicalSessionIdSet* lsids) const;
+
+ /*
+ * Returns a list of all open cursors for the given session.
+ */
+ stdx::unordered_set<CursorId> getCursorsForSession(LogicalSessionId lsid) const;
+
+ /**
* Returns the number of ClientCursors currently registered. Excludes any registered bare
* PlanExecutors.
*/
diff --git a/src/mongo/db/db_raii.cpp b/src/mongo/db/db_raii.cpp
index 478047eb0cc..bff855fcc5b 100644
--- a/src/mongo/db/db_raii.cpp
+++ b/src/mongo/db/db_raii.cpp
@@ -72,6 +72,14 @@ AutoGetCollection::AutoGetCollection(OperationContext* opCtx,
}
}
+AutoGetCollectionOrView::AutoGetCollectionOrView(OperationContext* opCtx,
+ const NamespaceString& nss,
+ LockMode modeAll)
+ : _autoColl(opCtx, nss, modeAll, modeAll, AutoGetCollection::ViewMode::kViewsPermitted),
+ _view(_autoColl.getDb() && !_autoColl.getCollection()
+ ? _autoColl.getDb()->getViewCatalog()->lookup(opCtx, nss.ns())
+ : nullptr) {}
+
AutoGetOrCreateDb::AutoGetOrCreateDb(OperationContext* opCtx, StringData ns, LockMode mode)
: _dbLock(opCtx, ns, mode), _db(dbHolder().get(opCtx, ns)) {
invariant(mode == MODE_IX || mode == MODE_X);
diff --git a/src/mongo/db/db_raii.h b/src/mongo/db/db_raii.h
index 753909e3c6a..641b64e7988 100644
--- a/src/mongo/db/db_raii.h
+++ b/src/mongo/db/db_raii.h
@@ -128,12 +128,55 @@ private:
const Lock::CollectionLock _collLock;
Collection* const _coll;
+ friend class AutoGetCollectionOrView;
friend class AutoGetCollectionForRead;
friend class AutoGetCollectionForReadCommand;
friend class AutoGetCollectionOrViewForReadCommand;
};
/**
+ * RAII-style class which acquires the appropriate hierarchy of locks for a collection or
+ * view. The pointer to a view definition is nullptr if it does not exist.
+ *
+ * Use this when you have not yet determined if the namespace is a view or a collection.
+ * For example, you can use this to access a namespace's CursorManager.
+ *
+ * It is guaranteed that locks will be released when this object goes out of scope, therefore
+ * the view returned by this class should not be retained.
+ */
+class AutoGetCollectionOrView {
+ MONGO_DISALLOW_COPYING(AutoGetCollectionOrView);
+
+public:
+ AutoGetCollectionOrView(OperationContext* opCtx, const NamespaceString& nss, LockMode modeAll);
+
+ /**
+ * Returns nullptr if the database didn't exist.
+ */
+ Database* getDb() const {
+ return _autoColl.getDb();
+ }
+
+ /**
+ * Returns nullptr if the collection didn't exist.
+ */
+ Collection* getCollection() const {
+ return _autoColl.getCollection();
+ }
+
+ /**
+ * Returns nullptr if the view didn't exist.
+ */
+ ViewDefinition* getView() const {
+ return _view.get();
+ }
+
+private:
+ const AutoGetCollection _autoColl;
+ std::shared_ptr<ViewDefinition> _view;
+};
+
+/**
* RAII-style class, which acquires a lock on the specified database in the requested mode and
* obtains a reference to the database, creating it was non-existing. Used as a shortcut for
* calls to dbHolder().openDb(), taking care of locking details. The requested mode must be
diff --git a/src/mongo/db/logical_session_id.h b/src/mongo/db/logical_session_id.h
index eca1d8a33f5..f1fb85ce89e 100644
--- a/src/mongo/db/logical_session_id.h
+++ b/src/mongo/db/logical_session_id.h
@@ -32,6 +32,7 @@
#include "mongo/base/status_with.h"
#include "mongo/db/logical_session_id_gen.h"
+#include "mongo/stdx/unordered_set.h"
#include "mongo/util/uuid.h"
namespace mongo {
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index 7c42568f7c0..854f3045506 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -255,6 +255,7 @@ env.Library(
],
LIBDEPS=[
"collation/collator_factory_mock",
+ "$BUILD_DIR/mongo/db/logical_session_id",
"$BUILD_DIR/mongo/db/service_context",
],
)
diff --git a/src/mongo/db/query/query_test_service_context.cpp b/src/mongo/db/query/query_test_service_context.cpp
index e2682fe44d4..4ad6ac8e857 100644
--- a/src/mongo/db/query/query_test_service_context.cpp
+++ b/src/mongo/db/query/query_test_service_context.cpp
@@ -44,4 +44,13 @@ ServiceContext::UniqueOperationContext QueryTestServiceContext::makeOperationCon
return _uniqueClient->makeOperationContext();
}
+ServiceContext::UniqueOperationContext QueryTestServiceContext::makeOperationContext(
+ LogicalSessionId lsid) {
+ return _uniqueClient->makeOperationContext(std::move(lsid));
+}
+
+Client* QueryTestServiceContext::getClient() const {
+ return _uniqueClient.get();
+}
+
} // namespace mongo
diff --git a/src/mongo/db/query/query_test_service_context.h b/src/mongo/db/query/query_test_service_context.h
index 7466116d354..658b73ef36a 100644
--- a/src/mongo/db/query/query_test_service_context.h
+++ b/src/mongo/db/query/query_test_service_context.h
@@ -29,6 +29,7 @@
#pragma once
#include "mongo/db/client.h"
+#include "mongo/db/logical_session_id.h"
#include "mongo/db/service_context_noop.h"
namespace mongo {
@@ -44,6 +45,10 @@ public:
ServiceContext::UniqueOperationContext makeOperationContext();
+ ServiceContext::UniqueOperationContext makeOperationContext(LogicalSessionId lsid);
+
+ Client* getClient() const;
+
private:
ServiceContextNoop _serviceContext;
ServiceContext::UniqueClient _uniqueClient;
diff --git a/src/mongo/dbtests/cursor_manager_test.cpp b/src/mongo/dbtests/cursor_manager_test.cpp
index 07ec00df8fc..9b0d495e5df 100644
--- a/src/mongo/dbtests/cursor_manager_test.cpp
+++ b/src/mongo/dbtests/cursor_manager_test.cpp
@@ -28,6 +28,11 @@
#include "mongo/platform/basic.h"
+#include <algorithm>
+
+#include <boost/optional/optional.hpp>
+#include <boost/optional/optional_io.hpp>
+
#include "mongo/db/client.h"
#include "mongo/db/clientcursor.h"
#include "mongo/db/cursor_manager.h"
@@ -35,6 +40,7 @@
#include "mongo/db/exec/working_set.h"
#include "mongo/db/exec/working_set_common.h"
#include "mongo/db/operation_context.h"
+#include "mongo/db/operation_context_noop.h"
#include "mongo/db/query/plan_executor.h"
#include "mongo/db/query/query_test_service_context.h"
#include "mongo/dbtests/dbtests.h"
@@ -85,15 +91,28 @@ public:
}
std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> makeFakePlanExecutor() {
+ return makeFakePlanExecutor(_opCtx.get());
+ }
+
+ std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> makeFakePlanExecutor(
+ OperationContext* opCtx) {
auto workingSet = stdx::make_unique<WorkingSet>();
- auto queuedDataStage = stdx::make_unique<QueuedDataStage>(_opCtx.get(), workingSet.get());
- return unittest::assertGet(PlanExecutor::make(_opCtx.get(),
+ auto queuedDataStage = stdx::make_unique<QueuedDataStage>(opCtx, workingSet.get());
+ return unittest::assertGet(PlanExecutor::make(opCtx,
std::move(workingSet),
std::move(queuedDataStage),
kTestNss,
PlanExecutor::YieldPolicy::NO_YIELD));
}
+ ClientCursorParams makeParams(OperationContext* opCtx) {
+ return {makeFakePlanExecutor(opCtx), kTestNss, {}, false, BSONObj()};
+ }
+
+ ClientCursorPin makeCursor(OperationContext* opCtx) {
+ return _cursorManager.registerCursor(opCtx, makeParams(opCtx));
+ }
+
ClockSourceMock* useClock() {
return _clock;
}
@@ -111,6 +130,16 @@ private:
CursorManager _cursorManager{kTestNss};
};
+class CursorManagerTestCustomOpCtx : public CursorManagerTest {
+ void setUp() override {
+ _queryServiceContext->getClient()->resetOperationContext();
+ }
+
+ void tearDown() override {
+ _queryServiceContext->getClient()->setOperationContext(_opCtx.get());
+ }
+};
+
TEST_F(CursorManagerTest, GlobalCursorManagerShouldReportOwnershipOfCursorsItCreated) {
for (int i = 0; i < 1000; i++) {
auto cursorPin = CursorManager::getGlobalCursorManager()->registerCursor(
@@ -397,5 +426,161 @@ TEST_F(CursorManagerTest, CursorShouldNotTimeOutUntilIdleForLongEnoughAfterBeing
ASSERT_EQ(1UL, cursorManager->timeoutCursors(_opCtx.get(), clock->now()));
ASSERT_EQ(0UL, cursorManager->numCursors());
}
+
+/**
+ * Test that cursors inherit the logical session id from their operation context
+ */
+TEST_F(CursorManagerTestCustomOpCtx, LogicalSessionIdOnOperationCtxTest) {
+ // Cursors created on an op ctx without a session id have no session id.
+ {
+ auto opCtx = _queryServiceContext->makeOperationContext();
+ auto pinned = makeCursor(opCtx.get());
+
+ ASSERT_EQUALS(pinned.getCursor()->getSessionId(), boost::none);
+ }
+
+ // Cursors created on an op ctx with a session id have a session id.
+ {
+ auto lsid = LogicalSessionId::gen();
+ auto opCtx2 = _queryServiceContext->makeOperationContext(lsid);
+ auto pinned2 = makeCursor(opCtx2.get());
+
+ ASSERT_EQUALS(pinned2.getCursor()->getSessionId(), lsid);
+ }
+}
+
+/**
+ * Test that a manager whose cursors do not have sessions does not return them.
+ */
+TEST_F(CursorManagerTestCustomOpCtx, CursorsWithoutSessions) {
+ // Add a cursor with no session to the cursor manager.
+ auto opCtx = _queryServiceContext->makeOperationContext();
+ auto pinned = makeCursor(opCtx.get());
+ ASSERT_EQUALS(pinned.getCursor()->getSessionId(), boost::none);
+
+ // Retrieve all sessions active in manager - set should be empty.
+ LogicalSessionIdSet lsids;
+ useCursorManager()->appendActiveSessions(&lsids);
+ ASSERT(lsids.empty());
+}
+
+/**
+ * Test a manager that has one cursor running inside of a session.
+ */
+TEST_F(CursorManagerTestCustomOpCtx, OneCursorWithASession) {
+ // Add a cursor with a session to the cursor manager.
+ auto lsid = LogicalSessionId::gen();
+ auto opCtx = _queryServiceContext->makeOperationContext(lsid);
+ auto pinned = makeCursor(opCtx.get());
+
+ // Retrieve all sessions active in manager - set should contain just lsid.
+ LogicalSessionIdSet lsids;
+ useCursorManager()->appendActiveSessions(&lsids);
+ ASSERT_EQ(lsids.size(), size_t(1));
+ ASSERT(lsids.find(lsid) != lsids.end());
+
+ // Retrieve all cursors for this lsid - should be just ours.
+ auto cursors = useCursorManager()->getCursorsForSession(lsid);
+ ASSERT_EQ(cursors.size(), size_t(1));
+ auto cursorId = pinned.getCursor()->cursorid();
+ ASSERT(cursors.find(cursorId) != cursors.end());
+
+ // Remove the cursor from the manager.
+ pinned.release();
+ ASSERT_OK(useCursorManager()->eraseCursor(opCtx.get(), cursorId, false));
+
+ // There should be no more cursor entries by session id.
+ LogicalSessionIdSet sessions;
+ useCursorManager()->appendActiveSessions(&sessions);
+ ASSERT(sessions.empty());
+ ASSERT(useCursorManager()->getCursorsForSession(lsid).empty());
+}
+
+/**
+ * Test a manager with multiple cursors running inside of the same session.
+ */
+TEST_F(CursorManagerTestCustomOpCtx, MultipleCursorsWithSameSession) {
+ // Add two cursors on the same session to the cursor manager.
+ auto lsid = LogicalSessionId::gen();
+ auto opCtx = _queryServiceContext->makeOperationContext(lsid);
+ auto pinned = makeCursor(opCtx.get());
+ auto pinned2 = makeCursor(opCtx.get());
+
+ auto cursorId1 = pinned.getCursor()->cursorid();
+ auto cursorId2 = pinned2.getCursor()->cursorid();
+
+ // Retrieve all sessions - set should contain just lsid.
+ stdx::unordered_set<LogicalSessionId, LogicalSessionId::Hash> lsids;
+ useCursorManager()->appendActiveSessions(&lsids);
+ ASSERT_EQ(lsids.size(), size_t(1));
+ ASSERT(lsids.find(lsid) != lsids.end());
+
+ // Retrieve all cursors for session - should be both cursors.
+ auto cursors = useCursorManager()->getCursorsForSession(lsid);
+ ASSERT_EQ(cursors.size(), size_t(2));
+ ASSERT(cursors.find(cursorId1) != cursors.end());
+ ASSERT(cursors.find(cursorId2) != cursors.end());
+
+ // Remove one cursor from the manager.
+ pinned.release();
+ ASSERT_OK(useCursorManager()->eraseCursor(opCtx.get(), cursorId1, false));
+
+ // Should still be able to retrieve the session.
+ lsids.clear();
+ useCursorManager()->appendActiveSessions(&lsids);
+ ASSERT_EQ(lsids.size(), size_t(1));
+ ASSERT(lsids.find(lsid) != lsids.end());
+
+ // Should still be able to retrieve remaining cursor by session.
+ cursors = useCursorManager()->getCursorsForSession(lsid);
+ ASSERT_EQ(cursors.size(), size_t(1));
+ ASSERT(cursors.find(cursorId2) != cursors.end());
+}
+
+/**
+ * Test a manager with multiple cursors running inside of different sessions.
+ */
+TEST_F(CursorManagerTestCustomOpCtx, MultipleCursorsMultipleSessions) {
+ auto lsid1 = LogicalSessionId::gen();
+ auto lsid2 = LogicalSessionId::gen();
+
+ CursorId cursor1;
+ CursorId cursor2;
+
+ // Cursor with session 1.
+ {
+ auto opCtx1 = _queryServiceContext->makeOperationContext(lsid1);
+ cursor1 = makeCursor(opCtx1.get()).getCursor()->cursorid();
+ }
+
+ // Cursor with session 2.
+ {
+ auto opCtx2 = _queryServiceContext->makeOperationContext(lsid2);
+ cursor2 = makeCursor(opCtx2.get()).getCursor()->cursorid();
+ }
+
+ // Cursor with no session.
+ {
+ auto opCtx3 = _queryServiceContext->makeOperationContext();
+ makeCursor(opCtx3.get()).getCursor();
+ }
+
+ // Retrieve all sessions - should be both lsids.
+ LogicalSessionIdSet lsids;
+ useCursorManager()->appendActiveSessions(&lsids);
+ ASSERT_EQ(lsids.size(), size_t(2));
+ ASSERT(lsids.find(lsid1) != lsids.end());
+ ASSERT(lsids.find(lsid2) != lsids.end());
+
+ // Retrieve cursors for each session - should be just one.
+ auto cursors1 = useCursorManager()->getCursorsForSession(lsid1);
+ ASSERT_EQ(cursors1.size(), size_t(1));
+ ASSERT(cursors1.find(cursor1) != cursors1.end());
+
+ auto cursors2 = useCursorManager()->getCursorsForSession(lsid2);
+ ASSERT_EQ(cursors2.size(), size_t(1));
+ ASSERT(cursors2.find(cursor2) != cursors2.end());
+}
+
} // namespace
} // namespace mongo