diff options
-rw-r--r-- | jstests/core/txns/kill_cursors_in_transaction.js | 67 | ||||
-rw-r--r-- | src/mongo/db/commands/killcursors_cmd.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_common.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/session.cpp | 1 |
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}}; |