summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTess Avitabile <tess.avitabile@mongodb.com>2018-05-10 10:10:10 -0400
committerTess Avitabile <tess.avitabile@mongodb.com>2018-05-15 16:01:49 -0400
commitbae37257c81b34327db93208d31d4963ff6a8cc6 (patch)
tree0f437b0005e6cb78c6e117fe749e5c6c64bce195
parent9df77c72447bad5fab1d2b40feda83749b3767db (diff)
downloadmongo-bae37257c81b34327db93208d31d4963ff6a8cc6.tar.gz
SERVER-34903 Allow killCursors within a transaction
-rw-r--r--jstests/core/txns/kill_cursors_in_transaction.js67
-rw-r--r--src/mongo/db/commands/killcursors_cmd.cpp8
-rw-r--r--src/mongo/db/service_entry_point_common.cpp1
-rw-r--r--src/mongo/db/session.cpp1
4 files changed, 77 insertions, 0 deletions
diff --git a/jstests/core/txns/kill_cursors_in_transaction.js b/jstests/core/txns/kill_cursors_in_transaction.js
new file mode 100644
index 00000000000..e0acb9905dd
--- /dev/null
+++ b/jstests/core/txns/kill_cursors_in_transaction.js
@@ -0,0 +1,67 @@
+// Tests that the killCursors command is allowed in transactions.
+// @tags: [uses_transactions]
+(function() {
+ "use strict";
+
+ const dbName = "test";
+ const collName = "kill_cursors_in_transaction";
+ const testDB = db.getSiblingDB(dbName);
+ const session = db.getMongo().startSession({causalConsistency: false});
+ const sessionDb = session.getDatabase(dbName);
+ const sessionColl = sessionDb[collName];
+
+ sessionColl.drop();
+ for (let i = 0; i < 4; ++i) {
+ assert.commandWorked(sessionColl.insert({_id: i}));
+ }
+
+ jsTest.log("Test that the killCursors command is allowed in transactions.");
+
+ session.startTransaction();
+ let res = assert.commandWorked(sessionDb.runCommand({find: collName, batchSize: 2}));
+ assert(res.hasOwnProperty("cursor"), tojson(res));
+ assert(res.cursor.hasOwnProperty("id"), tojson(res));
+ assert.commandWorked(sessionDb.runCommand({killCursors: collName, cursors: [res.cursor.id]}));
+ session.commitTransaction();
+
+ jsTest.log("Test that the killCursors can be the first operation in a transaction.");
+ // Note that it is not a requirement to support this behavior. This test is present to ensure
+ // that the server does not crash or return an unhelpful error message.
+
+ res = assert.commandWorked(sessionDb.runCommand({find: collName, batchSize: 2}));
+ assert(res.hasOwnProperty("cursor"), tojson(res));
+ assert(res.cursor.hasOwnProperty("id"), tojson(res));
+ session.startTransaction();
+ assert.commandWorked(sessionDb.runCommand({killCursors: collName, cursors: [res.cursor.id]}));
+ session.commitTransaction();
+
+ jsTest.log("killCursors must not block on locks held by the transaction in which it is run.");
+
+ session.startTransaction();
+
+ // Open a cursor on the collection.
+ res = assert.commandWorked(sessionDb.runCommand({find: collName, batchSize: 2}));
+ assert(res.hasOwnProperty("cursor"), tojson(res));
+ assert(res.cursor.hasOwnProperty("id"), tojson(res));
+
+ // Start a drop, which will hang.
+ let awaitDrop = startParallelShell(function() {
+ db.getSiblingDB("test")["kill_cursors_in_transaction"].drop();
+ });
+
+ // Wait for the drop to have a pending MODE_X lock on the database.
+ assert.soon(function() {
+ return testDB.runCommand({find: collName, maxTimeMS: 100}).code ===
+ ErrorCodes.ExceededTimeLimit;
+ });
+
+ // killCursors does not block behind the pending MODE_X lock.
+ assert.commandWorked(sessionDb.runCommand({killCursors: collName, cursors: [res.cursor.id]}));
+
+ session.commitTransaction();
+
+ // Once the transaction has committed, the drop can proceed.
+ awaitDrop();
+
+ session.endSession();
+}());
diff --git a/src/mongo/db/commands/killcursors_cmd.cpp b/src/mongo/db/commands/killcursors_cmd.cpp
index e3dd7de3d35..ae6056c3289 100644
--- a/src/mongo/db/commands/killcursors_cmd.cpp
+++ b/src/mongo/db/commands/killcursors_cmd.cpp
@@ -47,6 +47,14 @@ class KillCursorsCmd final : public KillCursorsCmdBase {
public:
KillCursorsCmd() = default;
+ bool supportsReadConcern(const std::string& dbName,
+ const BSONObj& cmdObj,
+ repl::ReadConcernLevel level) const final {
+ // killCursors must support snapshot read concern in order to be run in transactions.
+ return level == repl::ReadConcernLevel::kLocalReadConcern ||
+ level == repl::ReadConcernLevel::kSnapshotReadConcern;
+ }
+
private:
Status _checkAuth(Client* client, const NamespaceString& nss, CursorId id) const final {
auto opCtx = client->getOperationContext();
diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp
index 5e29ff2d268..52c9ebab091 100644
--- a/src/mongo/db/service_entry_point_common.cpp
+++ b/src/mongo/db/service_entry_point_common.cpp
@@ -126,6 +126,7 @@ const StringMap<int> sessionCheckoutWhitelist = {{"abortTransaction", 1},
{"getMore", 1},
{"group", 1},
{"insert", 1},
+ {"killCursors", 1},
{"mapReduce", 1},
{"parallelCollectionScan", 1},
{"prepareTransaction", 1},
diff --git a/src/mongo/db/session.cpp b/src/mongo/db/session.cpp
index ebdf3be0237..956b8c2c101 100644
--- a/src/mongo/db/session.cpp
+++ b/src/mongo/db/session.cpp
@@ -106,6 +106,7 @@ const StringMap<int> txnCmdWhitelist = {{"abortTransaction", 1},
{"geoSearch", 1},
{"getMore", 1},
{"insert", 1},
+ {"killCursors", 1},
{"prepareTransaction", 1},
{"update", 1}};