summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/currentop_cursors.js188
-rw-r--r--jstests/noPassthrough/currentop_active_cursor.js1
-rw-r--r--src/mongo/db/clientcursor.cpp8
-rw-r--r--src/mongo/db/clientcursor.h41
-rw-r--r--src/mongo/db/commands/find_cmd.cpp5
-rw-r--r--src/mongo/db/commands/getmore_cmd.cpp7
-rw-r--r--src/mongo/db/curop.cpp8
-rw-r--r--src/mongo/db/cursor_manager.cpp2
-rw-r--r--src/mongo/db/generic_cursor.idl20
-rw-r--r--src/mongo/db/query/find.cpp12
-rw-r--r--src/mongo/dbtests/querytests.cpp2
-rw-r--r--src/mongo/s/query/cluster_client_cursor.h25
-rw-r--r--src/mongo/s/query/cluster_client_cursor_impl.cpp31
-rw-r--r--src/mongo/s/query/cluster_client_cursor_impl.h19
-rw-r--r--src/mongo/s/query/cluster_client_cursor_mock.cpp20
-rw-r--r--src/mongo/s/query/cluster_client_cursor_mock.h16
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.cpp31
-rw-r--r--src/mongo/s/query/cluster_cursor_manager.h25
-rw-r--r--src/mongo/s/query/cluster_find.cpp2
19 files changed, 402 insertions, 61 deletions
diff --git a/jstests/core/currentop_cursors.js b/jstests/core/currentop_cursors.js
index b4e0949fac9..e8fad63f8bb 100644
--- a/jstests/core/currentop_cursors.js
+++ b/jstests/core/currentop_cursors.js
@@ -2,44 +2,168 @@
* Tests that an idle cursor will appear in the $currentOp output if the idleCursors option is
* set to true.
*
- * @tags: [assumes_read_concern_unchanged]
+ * @tags: [assumes_read_concern_unchanged, requires_capped]
*/
(function() {
"use strict";
const coll = db.jstests_currentop;
- const adminDB = db.getSiblingDB("admin");
- coll.drop();
+ // Avoiding using the shell helper to avoid the implicit collection recreation.
+ db.runCommand({drop: coll.getName()});
+ assert.commandWorked(db.createCollection(coll.getName(), {capped: true, size: 1000}));
for (let i = 0; i < 5; ++i) {
assert.commandWorked(coll.insert({"val": i}));
}
- const findOut =
- assert.commandWorked(db.runCommand({find: "jstests_currentop", batchSize: 2})).cursor.id;
- const result = adminDB
- .aggregate([
- {$currentOp: {localOps: true, allUsers: false, idleCursors: true}},
- {$match: {$and: [{type: "idleCursor"}, {"cursor.cursorId": findOut}]}}
- ])
- .toArray();
- assert.eq(result.length, 1, result);
- assert.eq(result[0].cursor.nDocsReturned, 2, result);
- assert.eq(result[0].cursor.tailable, false, result);
- assert.eq(result[0].cursor.awaitData, false, result);
- assert.eq(result[0].cursor.noCursorTimeout, false, result);
- assert.eq(result[0].cursor.ns, coll.getFullName(), result);
- assert.eq(result[0].cursor.originatingCommand.find, "jstests_currentop", result);
- assert.eq(result[0].cursor.originatingCommand.batchSize, 2, result);
-
- const noIdle = adminDB
- .aggregate([
- {$currentOp: {allUsers: false, idleCursors: false}},
- {$match: {$and: [{type: "idleCursor"}, {"cursor.cursorId": findOut}]}}
- ])
- .toArray();
- assert.eq(noIdle.length, 0, tojson(noIdle));
- const noFlag =
- adminDB.aggregate([{$currentOp: {allUsers: false}}, {$match: {type: "idleCursor"}}])
- .toArray();
-
- assert.eq(noIdle.length, 0, tojson(noFlag));
+ /**
+ * runTest creates a new collection called jstests_currentop and then runs the provided find
+ * query. It calls $currentOp and does some basic assertions to make sure idleCursors is
+ * behaving as intended in each case.
+ * findFunc: A function that runs a find query. Is expected to return a cursorID.
+ * Arbitrary code can be run in findFunc as long as it returns a cursorID.
+ * assertFunc: A function that runs assertions against the results of the $currentOp.
+ * Takes the following arguments
+ * 'findOut': The cursorID returned from findFunc.
+ * 'result': The results from running $currenpOp as an array of JSON objects.
+ * Arbitrary code can be run in assertFunc, and there is no return value needed.
+ */
+ function runTest({findFunc, assertFunc}) {
+ const adminDB = db.getSiblingDB("admin");
+ const findOut = findFunc();
+ const result =
+ adminDB
+ .aggregate([
+ {$currentOp: {localOps: true, allUsers: false, idleCursors: true}},
+ {$match: {$and: [{type: "idleCursor"}, {"cursor.cursorId": findOut}]}}
+ ])
+ .toArray();
+ assert.eq(result[0].cursor.ns, coll.getFullName(), result);
+ assert.eq(result[0].cursor.originatingCommand.find, coll.getName(), result);
+ assertFunc(findOut, result);
+ const noIdle =
+ adminDB
+ .aggregate([
+ {$currentOp: {allUsers: false, idleCursors: false}},
+ {$match: {$and: [{type: "idleCursor"}, {"cursor.cursorId": findOut}]}}
+ ])
+ .toArray();
+ assert.eq(noIdle.length, 0, tojson(noIdle));
+ const noFlag =
+ adminDB.aggregate([{$currentOp: {allUsers: false}}, {$match: {type: "idleCursor"}}])
+ .toArray();
+
+ assert.eq(noIdle.length, 0, tojson(noFlag));
+ }
+
+ runTest({
+ findFunc: function() {
+ return assert.commandWorked(db.runCommand({find: "jstests_currentop", batchSize: 2}))
+ .cursor.id;
+ },
+ assertFunc: function(cursorId, result) {
+ assert.eq(result.length, 1, result);
+ const idleCursor = result[0].cursor;
+ assert.eq(idleCursor.nDocsReturned, 2, result);
+ assert.eq(idleCursor.nBatchesReturned, 1, result);
+ assert.eq(idleCursor.tailable, false, result);
+ assert.eq(idleCursor.awaitData, false, result);
+ assert.eq(idleCursor.noCursorTimeout, false, result);
+ assert.eq(idleCursor.originatingCommand.batchSize, 2, result);
+ assert.lte(idleCursor.createdDate, idleCursor.lastAccessDate, result);
+ }
+ });
+ runTest({
+ findFunc: function() {
+ return assert
+ .commandWorked(db.runCommand({
+ find: "jstests_currentop",
+ batchSize: 2,
+ tailable: true,
+ awaitData: true,
+ noCursorTimeout: true
+ }))
+ .cursor.id;
+ },
+ assertFunc: function(cursorId, result) {
+
+ assert.eq(result.length, 1, result);
+ const idleCursor = result[0].cursor;
+ assert.eq(idleCursor.tailable, true, result);
+ assert.eq(idleCursor.awaitData, true, result);
+ assert.eq(idleCursor.noCursorTimeout, true, result);
+ assert.eq(idleCursor.originatingCommand.batchSize, 2, result);
+ }
+ });
+ runTest({
+ findFunc: function() {
+ return assert.commandWorked(db.runCommand({find: "jstests_currentop", batchSize: 2}))
+ .cursor.id;
+ },
+ assertFunc: function(cursorId, result) {
+ const secondCursor =
+ assert.commandWorked(db.runCommand({find: "jstests_currentop", batchSize: 2}));
+ const adminDB = db.getSiblingDB("admin");
+ const secondResult =
+ adminDB
+ .aggregate([
+ {$currentOp: {localOps: true, allUsers: false, idleCursors: true}},
+ {
+ $match: {
+ $and: [
+ {type: "idleCursor"},
+ {"cursor.cursorId": secondCursor.cursor.id}
+ ]
+ }
+ }
+ ])
+ .toArray();
+ assert.lt(result[0].cursor.createdDate, secondResult[0].cursor.createdDate, function() {
+ return tojson(result) + tojson(secondResult);
+ });
+
+ }
+ });
+
+ runTest({
+ findFunc: function() {
+ return assert
+ .commandWorked(
+ db.runCommand({find: "jstests_currentop", batchSize: 4, noCursorTimeout: true}))
+ .cursor.id;
+ },
+ assertFunc: function(cursorId, result) {
+ const idleCursor = result[0].cursor;
+ assert.eq(result.length, 1, result);
+ assert.eq(idleCursor.nDocsReturned, 4, result);
+ assert.eq(idleCursor.nBatchesReturned, 1, result);
+ assert.eq(idleCursor.noCursorTimeout, true, result);
+ assert.eq(idleCursor.originatingCommand.batchSize, 4, result);
+ }
+ });
+
+ runTest({
+ findFunc: function() {
+ return assert.commandWorked(db.runCommand({find: "jstests_currentop", batchSize: 2}))
+ .cursor.id;
+ },
+ assertFunc: function(cursorId, result) {
+ const adminDB = db.getSiblingDB("admin");
+ const originalAccess = result[0].cursor.lastAccessDate;
+ assert.commandWorked(
+ db.runCommand({getMore: cursorId, collection: "jstests_currentop", batchSize: 2}));
+ result =
+ adminDB
+ .aggregate([
+ {$currentOp: {localOps: true, allUsers: false, idleCursors: true}},
+ {$match: {$and: [{type: "idleCursor"}, {"cursor.cursorId": cursorId}]}}
+ ])
+ .toArray();
+ const idleCursor = result[0].cursor;
+ assert.eq(idleCursor.nDocsReturned, 4, result);
+ assert.eq(idleCursor.nBatchesReturned, 2, result);
+ assert.eq(idleCursor.originatingCommand.batchSize, 2, result);
+ assert.lt(idleCursor.createdDate, idleCursor.lastAccessDate, result);
+ assert.lt(originalAccess, idleCursor.lastAccessDate, result);
+ }
+ });
+
})();
diff --git a/jstests/noPassthrough/currentop_active_cursor.js b/jstests/noPassthrough/currentop_active_cursor.js
index c8375f17e5e..a7c6f40a6db 100644
--- a/jstests/noPassthrough/currentop_active_cursor.js
+++ b/jstests/noPassthrough/currentop_active_cursor.js
@@ -29,7 +29,6 @@
const cursorObject = activeCursors[0].cursor;
assert.eq(cursorObject.originatingCommand.find, coll.getName(), tojson(activeCursors));
assert.eq(cursorObject.nDocsReturned, 2, tojson(activeCursors));
- assert.eq(cursorObject.ns, coll.getFullName(), tojson(activeCursors));
assert.eq(cursorObject.tailable, false, tojson(activeCursors));
assert.eq(cursorObject.awaitData, false, tojson(activeCursors));
}
diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp
index 1c747d07998..756f1fbc7eb 100644
--- a/src/mongo/db/clientcursor.cpp
+++ b/src/mongo/db/clientcursor.cpp
@@ -91,7 +91,8 @@ ClientCursor::ClientCursor(ClientCursorParams params,
_queryOptions(params.queryOptions),
_exec(std::move(params.exec)),
_operationUsingCursor(operationUsingCursor),
- _lastUseDate(now) {
+ _lastUseDate(now),
+ _createdDate(now) {
invariant(_cursorManager);
invariant(_exec);
invariant(_operationUsingCursor);
@@ -133,12 +134,15 @@ GenericCursor ClientCursor::toGenericCursor() const {
GenericCursor gc;
gc.setCursorId(cursorid());
gc.setNs(nss());
- gc.setNDocsReturned(pos());
+ gc.setNDocsReturned(nReturnedSoFar());
gc.setTailable(isTailable());
gc.setAwaitData(isAwaitData());
gc.setNoCursorTimeout(isNoTimeout());
gc.setOriginatingCommand(getOriginatingCommandObj());
gc.setLsid(getSessionId());
+ gc.setLastAccessDate(getLastUseDate());
+ gc.setCreatedDate(getCreatedDate());
+ gc.setNBatchesReturned(getNBatches());
if (auto opCtx = _operationUsingCursor) {
gc.setOperationUsingCursorId(opCtx->getOpID());
}
diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h
index 7602af622d0..96cae17da5b 100644
--- a/src/mongo/db/clientcursor.h
+++ b/src/mongo/db/clientcursor.h
@@ -168,22 +168,45 @@ public:
/**
* Returns the total number of query results returned by the cursor so far.
*/
- long long pos() const {
- return _pos;
+ std::uint64_t nReturnedSoFar() const {
+ return _nReturnedSoFar;
}
/**
* Increments the cursor's tracked number of query results returned so far by 'n'.
*/
- void incPos(long long n) {
- _pos += n;
+ void incNReturnedSoFar(std::uint64_t n) {
+ _nReturnedSoFar += n;
}
/**
* Sets the cursor's tracked number of query results returned so far to 'n'.
*/
- void setPos(long long n) {
- _pos = n;
+ void setNReturnedSoFar(std::uint64_t n) {
+ invariant(n >= _nReturnedSoFar);
+ _nReturnedSoFar = n;
+ }
+
+ /**
+ * Returns the number of batches returned by this cursor so far.
+ */
+ std::uint64_t getNBatches() const {
+ return _nBatchesReturned;
+ }
+
+ /**
+ * Increments the number of batches returned so far by one.
+ */
+ void incNBatches() {
+ ++_nBatchesReturned;
+ }
+
+ Date_t getLastUseDate() const {
+ return _lastUseDate;
+ }
+
+ Date_t getCreatedDate() const {
+ return _createdDate;
}
/**
@@ -304,7 +327,10 @@ private:
bool _disposed = false;
// Tracks the number of results returned by this cursor so far.
- long long _pos = 0;
+ std::uint64_t _nReturnedSoFar = 0;
+
+ // Tracks the number of batches returned by this cursor so far.
+ std::uint64_t _nBatchesReturned = 0;
// Holds an owned copy of the command specification received from the client.
const BSONObj _originatingCommand;
@@ -347,6 +373,7 @@ private:
OperationContext* _operationUsingCursor;
Date_t _lastUseDate;
+ Date_t _createdDate;
};
/**
diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp
index 495395e3bc6..b08b79ab4fb 100644
--- a/src/mongo/db/commands/find_cmd.cpp
+++ b/src/mongo/db/commands/find_cmd.cpp
@@ -326,7 +326,7 @@ public:
CursorResponseBuilder firstBatch(result, options);
BSONObj obj;
PlanExecutor::ExecState state = PlanExecutor::ADVANCED;
- long long numResults = 0;
+ std::uint64_t numResults = 0;
while (!FindCommon::enoughForFirstBatch(originalQR, numResults) &&
PlanExecutor::ADVANCED == (state = exec->getNext(&obj, nullptr))) {
// If we can't fit this result inside the current batch, then we stash it for later.
@@ -384,7 +384,8 @@ public:
pinnedCursor.getCursor()->setLeftoverMaxTimeMicros(
opCtx->getRemainingMaxTimeMicros());
}
- pinnedCursor.getCursor()->setPos(numResults);
+ pinnedCursor.getCursor()->setNReturnedSoFar(numResults);
+ pinnedCursor.getCursor()->incNBatches();
// Fill out curop based on the results.
endQueryOp(opCtx, collection, *cursorExec, numResults, cursorId);
diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp
index 257e340b6b6..1292c37b3f9 100644
--- a/src/mongo/db/commands/getmore_cmd.cpp
+++ b/src/mongo/db/commands/getmore_cmd.cpp
@@ -187,7 +187,7 @@ public:
const GetMoreRequest& request,
CursorResponseBuilder* nextBatch,
PlanExecutor::ExecState* state,
- long long* numResults) {
+ std::uint64_t* numResults) {
PlanExecutor* exec = cursor->getExecutor();
// If an awaitData getMore is killed during this process due to our max time expiring at
@@ -434,7 +434,7 @@ public:
CursorResponseBuilder nextBatch(reply, CursorResponseBuilder::Options());
BSONObj obj;
PlanExecutor::ExecState state = PlanExecutor::ADVANCED;
- long long numResults = 0;
+ std::uint64_t numResults = 0;
// We report keysExamined and docsExamined to OpDebug for a given getMore operation. To
// obtain these values we need to take a diff of the pre-execution and post-execution
@@ -495,7 +495,8 @@ public:
exec->detachFromOperationContext();
cursor->setLeftoverMaxTimeMicros(opCtx->getRemainingMaxTimeMicros());
- cursor->incPos(numResults);
+ cursor->incNReturnedSoFar(numResults);
+ cursor->incNBatches();
} else {
curOp->debug().cursorExhausted = true;
}
diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp
index 34df70eccb9..361ca3220b2 100644
--- a/src/mongo/db/curop.cpp
+++ b/src/mongo/db/curop.cpp
@@ -500,7 +500,15 @@ void CurOp::reportState(BSONObjBuilder* builder, bool truncateOps) {
"truncatedObj", _genericCursor->getOriginatingCommand().get(), maxQuerySize, &tempObj);
auto originatingCommand = tempObj.done().getObjectField("truncatedObj");
_genericCursor->setOriginatingCommand(originatingCommand.getOwned());
+ // lsid and ns exist in the top level curop object, so they need to be temporarily
+ // removed from the cursor object to avoid duplicating information.
+ auto lsid = _genericCursor->getLsid();
+ auto ns = _genericCursor->getNs();
+ _genericCursor->setLsid(boost::none);
+ _genericCursor->setNs(boost::none);
builder->append("cursor", _genericCursor->toBSON());
+ _genericCursor->setLsid(lsid);
+ _genericCursor->setNs(ns);
}
if (!_message.empty()) {
diff --git a/src/mongo/db/cursor_manager.cpp b/src/mongo/db/cursor_manager.cpp
index 984dc31a099..17aa13a65fb 100644
--- a/src/mongo/db/cursor_manager.cpp
+++ b/src/mongo/db/cursor_manager.cpp
@@ -565,7 +565,7 @@ StatusWith<ClientCursorPin> CursorManager::pinCursor(OperationContext* opCtx,
cursor->_operationUsingCursor = opCtx;
- // We use pinning of a cursor as a proxy for active, user-initiated use of a cursor. Therefor,
+ // We use pinning of a cursor as a proxy for active, user-initiated use of a cursor. Therefore,
// we pass down to the logical session cache and vivify the record (updating last use).
if (cursor->getSessionId()) {
auto vivifyCursorStatus =
diff --git a/src/mongo/db/generic_cursor.idl b/src/mongo/db/generic_cursor.idl
index 09a261baac1..24661fa9221 100644
--- a/src/mongo/db/generic_cursor.idl
+++ b/src/mongo/db/generic_cursor.idl
@@ -36,10 +36,26 @@ structs:
description: The namespace of the cursor.
type: namespacestring
optional: true
+ createdDate:
+ description: The time and date the cursor was created.
+ type: date
+ optional: true
+ lastAccessDate:
+ description: The last time the cursor was used (was pinned).
+ type: date
+ optional: true
nDocsReturned:
description: The number of docs returned by the cursor.
type: long
optional: true
+ nBatchesReturned:
+ description: The number of batches returned by the cursor.
+ type: long
+ optional: true
+ noCursorTimeout:
+ description: If true the cursor will not be timed out because of inactivity.
+ type: bool
+ optional: true
tailable:
description: Whether the cursor is tailable and remains open after exhausting all documents in the find.
type: bool
@@ -48,10 +64,6 @@ structs:
description: Whether this is a tailable and awaitData cursor that will block waiting for new data to be inserted into a capped collection.
type: bool
optional: true
- noCursorTimeout:
- description: If true the cursor will not be timed out because of inactivity.
- type: bool
- optional: true
originatingCommand:
description: The bson object containing the command that created the cursor.
type: object
diff --git a/src/mongo/db/query/find.cpp b/src/mongo/db/query/find.cpp
index 6a43af3c64f..e23bb87e265 100644
--- a/src/mongo/db/query/find.cpp
+++ b/src/mongo/db/query/find.cpp
@@ -174,7 +174,7 @@ namespace {
void generateBatch(int ntoreturn,
ClientCursor* cursor,
BufBuilder* bb,
- int* numResults,
+ std::uint64_t* numResults,
PlanExecutor::ExecState* state) {
PlanExecutor* exec = cursor->getExecutor();
@@ -312,7 +312,7 @@ Message getMore(OperationContext* opCtx,
// These are set in the QueryResult msg we return.
int resultFlags = ResultFlag_AwaitCapable;
- int numResults = 0;
+ std::uint64_t numResults = 0;
int startingResult = 0;
const int InitialBufSize =
@@ -377,7 +377,7 @@ Message getMore(OperationContext* opCtx,
opCtx->checkForInterrupt(); // May trigger maxTimeAlwaysTimeOut fail point.
// What number result are we starting at? Used to fill out the reply.
- startingResult = cc->pos();
+ startingResult = cc->nReturnedSoFar();
uint64_t notifierVersion = 0;
std::shared_ptr<CappedInsertNotifier> notifier;
@@ -486,7 +486,8 @@ Message getMore(OperationContext* opCtx,
<< PlanExecutor::statestr(state);
} else {
// Continue caching the ClientCursor.
- cc->incPos(numResults);
+ cc->incNReturnedSoFar(numResults);
+ cc->incNBatches();
exec->saveState();
exec->detachFromOperationContext();
LOG(5) << "getMore saving client cursor ended with state "
@@ -685,7 +686,8 @@ std::string runQuery(OperationContext* opCtx,
curOp.debug().exhaust = true;
}
- pinnedCursor.getCursor()->setPos(numResults);
+ pinnedCursor.getCursor()->setNReturnedSoFar(numResults);
+ pinnedCursor.getCursor()->incNBatches();
// We assume that cursors created through a DBDirectClient are always used from their
// original OperationContext, so we do not need to move time to and from the cursor.
diff --git a/src/mongo/dbtests/querytests.cpp b/src/mongo/dbtests/querytests.cpp
index 9e139cce693..e97794d69fd 100644
--- a/src/mongo/dbtests/querytests.cpp
+++ b/src/mongo/dbtests/querytests.cpp
@@ -284,7 +284,7 @@ public:
dbtests::WriteContextForTests ctx(&_opCtx, ns);
auto pinnedCursor = unittest::assertGet(
ctx.getCollection()->getCursorManager()->pinCursor(&_opCtx, cursorId));
- ASSERT_EQUALS(2, pinnedCursor.getCursor()->pos());
+ ASSERT_EQUALS(std::uint64_t(2), pinnedCursor.getCursor()->nReturnedSoFar());
}
cursor = _client.getMore(ns, cursorId);
diff --git a/src/mongo/s/query/cluster_client_cursor.h b/src/mongo/s/query/cluster_client_cursor.h
index 5ad4ec4298f..8e993d7a737 100644
--- a/src/mongo/s/query/cluster_client_cursor.h
+++ b/src/mongo/s/query/cluster_client_cursor.h
@@ -160,6 +160,31 @@ public:
*/
virtual boost::optional<ReadPreferenceSetting> getReadPreference() const = 0;
+ /**
+ * Returns the creation date of the cursor.
+ */
+ virtual Date_t getCreatedDate() const = 0;
+
+ /**
+ * Returns the date the cursor was last used.
+ */
+ virtual Date_t getLastUseDate() const = 0;
+
+ /**
+ * Set the last use date to the provided time.
+ */
+ virtual void setLastUseDate(Date_t now) = 0;
+
+ /**
+ * Returns the number of batches returned by this cursor.
+ */
+ virtual std::uint64_t getNBatches() const = 0;
+
+ /**
+ * Increment the number of batches returned so far by one.
+ */
+ virtual void incNBatches() = 0;
+
//
// maxTimeMS support.
//
diff --git a/src/mongo/s/query/cluster_client_cursor_impl.cpp b/src/mongo/s/query/cluster_client_cursor_impl.cpp
index acda45f66f0..e58bbd1accc 100644
--- a/src/mongo/s/query/cluster_client_cursor_impl.cpp
+++ b/src/mongo/s/query/cluster_client_cursor_impl.cpp
@@ -79,7 +79,9 @@ ClusterClientCursorImpl::ClusterClientCursorImpl(OperationContext* opCtx,
: _params(std::move(params)),
_root(buildMergerPlan(opCtx, executor, &_params)),
_lsid(lsid),
- _opCtx(opCtx) {
+ _opCtx(opCtx),
+ _createdDate(opCtx->getServiceContext()->getPreciseClockSource()->now()),
+ _lastUseDate(_createdDate) {
dassert(!_params.compareWholeSortKey ||
SimpleBSONObjComparator::kInstance.evaluate(
_params.sort == AsyncResultsMerger::kWholeSortKeySortPattern));
@@ -89,7 +91,12 @@ ClusterClientCursorImpl::ClusterClientCursorImpl(OperationContext* opCtx,
std::unique_ptr<RouterExecStage> root,
ClusterClientCursorParams&& params,
boost::optional<LogicalSessionId> lsid)
- : _params(std::move(params)), _root(std::move(root)), _lsid(lsid), _opCtx(opCtx) {
+ : _params(std::move(params)),
+ _root(std::move(root)),
+ _lsid(lsid),
+ _opCtx(opCtx),
+ _createdDate(opCtx->getServiceContext()->getPreciseClockSource()->now()),
+ _lastUseDate(_createdDate) {
dassert(!_params.compareWholeSortKey ||
SimpleBSONObjComparator::kInstance.evaluate(
_params.sort == AsyncResultsMerger::kWholeSortKeySortPattern));
@@ -181,6 +188,26 @@ boost::optional<TxnNumber> ClusterClientCursorImpl::getTxnNumber() const {
return _params.txnNumber;
}
+Date_t ClusterClientCursorImpl::getCreatedDate() const {
+ return _createdDate;
+}
+
+Date_t ClusterClientCursorImpl::getLastUseDate() const {
+ return _lastUseDate;
+}
+
+void ClusterClientCursorImpl::setLastUseDate(Date_t now) {
+ _lastUseDate = std::move(now);
+}
+
+std::uint64_t ClusterClientCursorImpl::getNBatches() const {
+ return _nBatchesReturned;
+}
+
+void ClusterClientCursorImpl::incNBatches() {
+ ++_nBatchesReturned;
+}
+
boost::optional<ReadPreferenceSetting> ClusterClientCursorImpl::getReadPreference() const {
return _params.readPreference;
}
diff --git a/src/mongo/s/query/cluster_client_cursor_impl.h b/src/mongo/s/query/cluster_client_cursor_impl.h
index 04e97cad3d9..b1d8908eea1 100644
--- a/src/mongo/s/query/cluster_client_cursor_impl.h
+++ b/src/mongo/s/query/cluster_client_cursor_impl.h
@@ -130,6 +130,16 @@ public:
boost::optional<ReadPreferenceSetting> getReadPreference() const final;
+ Date_t getCreatedDate() const final;
+
+ Date_t getLastUseDate() const final;
+
+ void setLastUseDate(Date_t now) final;
+
+ std::uint64_t getNBatches() const final;
+
+ void incNBatches() final;
+
public:
/**
* Constructs a CCC whose result set is generated by a mock execution stage.
@@ -172,6 +182,15 @@ private:
// The OperationContext that we're executing within. This can be updated if necessary by using
// detachFromOperationContext() and reattachToOperationContext().
OperationContext* _opCtx = nullptr;
+
+ // The time the cursor was created.
+ Date_t _createdDate;
+
+ // The time when the cursor was last unpinned, i.e. the end of the last getMore.
+ Date_t _lastUseDate;
+
+ // The number of batches returned by this cursor.
+ std::uint64_t _nBatchesReturned = 0;
};
} // namespace mongo
diff --git a/src/mongo/s/query/cluster_client_cursor_mock.cpp b/src/mongo/s/query/cluster_client_cursor_mock.cpp
index 616d48d275c..6649cee9494 100644
--- a/src/mongo/s/query/cluster_client_cursor_mock.cpp
+++ b/src/mongo/s/query/cluster_client_cursor_mock.cpp
@@ -77,6 +77,26 @@ long long ClusterClientCursorMock::getNumReturnedSoFar() const {
return _numReturnedSoFar;
}
+std::uint64_t ClusterClientCursorMock::getNBatches() const {
+ return _nBatchesReturned;
+}
+
+void ClusterClientCursorMock::incNBatches() {
+ ++_nBatchesReturned;
+}
+
+Date_t ClusterClientCursorMock::getCreatedDate() const {
+ return _createdDate;
+}
+
+Date_t ClusterClientCursorMock::getLastUseDate() const {
+ return _lastUseDate;
+}
+
+void ClusterClientCursorMock::setLastUseDate(Date_t now) {
+ _lastUseDate = std::move(now);
+}
+
void ClusterClientCursorMock::kill(OperationContext* opCtx) {
_killed = true;
if (_killCallback) {
diff --git a/src/mongo/s/query/cluster_client_cursor_mock.h b/src/mongo/s/query/cluster_client_cursor_mock.h
index f5b1464b94b..6c5806cc9c2 100644
--- a/src/mongo/s/query/cluster_client_cursor_mock.h
+++ b/src/mongo/s/query/cluster_client_cursor_mock.h
@@ -84,6 +84,16 @@ public:
boost::optional<ReadPreferenceSetting> getReadPreference() const final;
+ Date_t getCreatedDate() const final;
+
+ Date_t getLastUseDate() const final;
+
+ void setLastUseDate(Date_t now) final;
+
+ std::uint64_t getNBatches() const final;
+
+ void incNBatches() final;
+
/**
* Returns true unless marked as having non-exhausted remote cursors via
* markRemotesNotExhausted().
@@ -116,6 +126,12 @@ private:
boost::optional<TxnNumber> _txnNumber;
OperationContext* _opCtx = nullptr;
+
+ Date_t _createdDate;
+
+ Date_t _lastUseDate;
+
+ std::uint64_t _nBatchesReturned = 0;
};
} // namespace mongo
diff --git a/src/mongo/s/query/cluster_cursor_manager.cpp b/src/mongo/s/query/cluster_cursor_manager.cpp
index 4964991e8bb..37ed6216dd9 100644
--- a/src/mongo/s/query/cluster_cursor_manager.cpp
+++ b/src/mongo/s/query/cluster_cursor_manager.cpp
@@ -161,6 +161,29 @@ long long ClusterCursorManager::PinnedCursor::getNumReturnedSoFar() const {
return _cursor->getNumReturnedSoFar();
}
+Date_t ClusterCursorManager::PinnedCursor::getLastUseDate() const {
+ invariant(_cursor);
+ return _cursor->getLastUseDate();
+}
+
+void ClusterCursorManager::PinnedCursor::setLastUseDate(Date_t now) {
+ invariant(_cursor);
+ _cursor->setLastUseDate(now);
+}
+Date_t ClusterCursorManager::PinnedCursor::getCreatedDate() const {
+ invariant(_cursor);
+ return _cursor->getCreatedDate();
+}
+void ClusterCursorManager::PinnedCursor::incNBatches() {
+ invariant(_cursor);
+ return _cursor->incNBatches();
+}
+
+long long ClusterCursorManager::PinnedCursor::getNBatches() const {
+ invariant(_cursor);
+ return _cursor->getNBatches();
+}
+
void ClusterCursorManager::PinnedCursor::queueResult(const ClusterQueryResult& result) {
invariant(_cursor);
_cursor->queueResult(result);
@@ -180,6 +203,9 @@ GenericCursor ClusterCursorManager::PinnedCursor::toGenericCursor() const {
gc.setTailable(isTailable());
gc.setAwaitData(isTailableAndAwaitData());
gc.setOriginatingCommand(getOriginatingCommand());
+ gc.setLastAccessDate(getLastUseDate());
+ gc.setCreatedDate(getCreatedDate());
+ gc.setNBatchesReturned(getNBatches());
return gc;
}
@@ -342,7 +368,6 @@ StatusWith<ClusterCursorManager::PinnedCursor> ClusterCursorManager::checkOutCur
return vivifyCursorStatus;
}
}
-
cursor->reattachToOperationContext(opCtx);
return PinnedCursor(this, std::move(cursor), nss, cursorId);
}
@@ -359,6 +384,7 @@ void ClusterCursorManager::checkInCursor(std::unique_ptr<ClusterClientCursor> cu
OperationContext* opCtx = cursor->getCurrentOperationContext();
invariant(opCtx);
cursor->detachFromOperationContext();
+ cursor->setLastUseDate(now);
stdx::unique_lock<stdx::mutex> lk(_mutex);
@@ -590,12 +616,15 @@ GenericCursor ClusterCursorManager::CursorEntry::cursorToGenericCursor(
GenericCursor gc;
gc.setCursorId(cursorId);
gc.setNs(ns);
+ gc.setCreatedDate(_cursor->getCreatedDate());
+ gc.setLastAccessDate(_cursor->getLastUseDate());
gc.setLsid(_cursor->getLsid());
gc.setNDocsReturned(_cursor->getNumReturnedSoFar());
gc.setTailable(_cursor->isTailable());
gc.setAwaitData(_cursor->isTailableAndAwaitData());
gc.setOriginatingCommand(_cursor->getOriginatingCommand());
gc.setNoCursorTimeout(getLifetimeType() == CursorLifetime::Immortal);
+ gc.setNBatchesReturned(_cursor->getNBatches());
return gc;
}
diff --git a/src/mongo/s/query/cluster_cursor_manager.h b/src/mongo/s/query/cluster_cursor_manager.h
index 6c1b0d10c92..f74e8a4ba97 100644
--- a/src/mongo/s/query/cluster_cursor_manager.h
+++ b/src/mongo/s/query/cluster_cursor_manager.h
@@ -213,6 +213,31 @@ public:
long long getNumReturnedSoFar() const;
/**
+ * Returns the creation date of the cursor.
+ */
+ Date_t getCreatedDate() const;
+
+ /**
+ * Returns the time the cursor was last used.
+ */
+ Date_t getLastUseDate() const;
+
+ /**
+ * Set the cursor's lastUseDate to the given time.
+ */
+ void setLastUseDate(Date_t now);
+
+ /**
+ * Increment the number of batches returned by this cursor.
+ */
+ void incNBatches();
+
+ /**
+ * Get the number of batches returned by this cursor.
+ */
+ long long getNBatches() const;
+
+ /**
* Returns a GenericCursor version of the pinned cursor.
*/
GenericCursor toGenericCursor() const;
diff --git a/src/mongo/s/query/cluster_find.cpp b/src/mongo/s/query/cluster_find.cpp
index a805ad35078..50f99690d84 100644
--- a/src/mongo/s/query/cluster_find.cpp
+++ b/src/mongo/s/query/cluster_find.cpp
@@ -361,6 +361,7 @@ CursorId runQueryWithoutRetrying(OperationContext* opCtx,
? ClusterCursorManager::CursorLifetime::Immortal
: ClusterCursorManager::CursorLifetime::Mortal;
auto authUsers = AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames();
+ ccc->incNBatches();
auto cursorId = uassertStatusOK(cursorManager->registerCursor(
opCtx, ccc.releaseCursor(), query.nss(), cursorType, cursorLifetime, authUsers));
@@ -646,6 +647,7 @@ StatusWith<CursorResponse> ClusterFind::runGetMore(OperationContext* opCtx,
}
pinnedCursor.getValue().setLeftoverMaxTimeMicros(opCtx->getRemainingMaxTimeMicros());
+ pinnedCursor.getValue().incNBatches();
// Upon successful completion, transfer ownership of the cursor back to the cursor manager. If
// the cursor has been exhausted, the cursor manager will clean it up for us.
pinnedCursor.getValue().returnCursor(cursorState);