diff options
-rw-r--r-- | jstests/core/apply_ops_atomicity.js | 38 | ||||
-rw-r--r-- | src/mongo/base/error_codes.err | 1 | ||||
-rw-r--r-- | src/mongo/db/concurrency/fast_map_noalloc.h | 4 |
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; |