diff options
-rw-r--r-- | jstests/core/currentop_cursors.js | 188 | ||||
-rw-r--r-- | jstests/noPassthrough/currentop_active_cursor.js | 1 | ||||
-rw-r--r-- | src/mongo/db/clientcursor.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/clientcursor.h | 41 | ||||
-rw-r--r-- | src/mongo/db/commands/find_cmd.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/getmore_cmd.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/curop.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/cursor_manager.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/generic_cursor.idl | 20 | ||||
-rw-r--r-- | src/mongo/db/query/find.cpp | 12 | ||||
-rw-r--r-- | src/mongo/dbtests/querytests.cpp | 2 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_client_cursor.h | 25 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_client_cursor_impl.cpp | 31 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_client_cursor_impl.h | 19 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_client_cursor_mock.cpp | 20 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_client_cursor_mock.h | 16 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_cursor_manager.cpp | 31 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_cursor_manager.h | 25 | ||||
-rw-r--r-- | src/mongo/s/query/cluster_find.cpp | 2 |
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); |