summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/core/apply_ops_atomicity.js38
-rw-r--r--src/mongo/base/error_codes.err1
-rw-r--r--src/mongo/db/concurrency/fast_map_noalloc.h4
3 files changed, 43 insertions, 0 deletions
diff --git a/jstests/core/apply_ops_atomicity.js b/jstests/core/apply_ops_atomicity.js
index f55a44f4048..afc131959b9 100644
--- a/jstests/core/apply_ops_atomicity.js
+++ b/jstests/core/apply_ops_atomicity.js
@@ -34,4 +34,42 @@
assert.commandWorked(newDB.runCommand(
{applyOps: [{op: "u", ns: newDBName + ".foo", o: {_id: 5, x: 17}, o2: {_id: 5, x: 16}}]}));
+ var sawTooManyLocksError = false;
+
+ function applyWithManyLocks(n) {
+ let cappedOps = [];
+ let multiOps = [];
+
+ for (let i = 0; i < n; i++) {
+ // Write to a capped collection, as that may require a lock for serialization.
+ let cappedName = "capped" + n + "-" + i;
+ newDB.createCollection(cappedName, {capped: true, size: 100});
+ cappedOps.push({op: 'i', ns: newDBName + "." + cappedName, o: {_id: 0}});
+
+ // Make an index multi-key, as that may require a lock for updating the catalog.
+ let multiName = "multi" + +n + "-" + i;
+ newDB[multiName].createIndex({x: 1});
+ multiOps.push({op: 'i', ns: newDBName + "." + multiName, o: {_id: 0, x: [0, 1]}});
+ }
+
+ let res = [cappedOps, multiOps].map((applyOps) => newDB.runCommand({applyOps}));
+ sawTooManyLocksError |= res.some((res) => res.code === ErrorCodes.TooManyLocks);
+ // Transactions involving just two collections should succeed.
+ if (n <= 2)
+ res.every((res) => res.ok);
+ // All transactions should either completely succeed or completely fail.
+ assert(res.every((res) => res.results.every((result) => result == res.ok)));
+ assert(res.every((res) => !res.ok || res.applied == n));
+ }
+
+ // Try requiring different numbers of collection accesses in a single operation to cover
+ // all edge cases, so we run out of available locks in different code paths such as during
+ // oplog application.
+ applyWithManyLocks(1);
+ applyWithManyLocks(2);
+
+ for (let i = 9; i < 16; i++) {
+ applyWithManyLocks(i);
+ }
+ assert(sawTooManyLocksError, "test no longer exhausts the max number of locks held at once");
})();
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index 280f83133a9..c090a203f77 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -200,6 +200,7 @@ error_code("ReplicaSetMonitorRemoved", 199)
error_code("ChunkRangeCleanupPending", 200)
error_code("CannotBuildIndexKeys", 201)
error_code("NetworkInterfaceExceededTimeLimit", 202)
+error_code("TooManyLocks", 208)
# Non-sequential error codes (for compatibility only)
error_code("SocketException", 9001)
diff --git a/src/mongo/db/concurrency/fast_map_noalloc.h b/src/mongo/db/concurrency/fast_map_noalloc.h
index 8d862d7ac89..e08909b8439 100644
--- a/src/mongo/db/concurrency/fast_map_noalloc.h
+++ b/src/mongo/db/concurrency/fast_map_noalloc.h
@@ -181,6 +181,10 @@ public:
* entry just inserted.
*/
Iterator insert(const KeyType& key) {
+ uassert(ErrorCodes::TooManyLocks,
+ "Operation requires too many locks",
+ _fastAccessUsedSize < PreallocCount);
+
// Find the first unused slot. This could probably be even further optimized by adding
// a field pointing to the first unused location.
int idx = 0;