summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Storch <david.storch@10gen.com>2015-06-22 12:25:10 -0400
committerDavid Storch <david.storch@10gen.com>2015-06-23 14:16:18 -0400
commitb8f0f273d8f2db1aed1974b94c9b18a63656ad48 (patch)
treeef86771a005eed7dc7d409857f187aba764a0a02
parentedfeb6a9f7a5f30a951c673374e9e058b1886a10 (diff)
downloadmongo-b8f0f273d8f2db1aed1974b94c9b18a63656ad48.tar.gz
SERVER-5811 implement the killCursors command for mongod
Implementing the killCursors command in mongos and the mongo shell is further work.
-rw-r--r--jstests/auth/lib/commands_lib.js40
-rw-r--r--src/mongo/db/SConscript1
-rw-r--r--src/mongo/db/auth/authorization_session.cpp12
-rw-r--r--src/mongo/db/catalog/cursor_manager.cpp37
-rw-r--r--src/mongo/db/catalog/cursor_manager.h10
-rw-r--r--src/mongo/db/commands/getmore_cmd.cpp12
-rw-r--r--src/mongo/db/commands/killcursors_cmd.cpp171
-rw-r--r--src/mongo/db/commands/list_collections.cpp2
-rw-r--r--src/mongo/db/commands/list_indexes.cpp4
-rw-r--r--src/mongo/db/namespace_string.cpp16
-rw-r--r--src/mongo/db/namespace_string.h10
-rw-r--r--src/mongo/db/namespace_string_test.cpp50
-rw-r--r--src/mongo/db/query/SConscript5
-rw-r--r--src/mongo/db/query/find.cpp18
-rw-r--r--src/mongo/db/query/killcursors_request.cpp105
-rw-r--r--src/mongo/db/query/killcursors_request.h55
-rw-r--r--src/mongo/db/query/killcursors_request_test.cpp138
-rw-r--r--src/mongo/db/query/killcursors_response.cpp137
-rw-r--r--src/mongo/db/query/killcursors_response.h63
-rw-r--r--src/mongo/db/query/killcursors_response_test.cpp124
-rw-r--r--src/mongo/dbtests/querytests.cpp2
21 files changed, 942 insertions, 70 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js
index 3fe3fddfeef..7189ba346f9 100644
--- a/jstests/auth/lib/commands_lib.js
+++ b/jstests/auth/lib/commands_lib.js
@@ -1478,6 +1478,46 @@ var authCommandsLib = {
]
},
{
+ testname: "killCursors",
+ command: {killCursors: "foo", cursors: [NumberLong("123")]},
+ skipSharded: true, // TODO enable when killCursors command is implemented on mongos
+ testcases: [
+ {
+ runOnDb: firstDbName,
+ roles: {
+ read: 1,
+ readAnyDatabase: 1,
+ readWrite: 1,
+ readWriteAnyDatabase: 1,
+ dbOwner: 1,
+ hostManager: 1,
+ clusterAdmin: 1,
+ root: 1,
+ __system: 1
+ },
+ privileges: [
+ { resource: {db: firstDbName, collection: "foo"}, actions: ["killCursors"] }
+ ],
+ expectFail: true
+ },
+ {
+ runOnDb: secondDbName,
+ roles: {
+ readAnyDatabase: 1,
+ readWriteAnyDatabase: 1,
+ hostManager: 1,
+ clusterAdmin: 1,
+ root: 1,
+ __system: 1
+ },
+ privileges: [
+ { resource: {db: secondDbName, collection: "foo"}, actions: ["killCursors"] }
+ ],
+ expectFail: true
+ }
+ ]
+ },
+ {
testname: "killOp", // standalone version
command: {killOp: 1, op: 123},
skipSharded: true,
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index 85eecce432a..d3e5ea90e7a 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -532,6 +532,7 @@ serverOnlyFiles = [
"commands/group.cpp",
"commands/index_filter_commands.cpp",
"commands/kill_op.cpp",
+ "commands/killcursors_cmd.cpp",
"commands/list_collections.cpp",
"commands/list_databases.cpp",
"commands/list_indexes.cpp",
diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp
index 22bd1b1f191..a193655b242 100644
--- a/src/mongo/db/auth/authorization_session.cpp
+++ b/src/mongo/db/auth/authorization_session.cpp
@@ -192,7 +192,7 @@ Status AuthorizationSession::checkAuthForQuery(const NamespaceString& ns, const
Status AuthorizationSession::checkAuthForGetMore(const NamespaceString& ns, long long cursorID) {
// "ns" can be in one of three formats: "listCollections" format, "listIndexes" format, and
// normal format.
- if (ns.isListCollectionsGetMore()) {
+ if (ns.isListCollectionsCursorNS()) {
// "ns" is of the form "<db>.$cmd.listCollections". Check if we can perform the
// listCollections action on the database resource for "<db>".
if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(ns.db()),
@@ -201,10 +201,10 @@ Status AuthorizationSession::checkAuthForGetMore(const NamespaceString& ns, long
str::stream() << "not authorized for listCollections getMore on "
<< ns.ns());
}
- } else if (ns.isListIndexesGetMore()) {
+ } else if (ns.isListIndexesCursorNS()) {
// "ns" is of the form "<db>.$cmd.listIndexes.<coll>". Check if we can perform the
// listIndexes action on the "<db>.<coll>" namespace.
- NamespaceString targetNS = ns.getTargetNSForListIndexesGetMore();
+ NamespaceString targetNS = ns.getTargetNSForListIndexes();
if (!isAuthorizedForActionsOnNamespace(targetNS, ActionType::listIndexes)) {
return Status(ErrorCodes::Unauthorized,
str::stream() << "not authorized for listIndexes getMore on " << ns.ns());
@@ -275,15 +275,15 @@ Status AuthorizationSession::checkAuthForDelete(const NamespaceString& ns, const
Status AuthorizationSession::checkAuthForKillCursors(const NamespaceString& ns,
long long cursorID) {
// See implementation comments in checkAuthForGetMore(). This method looks very similar.
- if (ns.isListCollectionsGetMore()) {
+ if (ns.isListCollectionsCursorNS()) {
if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(ns.db()),
ActionType::killCursors)) {
return Status(ErrorCodes::Unauthorized,
str::stream() << "not authorized to kill listCollections cursor on "
<< ns.ns());
}
- } else if (ns.isListIndexesGetMore()) {
- NamespaceString targetNS = ns.getTargetNSForListIndexesGetMore();
+ } else if (ns.isListIndexesCursorNS()) {
+ NamespaceString targetNS = ns.getTargetNSForListIndexes();
if (!isAuthorizedForActionsOnNamespace(targetNS, ActionType::killCursors)) {
return Status(ErrorCodes::Unauthorized,
str::stream() << "not authorized to kill listIndexes cursor on "
diff --git a/src/mongo/db/catalog/cursor_manager.cpp b/src/mongo/db/catalog/cursor_manager.cpp
index 97426228ca9..4adae4eb821 100644
--- a/src/mongo/db/catalog/cursor_manager.cpp
+++ b/src/mongo/db/catalog/cursor_manager.cpp
@@ -213,7 +213,12 @@ bool GlobalCursorIdCache::eraseCursor(OperationContext* txn, CursorId id, bool c
// If this cursor is owned by the global cursor manager, ask it to erase the cursor for us.
if (globalCursorManager->ownsCursorId(id)) {
- return globalCursorManager->eraseCursor(txn, id, checkAuth);
+ Status eraseStatus = globalCursorManager->eraseCursor(txn, id, checkAuth);
+ massert(28697,
+ eraseStatus.reason(),
+ eraseStatus.code() == ErrorCodes::OK ||
+ eraseStatus.code() == ErrorCodes::CursorNotFound);
+ return eraseStatus.isOK();
}
// If not, then the cursor must be owned by a collection. Erase the cursor under the
@@ -225,7 +230,13 @@ bool GlobalCursorIdCache::eraseCursor(OperationContext* txn, CursorId id, bool c
audit::logKillCursorsAuthzCheck(txn->getClient(), nss, id, ErrorCodes::CursorNotFound);
return false;
}
- return collection->getCursorManager()->eraseCursor(txn, id, checkAuth);
+
+ Status eraseStatus = collection->getCursorManager()->eraseCursor(txn, id, checkAuth);
+ massert(16089,
+ eraseStatus.reason(),
+ eraseStatus.code() == ErrorCodes::OK ||
+ eraseStatus.code() == ErrorCodes::CursorNotFound);
+ return eraseStatus.isOK();
}
std::size_t GlobalCursorIdCache::timeoutCursors(OperationContext* txn, int millisSinceLastCall) {
@@ -495,27 +506,35 @@ void CursorManager::deregisterCursor(ClientCursor* cc) {
_deregisterCursor_inlock(cc);
}
-bool CursorManager::eraseCursor(OperationContext* txn, CursorId id, bool checkAuth) {
+Status CursorManager::eraseCursor(OperationContext* txn, CursorId id, bool shouldAudit) {
stdx::lock_guard<SimpleMutex> lk(_mutex);
CursorMap::iterator it = _cursors.find(id);
if (it == _cursors.end()) {
- if (checkAuth)
+ if (shouldAudit) {
audit::logKillCursorsAuthzCheck(txn->getClient(), _nss, id, ErrorCodes::CursorNotFound);
- return false;
+ }
+ return {ErrorCodes::CursorNotFound, str::stream() << "Cursor id not found: " << id};
}
ClientCursor* cursor = it->second;
- if (checkAuth)
- audit::logKillCursorsAuthzCheck(txn->getClient(), _nss, id, ErrorCodes::OK);
+ if (cursor->isPinned()) {
+ if (shouldAudit) {
+ audit::logKillCursorsAuthzCheck(
+ txn->getClient(), _nss, id, ErrorCodes::OperationFailed);
+ }
+ return {ErrorCodes::OperationFailed, str::stream() << "Cannot kill pinned cursor: " << id};
+ }
- massert(16089, str::stream() << "Cannot kill active cursor " << id, !cursor->isPinned());
+ if (shouldAudit) {
+ audit::logKillCursorsAuthzCheck(txn->getClient(), _nss, id, ErrorCodes::OK);
+ }
cursor->kill();
_deregisterCursor_inlock(cursor);
delete cursor;
- return true;
+ return Status::OK();
}
void CursorManager::_deregisterCursor_inlock(ClientCursor* cc) {
diff --git a/src/mongo/db/catalog/cursor_manager.h b/src/mongo/db/catalog/cursor_manager.h
index 81b9dd0107c..54d2a02374a 100644
--- a/src/mongo/db/catalog/cursor_manager.h
+++ b/src/mongo/db/catalog/cursor_manager.h
@@ -97,7 +97,15 @@ public:
CursorId registerCursor(ClientCursor* cc);
void deregisterCursor(ClientCursor* cc);
- bool eraseCursor(OperationContext* txn, CursorId id, bool checkAuth);
+ /**
+ * Returns an OK status if the cursor was successfully erased.
+ *
+ * Returns error code CursorNotFound if the cursor id is not owned by this manager. Returns
+ * error code OperationFailed if attempting to erase a pinned cursor.
+ *
+ * If 'shouldAudit' is true, will perform audit logging.
+ */
+ Status eraseCursor(OperationContext* txn, CursorId id, bool shouldAudit);
/**
* Returns true if the space of cursor ids that cursor manager is responsible for includes
diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp
index 23805bf1123..c5f32467c00 100644
--- a/src/mongo/db/commands/getmore_cmd.cpp
+++ b/src/mongo/db/commands/getmore_cmd.cpp
@@ -52,6 +52,7 @@
#include "mongo/db/repl/oplog.h"
#include "mongo/db/service_context.h"
#include "mongo/db/stats/counters.h"
+#include "mongo/stdx/memory.h"
#include "mongo/util/log.h"
#include "mongo/util/scopeguard.h"
@@ -144,7 +145,9 @@ public:
//
// 1) Normal cursor: we lock with "ctx" and hold it for the whole getMore.
// 2) Cursor owned by global cursor manager: we don't lock anything. These cursors
- // don't own any collection state.
+ // don't own any collection state. These cursors are generated either by the
+ // listCollections or listIndexes commands, as these special cursor-generating commands
+ // operate over catalog data rather than targeting the data within a collection.
// 3) Agg cursor: we lock with "ctx", then release, then relock with "unpinDBLock" and
// "unpinCollLock". This is because agg cursors handle locking internally (hence the
// release), but the pin and unpin of the cursor must occur under the collection
@@ -160,11 +163,10 @@ public:
std::unique_ptr<Lock::CollectionLock> unpinCollLock;
CursorManager* cursorManager;
- CursorManager* globalCursorManager = CursorManager::getGlobalCursorManager();
- if (globalCursorManager->ownsCursorId(request.cursorid)) {
- cursorManager = globalCursorManager;
+ if (request.nss.isListIndexesCursorNS() || request.nss.isListCollectionsCursorNS()) {
+ cursorManager = CursorManager::getGlobalCursorManager();
} else {
- ctx.reset(new AutoGetCollectionForRead(txn, request.nss));
+ ctx = stdx::make_unique<AutoGetCollectionForRead>(txn, request.nss);
Collection* collection = ctx->getCollection();
if (!collection) {
return appendCommandStatus(result,
diff --git a/src/mongo/db/commands/killcursors_cmd.cpp b/src/mongo/db/commands/killcursors_cmd.cpp
new file mode 100644
index 00000000000..27b978ca15c
--- /dev/null
+++ b/src/mongo/db/commands/killcursors_cmd.cpp
@@ -0,0 +1,171 @@
+/**
+ * Copyright (C) 2014 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
+
+#include "mongo/platform/basic.h"
+
+#include <memory>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/db/audit.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/catalog/collection.h"
+#include "mongo/db/catalog/cursor_manager.h"
+#include "mongo/db/client.h"
+#include "mongo/db/clientcursor.h"
+#include "mongo/db/commands.h"
+#include "mongo/db/db_raii.h"
+#include "mongo/db/query/killcursors_request.h"
+#include "mongo/db/query/killcursors_response.h"
+#include "mongo/stdx/memory.h"
+
+namespace mongo {
+
+/**
+ * Attempt to kill a list of cursor ids. The ClientCursors will be removed from the CursorManager
+ * and destroyed.
+ */
+class KillCursorsCmd : public Command {
+ MONGO_DISALLOW_COPYING(KillCursorsCmd);
+
+public:
+ KillCursorsCmd() : Command("killCursors") {}
+
+ bool isWriteCommandForConfigServer() const override {
+ return false;
+ }
+
+ bool slaveOk() const override {
+ return false;
+ }
+
+ bool slaveOverrideOk() const override {
+ return true;
+ }
+
+ bool maintenanceOk() const override {
+ return false;
+ }
+
+ bool adminOnly() const override {
+ return false;
+ }
+
+ void help(std::stringstream& help) const override {
+ help << "kill a list of cursor ids";
+ }
+
+ bool shouldAffectCommandCounter() const override {
+ return true;
+ }
+
+ Status checkAuthForCommand(ClientBasic* client,
+ const std::string& dbname,
+ const BSONObj& cmdObj) override {
+ auto statusWithRequest = KillCursorsRequest::parseFromBSON(dbname, cmdObj);
+ if (!statusWithRequest.isOK()) {
+ return statusWithRequest.getStatus();
+ }
+ auto killCursorsRequest = std::move(statusWithRequest.getValue());
+
+ AuthorizationSession* as = AuthorizationSession::get(client);
+
+ for (CursorId id : killCursorsRequest.cursorIds) {
+ Status authorizationStatus = as->checkAuthForKillCursors(killCursorsRequest.nss, id);
+
+ if (!authorizationStatus.isOK()) {
+ audit::logKillCursorsAuthzCheck(
+ client, killCursorsRequest.nss, id, ErrorCodes::Unauthorized);
+ return authorizationStatus;
+ }
+ }
+
+ return Status::OK();
+ }
+
+ bool run(OperationContext* txn,
+ const std::string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ std::string& errmsg,
+ BSONObjBuilder& result) override {
+ auto statusWithRequest = KillCursorsRequest::parseFromBSON(dbname, cmdObj);
+ if (!statusWithRequest.isOK()) {
+ return appendCommandStatus(result, statusWithRequest.getStatus());
+ }
+ auto killCursorsRequest = std::move(statusWithRequest.getValue());
+
+ std::unique_ptr<AutoGetCollectionForRead> ctx;
+
+ CursorManager* cursorManager;
+ if (killCursorsRequest.nss.isListIndexesCursorNS() ||
+ killCursorsRequest.nss.isListCollectionsCursorNS()) {
+ // listCollections and listIndexes are special cursor-generating commands whose cursors
+ // are managed globally, as they operate over catalog data rather than targeting the
+ // data within a collection.
+ cursorManager = CursorManager::getGlobalCursorManager();
+ } else {
+ ctx = stdx::make_unique<AutoGetCollectionForRead>(txn, killCursorsRequest.nss);
+ Collection* collection = ctx->getCollection();
+ if (!collection) {
+ return appendCommandStatus(result,
+ {ErrorCodes::InvalidNamespace,
+ str::stream() << "collection does not exist: "
+ << killCursorsRequest.nss.ns()});
+ }
+ cursorManager = collection->getCursorManager();
+ }
+ invariant(cursorManager);
+
+ std::vector<CursorId> cursorsKilled;
+ std::vector<CursorId> cursorsNotFound;
+ std::vector<CursorId> cursorsAlive;
+
+ for (CursorId id : killCursorsRequest.cursorIds) {
+ Status status = cursorManager->eraseCursor(txn, id, true /*shouldAudit*/);
+ if (status.isOK()) {
+ cursorsKilled.push_back(id);
+ } else if (status.code() == ErrorCodes::CursorNotFound) {
+ cursorsNotFound.push_back(id);
+ } else {
+ cursorsAlive.push_back(id);
+ }
+
+ audit::logKillCursorsAuthzCheck(
+ txn->getClient(), killCursorsRequest.nss, id, status.code());
+ }
+
+ KillCursorsResponse killCursorsResponse(cursorsKilled, cursorsNotFound, cursorsAlive);
+ killCursorsResponse.addToBSON(&result);
+ return true;
+ }
+
+} killCursorsCmd;
+
+} // namespace mongo
diff --git a/src/mongo/db/commands/list_collections.cpp b/src/mongo/db/commands/list_collections.cpp
index b48e98598ac..ac871b0f078 100644
--- a/src/mongo/db/commands/list_collections.cpp
+++ b/src/mongo/db/commands/list_collections.cpp
@@ -167,7 +167,7 @@ public:
std::string cursorNamespace = str::stream() << dbname << ".$cmd." << name;
dassert(NamespaceString(cursorNamespace).isValid());
- dassert(NamespaceString(cursorNamespace).isListCollectionsGetMore());
+ dassert(NamespaceString(cursorNamespace).isListCollectionsCursorNS());
PlanExecutor* rawExec;
Status makeStatus = PlanExecutor::make(txn,
diff --git a/src/mongo/db/commands/list_indexes.cpp b/src/mongo/db/commands/list_indexes.cpp
index ce0394156e3..9b84c27d6d8 100644
--- a/src/mongo/db/commands/list_indexes.cpp
+++ b/src/mongo/db/commands/list_indexes.cpp
@@ -164,8 +164,8 @@ public:
std::string cursorNamespace = str::stream() << dbname << ".$cmd." << name << "."
<< ns.coll();
dassert(NamespaceString(cursorNamespace).isValid());
- dassert(NamespaceString(cursorNamespace).isListIndexesGetMore());
- dassert(ns == NamespaceString(cursorNamespace).getTargetNSForListIndexesGetMore());
+ dassert(NamespaceString(cursorNamespace).isListIndexesCursorNS());
+ dassert(ns == NamespaceString(cursorNamespace).getTargetNSForListIndexes());
PlanExecutor* rawExec;
Status makeStatus = PlanExecutor::make(txn,
diff --git a/src/mongo/db/namespace_string.cpp b/src/mongo/db/namespace_string.cpp
index f8c0b6751bb..afe30a3a977 100644
--- a/src/mongo/db/namespace_string.cpp
+++ b/src/mongo/db/namespace_string.cpp
@@ -83,22 +83,22 @@ bool legalClientSystemNS(StringData ns, bool write) {
return false;
}
-bool NamespaceString::isListCollectionsGetMore() const {
+bool NamespaceString::isListCollectionsCursorNS() const {
return coll() == StringData("$cmd.listCollections", StringData::LiteralTag());
}
namespace {
-const StringData listIndexesGetMoreNSPrefix("$cmd.listIndexes.", StringData::LiteralTag());
+const StringData listIndexesCursorNSPrefix("$cmd.listIndexes.", StringData::LiteralTag());
} // namespace
-bool NamespaceString::isListIndexesGetMore() const {
- return coll().size() > listIndexesGetMoreNSPrefix.size() &&
- coll().startsWith(listIndexesGetMoreNSPrefix);
+bool NamespaceString::isListIndexesCursorNS() const {
+ return coll().size() > listIndexesCursorNSPrefix.size() &&
+ coll().startsWith(listIndexesCursorNSPrefix);
}
-NamespaceString NamespaceString::getTargetNSForListIndexesGetMore() const {
- dassert(isListIndexesGetMore());
- return NamespaceString(db(), coll().substr(listIndexesGetMoreNSPrefix.size()));
+NamespaceString NamespaceString::getTargetNSForListIndexes() const {
+ dassert(isListIndexesCursorNS());
+ return NamespaceString(db(), coll().substr(listIndexesCursorNSPrefix.size()));
}
string NamespaceString::escapeDbName(const StringData dbname) {
diff --git a/src/mongo/db/namespace_string.h b/src/mongo/db/namespace_string.h
index 881b9e1d468..9a7e54500cf 100644
--- a/src/mongo/db/namespace_string.h
+++ b/src/mongo/db/namespace_string.h
@@ -139,14 +139,14 @@ public:
bool isNormal() const {
return normal(_ns);
}
- bool isListCollectionsGetMore() const;
- bool isListIndexesGetMore() const;
+ bool isListCollectionsCursorNS() const;
+ bool isListIndexesCursorNS() const;
/**
- * Given a NamespaceString for which isListIndexesGetMore() returns true, returns the
- * NamespaceString for the collection that the "listIndexesGetMore" targets.
+ * Given a NamespaceString for which isListIndexesCursorNS() returns true, returns the
+ * NamespaceString for the collection that the "listIndexes" targets.
*/
- NamespaceString getTargetNSForListIndexesGetMore() const;
+ NamespaceString getTargetNSForListIndexes() const;
/**
* @return true if the namespace is valid. Special namespaces for internal use are considered as
diff --git a/src/mongo/db/namespace_string_test.cpp b/src/mongo/db/namespace_string_test.cpp
index d1b3c7b4420..ba85cc775e3 100644
--- a/src/mongo/db/namespace_string_test.cpp
+++ b/src/mongo/db/namespace_string_test.cpp
@@ -88,39 +88,39 @@ TEST(NamespaceStringTest, DatabaseValidNames) {
ASSERT(NamespaceString::normal("local.oplog.$main"));
}
-TEST(NamespaceStringTest, ListCollectionsGetMore) {
- ASSERT(NamespaceString("test.$cmd.listCollections").isListCollectionsGetMore());
+TEST(NamespaceStringTest, ListCollectionsCursorNS) {
+ ASSERT(NamespaceString("test.$cmd.listCollections").isListCollectionsCursorNS());
- ASSERT(!NamespaceString("test.foo").isListCollectionsGetMore());
- ASSERT(!NamespaceString("test.foo.$cmd.listCollections").isListCollectionsGetMore());
- ASSERT(!NamespaceString("test.$cmd.").isListCollectionsGetMore());
- ASSERT(!NamespaceString("test.$cmd.foo.").isListCollectionsGetMore());
- ASSERT(!NamespaceString("test.$cmd.listCollections.").isListCollectionsGetMore());
- ASSERT(!NamespaceString("test.$cmd.listIndexes").isListCollectionsGetMore());
- ASSERT(!NamespaceString("test.$cmd.listIndexes.foo").isListCollectionsGetMore());
+ ASSERT(!NamespaceString("test.foo").isListCollectionsCursorNS());
+ ASSERT(!NamespaceString("test.foo.$cmd.listCollections").isListCollectionsCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.").isListCollectionsCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.foo.").isListCollectionsCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.listCollections.").isListCollectionsCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.listIndexes").isListCollectionsCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.listIndexes.foo").isListCollectionsCursorNS());
}
-TEST(NamespaceStringTest, ListIndexesGetMore) {
+TEST(NamespaceStringTest, ListIndexesCursorNS) {
NamespaceString ns1("test.$cmd.listIndexes.f");
- ASSERT(ns1.isListIndexesGetMore());
- ASSERT("test.f" == ns1.getTargetNSForListIndexesGetMore().ns());
+ ASSERT(ns1.isListIndexesCursorNS());
+ ASSERT("test.f" == ns1.getTargetNSForListIndexes().ns());
NamespaceString ns2("test.$cmd.listIndexes.foo");
- ASSERT(ns2.isListIndexesGetMore());
- ASSERT("test.foo" == ns2.getTargetNSForListIndexesGetMore().ns());
+ ASSERT(ns2.isListIndexesCursorNS());
+ ASSERT("test.foo" == ns2.getTargetNSForListIndexes().ns());
NamespaceString ns3("test.$cmd.listIndexes.foo.bar");
- ASSERT(ns3.isListIndexesGetMore());
- ASSERT("test.foo.bar" == ns3.getTargetNSForListIndexesGetMore().ns());
-
- ASSERT(!NamespaceString("test.foo").isListIndexesGetMore());
- ASSERT(!NamespaceString("test.foo.$cmd.listIndexes").isListIndexesGetMore());
- ASSERT(!NamespaceString("test.$cmd.").isListIndexesGetMore());
- ASSERT(!NamespaceString("test.$cmd.foo.").isListIndexesGetMore());
- ASSERT(!NamespaceString("test.$cmd.listIndexes").isListIndexesGetMore());
- ASSERT(!NamespaceString("test.$cmd.listIndexes.").isListIndexesGetMore());
- ASSERT(!NamespaceString("test.$cmd.listCollections").isListIndexesGetMore());
- ASSERT(!NamespaceString("test.$cmd.listCollections.foo").isListIndexesGetMore());
+ ASSERT(ns3.isListIndexesCursorNS());
+ ASSERT("test.foo.bar" == ns3.getTargetNSForListIndexes().ns());
+
+ ASSERT(!NamespaceString("test.foo").isListIndexesCursorNS());
+ ASSERT(!NamespaceString("test.foo.$cmd.listIndexes").isListIndexesCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.").isListIndexesCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.foo.").isListIndexesCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.listIndexes").isListIndexesCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.listIndexes.").isListIndexesCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.listCollections").isListIndexesCursorNS());
+ ASSERT(!NamespaceString("test.$cmd.listCollections.foo").isListIndexesCursorNS());
}
TEST(NamespaceStringTest, CollectionComponentValidNames) {
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index bb209a8ae47..e899da3961b 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -105,11 +105,14 @@ env.Library(
'cursor_responses.cpp',
'find_and_modify_request.cpp',
'getmore_request.cpp',
+ 'killcursors_request.cpp',
+ 'killcursors_response.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/bson/bson',
'$BUILD_DIR/mongo/db/common',
'$BUILD_DIR/mongo/db/namespace_string',
+ '$BUILD_DIR/mongo/rpc/command_status',
]
)
@@ -119,6 +122,8 @@ env.CppUnitTest(
'count_request_test.cpp',
'find_and_modify_request_test.cpp',
'getmore_request_test.cpp',
+ 'killcursors_request_test.cpp',
+ 'killcursors_response_test.cpp',
],
LIBDEPS=[
'command_request_response',
diff --git a/src/mongo/db/query/find.cpp b/src/mongo/db/query/find.cpp
index 2283bc66e9f..a142377bab7 100644
--- a/src/mongo/db/query/find.cpp
+++ b/src/mongo/db/query/find.cpp
@@ -32,7 +32,6 @@
#include "mongo/db/query/find.h"
-
#include "mongo/client/dbclientinterface.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/db/catalog/database_holder.h"
@@ -57,6 +56,7 @@
#include "mongo/s/chunk_version.h"
#include "mongo/s/d_state.h"
#include "mongo/s/stale_exception.h"
+#include "mongo/stdx/memory.h"
#include "mongo/util/fail_point_service.h"
#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
@@ -266,8 +266,10 @@ QueryResult::View getMore(OperationContext* txn,
// or none of the getMore, or part of the getMore. The three cases in detail:
//
// 1) Normal cursor: we lock with "ctx" and hold it for the whole getMore.
- // 2) Cursor owned by global cursor manager: we don't lock anything. These cursors don't
- // own any collection state.
+ // 2) Cursor owned by global cursor manager: we don't lock anything. These cursors don't own
+ // any collection state. These cursors are generated either by the listCollections or
+ // listIndexes commands, as these special cursor-generating commands operate over catalog
+ // data rather than targeting the data within a collection.
// 3) Agg cursor: we lock with "ctx", then release, then relock with "unpinDBLock" and
// "unpinCollLock". This is because agg cursors handle locking internally (hence the
// release), but the pin and unpin of the cursor must occur under the collection lock.
@@ -283,11 +285,13 @@ QueryResult::View getMore(OperationContext* txn,
unique_ptr<Lock::CollectionLock> unpinCollLock;
CursorManager* cursorManager;
- CursorManager* globalCursorManager = CursorManager::getGlobalCursorManager();
- if (globalCursorManager->ownsCursorId(cursorid)) {
- cursorManager = globalCursorManager;
+ if (nss.isListIndexesCursorNS() || nss.isListCollectionsCursorNS()) {
+ // List collections and list indexes are special cursor-generating commands whose
+ // cursors are managed globally, as they operate over catalog data rather than targeting
+ // the data within a collection.
+ cursorManager = CursorManager::getGlobalCursorManager();
} else {
- ctx.reset(new AutoGetCollectionForRead(txn, nss));
+ ctx = stdx::make_unique<AutoGetCollectionForRead>(txn, nss);
Collection* collection = ctx->getCollection();
uassert(17356, "collection dropped between getMore calls", collection);
cursorManager = collection->getCursorManager();
diff --git a/src/mongo/db/query/killcursors_request.cpp b/src/mongo/db/query/killcursors_request.cpp
new file mode 100644
index 00000000000..6d95311accd
--- /dev/null
+++ b/src/mongo/db/query/killcursors_request.cpp
@@ -0,0 +1,105 @@
+/**
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/query/killcursors_request.h"
+
+namespace mongo {
+
+namespace {
+
+const char kCmdName[] = "killCursors";
+const char kCursorsField[] = "cursors";
+
+} // namespace
+
+KillCursorsRequest::KillCursorsRequest(const NamespaceString& nsString,
+ const std::vector<CursorId>& ids)
+ : nss(nsString), cursorIds(ids) {}
+
+StatusWith<KillCursorsRequest> KillCursorsRequest::parseFromBSON(const std::string& dbname,
+ const BSONObj& cmdObj) {
+ if (!str::equals(cmdObj.firstElement().fieldName(), kCmdName)) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "First field name must be '" << kCmdName << "' in: " << cmdObj};
+ }
+
+ if (cmdObj.firstElement().type() != BSONType::String) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "First parameter must be a string in: " << cmdObj};
+ }
+
+ std::string collName = cmdObj.firstElement().String();
+ const NamespaceString nss(dbname, collName);
+ if (!nss.isValid()) {
+ return {ErrorCodes::InvalidNamespace,
+ str::stream() << "Invalid collection name: " << nss.ns()};
+ }
+
+ if (cmdObj[kCursorsField].type() != BSONType::Array) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "Field '" << kCursorsField
+ << "' must be of type array in: " << cmdObj};
+ }
+
+ std::vector<CursorId> cursorIds;
+ for (BSONElement cursorEl : cmdObj[kCursorsField].Obj()) {
+ if (cursorEl.type() != BSONType::NumberLong) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "Field '" << kCursorsField
+ << "' contains an element that is not of type long: "
+ << cursorEl};
+ }
+ cursorIds.push_back(cursorEl.numberLong());
+ }
+
+ if (cursorIds.empty()) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "Must specify at least one cursor id in: " << cmdObj};
+ }
+
+ return KillCursorsRequest(nss, cursorIds);
+}
+
+BSONObj KillCursorsRequest::toBSON() const {
+ BSONObjBuilder builder;
+
+ builder.append(kCmdName, nss.coll());
+ BSONArrayBuilder idsBuilder(builder.subarrayStart(kCursorsField));
+ for (CursorId id : cursorIds) {
+ idsBuilder.append(id);
+ }
+ idsBuilder.doneFast();
+
+ return builder.obj();
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/query/killcursors_request.h b/src/mongo/db/query/killcursors_request.h
new file mode 100644
index 00000000000..34c8595eff6
--- /dev/null
+++ b/src/mongo/db/query/killcursors_request.h
@@ -0,0 +1,55 @@
+/**
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "mongo/base/status_with.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/db/clientcursor.h"
+#include "mongo/db/namespace_string.h"
+
+namespace mongo {
+
+struct KillCursorsRequest {
+ /**
+ * Constructs from a namespace and a list of cursors ids.
+ */
+ KillCursorsRequest(const NamespaceString& nsString, const std::vector<CursorId>& ids);
+
+ static StatusWith<KillCursorsRequest> parseFromBSON(const std::string& dbname,
+ const BSONObj& cmdObj);
+
+ BSONObj toBSON() const;
+
+ const NamespaceString nss;
+ const std::vector<CursorId> cursorIds;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/query/killcursors_request_test.cpp b/src/mongo/db/query/killcursors_request_test.cpp
new file mode 100644
index 00000000000..74ce8bfa31e
--- /dev/null
+++ b/src/mongo/db/query/killcursors_request_test.cpp
@@ -0,0 +1,138 @@
+/**
+ * Copyright 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/query/killcursors_request.h"
+
+#include "mongo/db/clientcursor.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+
+namespace {
+
+TEST(KillCursorsRequestTest, parseFromBSONSuccess) {
+ StatusWith<KillCursorsRequest> result =
+ KillCursorsRequest::parseFromBSON("db",
+ BSON("killCursors"
+ << "coll"
+ << "cursors"
+ << BSON_ARRAY(CursorId(123) << CursorId(456))));
+ ASSERT_OK(result.getStatus());
+ KillCursorsRequest request = result.getValue();
+ ASSERT_EQ(request.nss.ns(), "db.coll");
+ ASSERT_EQ(request.cursorIds.size(), 2U);
+ ASSERT_EQ(request.cursorIds[0], CursorId(123));
+ ASSERT_EQ(request.cursorIds[1], CursorId(456));
+}
+
+TEST(KillCursorsRequestTest, parseFromBSONFirstFieldNotKillCursors) {
+ StatusWith<KillCursorsRequest> result =
+ KillCursorsRequest::parseFromBSON("db",
+ BSON("foobar"
+ << "coll"
+ << "cursors"
+ << BSON_ARRAY(CursorId(123) << CursorId(456))));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse);
+}
+
+TEST(KillCursorsRequestTest, parseFromBSONFirstFieldNotString) {
+ StatusWith<KillCursorsRequest> result = KillCursorsRequest::parseFromBSON(
+ "db", BSON("killCursors" << 99 << "cursors" << BSON_ARRAY(CursorId(123) << CursorId(456))));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse);
+}
+
+TEST(KillCursorsRequestTest, parseFromBSONInvalidNamespace) {
+ StatusWith<KillCursorsRequest> result =
+ KillCursorsRequest::parseFromBSON("",
+ BSON("killCursors"
+ << "coll"
+ << "cursors"
+ << BSON_ARRAY(CursorId(123) << CursorId(456))));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::InvalidNamespace);
+}
+
+TEST(KillCursorsRequestTest, parseFromBSONCursorFieldMissing) {
+ StatusWith<KillCursorsRequest> result = KillCursorsRequest::parseFromBSON("db",
+ BSON("killCursors"
+ << "coll"));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse);
+}
+
+TEST(KillCursorsRequestTest, parseFromBSONCursorFieldNotArray) {
+ StatusWith<KillCursorsRequest> result =
+ KillCursorsRequest::parseFromBSON("db",
+ BSON("killCursors"
+ << "coll"
+ << "cursors" << CursorId(123)));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse);
+}
+
+TEST(KillCursorsRequestTest, parseFromBSONCursorFieldEmptyArray) {
+ StatusWith<KillCursorsRequest> result =
+ KillCursorsRequest::parseFromBSON("db",
+ BSON("killCursors"
+ << "coll"
+ << "cursors" << BSONArrayBuilder().arr()));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::BadValue);
+}
+
+
+TEST(KillCursorsRequestTest, parseFromBSONCursorFieldContainsEltOfWrongType) {
+ StatusWith<KillCursorsRequest> result =
+ KillCursorsRequest::parseFromBSON("db",
+ BSON("killCursors"
+ << "coll"
+ << "cursors"
+ << BSON_ARRAY(CursorId(123) << "foo"
+ << CursorId(456))));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse);
+}
+
+TEST(KillCursorsRequestTest, toBSON) {
+ const NamespaceString nss("db.coll");
+ std::vector<CursorId> cursorIds = {CursorId(123), CursorId(456)};
+ KillCursorsRequest request(nss, cursorIds);
+ BSONObj requestObj = request.toBSON();
+ BSONObj expectedObj = BSON("killCursors"
+ << "coll"
+ << "cursors" << BSON_ARRAY(CursorId(123) << CursorId(456)));
+ ASSERT_EQ(requestObj, expectedObj);
+}
+
+} // namespace
+
+} // namespace mongo
diff --git a/src/mongo/db/query/killcursors_response.cpp b/src/mongo/db/query/killcursors_response.cpp
new file mode 100644
index 00000000000..4117e4dafe0
--- /dev/null
+++ b/src/mongo/db/query/killcursors_response.cpp
@@ -0,0 +1,137 @@
+/**
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/query/killcursors_response.h"
+
+#include "mongo/rpc/get_status_from_command_result.h"
+
+namespace mongo {
+
+namespace {
+
+const char kKilledField[] = "cursorsKilled";
+const char kNotFoundField[] = "cursorsNotFound";
+const char kAliveField[] = "cursorsAlive";
+
+Status fillOutCursorArray(const BSONObj& cmdResponse,
+ StringData fieldName,
+ std::vector<CursorId>* cursorIds) {
+ BSONElement elt = cmdResponse[fieldName];
+ if (elt.eoo()) {
+ return Status::OK();
+ }
+
+ if (elt.type() != BSONType::Array) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "Field '" << fieldName
+ << "' must be of type array in: " << cmdResponse};
+ }
+
+ for (BSONElement cursorElt : elt.Obj()) {
+ if (cursorElt.type() != BSONType::NumberLong) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "Field '" << fieldName
+ << "' contains an element that is not of type long: "
+ << cursorElt};
+ }
+ cursorIds->push_back(cursorElt.numberLong());
+ }
+
+ if (cursorIds->empty()) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "Must specify at least one cursor id for field '" << fieldName
+ << "' in: " << cmdResponse};
+ }
+
+ return Status::OK();
+}
+
+void addCursorArrayToBSON(const std::vector<CursorId>& cursorIds,
+ StringData fieldName,
+ BSONObjBuilder* builder) {
+ BSONArrayBuilder idsBuilder(builder->subarrayStart(fieldName));
+ for (CursorId id : cursorIds) {
+ idsBuilder.append(id);
+ }
+ idsBuilder.doneFast();
+}
+
+} // namespace
+
+KillCursorsResponse::KillCursorsResponse() {}
+
+KillCursorsResponse::KillCursorsResponse(const std::vector<CursorId>& killed,
+ const std::vector<CursorId>& notFound,
+ const std::vector<CursorId>& alive)
+ : cursorsKilled(killed), cursorsNotFound(notFound), cursorsAlive(alive) {}
+
+StatusWith<KillCursorsResponse> KillCursorsResponse::parseFromBSON(const BSONObj& cmdResponse) {
+ Status cmdStatus = getStatusFromCommandResult(cmdResponse);
+ if (!cmdStatus.isOK()) {
+ return cmdStatus;
+ }
+
+ std::vector<CursorId> cursorsKilled;
+ Status killedStatus = fillOutCursorArray(cmdResponse, kKilledField, &cursorsKilled);
+ if (!killedStatus.isOK()) {
+ return killedStatus;
+ }
+
+ std::vector<CursorId> cursorsNotFound;
+ Status notFoundStatus = fillOutCursorArray(cmdResponse, kNotFoundField, &cursorsNotFound);
+ if (!notFoundStatus.isOK()) {
+ return notFoundStatus;
+ }
+
+ std::vector<CursorId> cursorsAlive;
+ Status aliveStatus = fillOutCursorArray(cmdResponse, kAliveField, &cursorsAlive);
+ if (!aliveStatus.isOK()) {
+ return aliveStatus;
+ }
+
+ return KillCursorsResponse(cursorsKilled, cursorsNotFound, cursorsAlive);
+}
+
+BSONObj KillCursorsResponse::toBSON() const {
+ BSONObjBuilder builder;
+ addToBSON(&builder);
+ builder.append("ok", 1.0);
+ return builder.obj();
+}
+
+void KillCursorsResponse::addToBSON(BSONObjBuilder* builder) const {
+ addCursorArrayToBSON(cursorsKilled, kKilledField, builder);
+ addCursorArrayToBSON(cursorsNotFound, kNotFoundField, builder);
+ addCursorArrayToBSON(cursorsAlive, kAliveField, builder);
+}
+
+} // namespace mongo
diff --git a/src/mongo/db/query/killcursors_response.h b/src/mongo/db/query/killcursors_response.h
new file mode 100644
index 00000000000..4f0d7838dfc
--- /dev/null
+++ b/src/mongo/db/query/killcursors_response.h
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include "mongo/base/status_with.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/db/clientcursor.h"
+
+namespace mongo {
+
+struct KillCursorsResponse {
+ /**
+ * Constructs an empty response.
+ */
+ KillCursorsResponse();
+
+ /**
+ * Constructs a response from known lists of cursor ids.
+ */
+ KillCursorsResponse(const std::vector<CursorId>& killed,
+ const std::vector<CursorId>& notFound,
+ const std::vector<CursorId>& alive);
+
+ static StatusWith<KillCursorsResponse> parseFromBSON(const BSONObj& cmdResponse);
+
+ BSONObj toBSON() const;
+
+ void addToBSON(BSONObjBuilder* builder) const;
+
+ const std::vector<CursorId> cursorsKilled;
+ const std::vector<CursorId> cursorsNotFound;
+ const std::vector<CursorId> cursorsAlive;
+};
+
+} // namespace mongo
diff --git a/src/mongo/db/query/killcursors_response_test.cpp b/src/mongo/db/query/killcursors_response_test.cpp
new file mode 100644
index 00000000000..fc7e029fe86
--- /dev/null
+++ b/src/mongo/db/query/killcursors_response_test.cpp
@@ -0,0 +1,124 @@
+/**
+ * Copyright 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/db/query/killcursors_response.h"
+
+#include "mongo/db/clientcursor.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+
+namespace {
+
+TEST(KillCursorsResponseTest, parseFromBSONSuccess) {
+ StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON(BSON(
+ "cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound"
+ << BSON_ARRAY(CursorId(456) << CursorId(6)) << "cursorsAlive"
+ << BSON_ARRAY(CursorId(7) << CursorId(8) << CursorId(9)) << "ok" << 1.0));
+ ASSERT_OK(result.getStatus());
+ KillCursorsResponse response = result.getValue();
+ ASSERT_EQ(response.cursorsKilled.size(), 1U);
+ ASSERT_EQ(response.cursorsKilled[0], CursorId(123));
+ ASSERT_EQ(response.cursorsNotFound.size(), 2U);
+ ASSERT_EQ(response.cursorsNotFound[0], CursorId(456));
+ ASSERT_EQ(response.cursorsNotFound[1], CursorId(6));
+ ASSERT_EQ(response.cursorsAlive.size(), 3U);
+ ASSERT_EQ(response.cursorsAlive[0], CursorId(7));
+ ASSERT_EQ(response.cursorsAlive[1], CursorId(8));
+ ASSERT_EQ(response.cursorsAlive[2], CursorId(9));
+}
+
+TEST(KillCursorsResponseTest, parseFromBSONSuccessOmitCursorsAlive) {
+ StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON(
+ BSON("cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound"
+ << BSON_ARRAY(CursorId(456) << CursorId(6)) << "ok" << 1.0));
+ ASSERT_OK(result.getStatus());
+ KillCursorsResponse response = result.getValue();
+ ASSERT_EQ(response.cursorsKilled.size(), 1U);
+ ASSERT_EQ(response.cursorsKilled[0], CursorId(123));
+ ASSERT_EQ(response.cursorsNotFound.size(), 2U);
+ ASSERT_EQ(response.cursorsNotFound[0], CursorId(456));
+ ASSERT_EQ(response.cursorsNotFound[1], CursorId(6));
+ ASSERT(response.cursorsAlive.empty());
+}
+
+TEST(KillCursorsResponseTest, parseFromBSONCommandNotOk) {
+ StatusWith<KillCursorsResponse> result =
+ KillCursorsResponse::parseFromBSON(BSON("ok" << 0.0 << "code" << 123 << "errmsg"
+ << "does not work"));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), 123);
+ ASSERT_EQ(result.getStatus().reason(), "does not work");
+}
+
+TEST(KillCursorsResponseTest, parseFromBSONFieldNotArray) {
+ StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON(BSON(
+ "cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound"
+ << "foobar"
+ << "cursorsAlive" << BSON_ARRAY(CursorId(7) << CursorId(8) << CursorId(9))
+ << "ok" << 1.0));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse);
+}
+
+TEST(KillCursorsResponseTest, parseFromBSONArrayContainsInvalidElement) {
+ StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON(
+ BSON("cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound"
+ << BSON_ARRAY(CursorId(456) << CursorId(6)) << "cursorsAlive"
+ << BSON_ARRAY(CursorId(7) << "foobar" << CursorId(9)) << "ok" << 1.0));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse);
+}
+
+TEST(KillCursorsResponseTest, parseFromBSONArrayEmpty) {
+ StatusWith<KillCursorsResponse> result = KillCursorsResponse::parseFromBSON(
+ BSON("cursorsKilled" << BSONArrayBuilder().arr() << "cursorsNotFound"
+ << BSON_ARRAY(CursorId(456) << CursorId(6)) << "cursorsAlive"
+ << BSON_ARRAY(CursorId(7) << "foobar" << CursorId(9)) << "ok" << 1.0));
+ ASSERT_NOT_OK(result.getStatus());
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::BadValue);
+}
+
+TEST(KillCursorsResponseTest, toBSON) {
+ std::vector<CursorId> killed = {CursorId(123)};
+ std::vector<CursorId> notFound = {CursorId(456), CursorId(6)};
+ std::vector<CursorId> alive = {CursorId(7), CursorId(8), CursorId(9)};
+ KillCursorsResponse response(killed, notFound, alive);
+ BSONObj responseObj = response.toBSON();
+ BSONObj expectedResponse = BSON(
+ "cursorsKilled" << BSON_ARRAY(CursorId(123)) << "cursorsNotFound"
+ << BSON_ARRAY(CursorId(456) << CursorId(6)) << "cursorsAlive"
+ << BSON_ARRAY(CursorId(7) << CursorId(8) << CursorId(9)) << "ok" << 1.0);
+ ASSERT_EQ(responseObj, expectedResponse);
+}
+
+} // namespace
+
+} // namespace mongo
diff --git a/src/mongo/dbtests/querytests.cpp b/src/mongo/dbtests/querytests.cpp
index d9c77b80f2c..4912aa1b3a5 100644
--- a/src/mongo/dbtests/querytests.cpp
+++ b/src/mongo/dbtests/querytests.cpp
@@ -1669,7 +1669,7 @@ public:
{
OldClientWriteContext ctx(&_txn, ns());
ClientCursorPin pinCursor(ctx.db()->getCollection(ns())->getCursorManager(), cursorId);
- string expectedAssertion = str::stream() << "Cannot kill active cursor " << cursorId;
+ string expectedAssertion = str::stream() << "Cannot kill pinned cursor: " << cursorId;
ASSERT_THROWS_WHAT(CursorManager::eraseCursorGlobal(&_txn, cursorId),
MsgAssertionException,
expectedAssertion);