diff options
author | Gregory Noma <gregory.noma@gmail.com> | 2020-04-10 09:26:58 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-04-10 13:45:41 +0000 |
commit | 9d4284a8010e35a640306017de895be568be4855 (patch) | |
tree | b58eccc0f90289de961c0e0d83ff069b9a443a72 | |
parent | d3da6de558fc4fbd7bb20617ed5d1f34c2c58bb3 (diff) | |
download | mongo-9d4284a8010e35a640306017de895be568be4855.tar.gz |
SERVER-47317 Take collection MODE_X locks instead of database MODE_X lock in covertToCapped
(cherry picked from commit ff483e300c692dd2a47a649c4bddc3632178b2fd)
-rw-r--r-- | jstests/noPassthroughWithMongod/convert_to_capped_no_conflicts.js | 38 | ||||
-rw-r--r-- | src/mongo/db/catalog/capped_utils.cpp | 55 | ||||
-rw-r--r-- | src/mongo/db/catalog/database.h | 4 | ||||
-rw-r--r-- | src/mongo/db/catalog/database_impl.cpp | 15 | ||||
-rw-r--r-- | src/mongo/db/catalog/database_impl.h | 10 |
5 files changed, 87 insertions, 35 deletions
diff --git a/jstests/noPassthroughWithMongod/convert_to_capped_no_conflicts.js b/jstests/noPassthroughWithMongod/convert_to_capped_no_conflicts.js new file mode 100644 index 00000000000..f037b0075ee --- /dev/null +++ b/jstests/noPassthroughWithMongod/convert_to_capped_no_conflicts.js @@ -0,0 +1,38 @@ +/** + * Tests that convertToCapped does not conflict with a database MODE_IX lock. + */ +(function() { +"use strict"; + +load("jstests/libs/parallel_shell_helpers.js"); +load("jstests/libs/wait_for_command.js"); + +const collName = "convert_to_capped_no_conflicts"; +const testDB = db.getSiblingDB("test"); +testDB.dropDatabase(); +const testColl = testDB.getCollection(collName); + +const sleepFunction = function(sleepDB) { + // If convertToCapped calls need to wait on this lock, holding this lock for 4 hours will + // trigger a test timeout. + assert.commandFailedWithCode( + db.getSiblingDB("test").adminCommand( + {sleep: 1, secs: 18000, lockTarget: sleepDB, lock: "iw", $comment: "Lock sleep"}), + ErrorCodes.Interrupted); +}; + +const sleepCommand = startParallelShell(funWithArgs(sleepFunction, "test"), testDB.getMongo().port); +const sleepID = + waitForCommand("sleepCmd", + op => (op["ns"] == "admin.$cmd" && op["command"]["$comment"] == "Lock sleep"), + testDB.getSiblingDB("admin")); + +assert.commandWorked(testColl.insert({a: 1})); +assert(!testColl.isCapped()); +assert.commandWorked(testDB.runCommand({convertToCapped: collName, size: 1})); +assert(testColl.isCapped()); + +// Interrupt the sleep command. +assert.commandWorked(testDB.getSiblingDB("admin").killOp(sleepID)); +sleepCommand(); +})();
\ No newline at end of file diff --git a/src/mongo/db/catalog/capped_utils.cpp b/src/mongo/db/catalog/capped_utils.cpp index 6aa842def14..9c45c28eea4 100644 --- a/src/mongo/db/catalog/capped_utils.cpp +++ b/src/mongo/db/catalog/capped_utils.cpp @@ -237,38 +237,51 @@ void cloneCollectionAsCapped(OperationContext* opCtx, MONGO_UNREACHABLE; } -void convertToCapped(OperationContext* opCtx, - const NamespaceString& collectionName, - long long size) { - StringData dbname = collectionName.db(); - StringData shortSource = collectionName.coll(); +void convertToCapped(OperationContext* opCtx, const NamespaceString& ns, long long size) { + StringData dbname = ns.db(); + StringData shortSource = ns.coll(); - AutoGetDb autoDb(opCtx, collectionName.db(), MODE_X); + AutoGetCollection autoColl(opCtx, ns, MODE_X); bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() && - !repl::ReplicationCoordinator::get(opCtx)->canAcceptWritesFor(opCtx, collectionName); + !repl::ReplicationCoordinator::get(opCtx)->canAcceptWritesFor(opCtx, ns); uassert(ErrorCodes::NotMaster, - str::stream() << "Not primary while converting " << collectionName - << " to a capped collection", + str::stream() << "Not primary while converting " << ns << " to a capped collection", !userInitiatedWritesAndNotPrimary); - Database* const db = autoDb.getDb(); + Database* const db = autoColl.getDb(); uassert( ErrorCodes::NamespaceNotFound, str::stream() << "database " << dbname << " not found", db); - BackgroundOperation::assertNoBgOpInProgForDb(dbname); - IndexBuildsCoordinator::get(opCtx)->assertNoBgOpInProgForDb(dbname); + BackgroundOperation::assertNoBgOpInProgForNs(ns); + if (Collection* coll = autoColl.getCollection()) { + IndexBuildsCoordinator::get(opCtx)->assertNoIndexBuildInProgForCollection(coll->uuid()); + } // Generate a temporary collection name that will not collide with any existing collections. - auto tmpNameResult = - db->makeUniqueCollectionNamespace(opCtx, "tmp%%%%%.convertToCapped." + shortSource); - uassertStatusOKWithContext(tmpNameResult, - str::stream() - << "Cannot generate temporary collection namespace to convert " - << collectionName << " to a capped collection"); - - const auto& longTmpName = tmpNameResult.getValue(); + boost::optional<Lock::CollectionLock> collLock; + const auto longTmpName = [&] { + while (true) { + auto tmpNameResult = + db->makeUniqueCollectionNamespace(opCtx, "tmp%%%%%.convertToCapped." + shortSource); + uassertStatusOKWithContext( + tmpNameResult, + str::stream() << "Cannot generate temporary collection namespace to convert " << ns + << " to a capped collection"); + + collLock.emplace(opCtx, tmpNameResult.getValue(), MODE_X); + if (!CollectionCatalog::get(opCtx).lookupCollectionByNamespace( + opCtx, tmpNameResult.getValue())) { + return std::move(tmpNameResult.getValue()); + } + + // The temporary collection was created by someone else between the name being + // generated and acquiring the lock on the collection, so try again with a new + // temporary collection name. + collLock.reset(); + } + }(); const auto shortTmpName = longTmpName.coll().toString(); cloneCollectionAsCapped(opCtx, db, shortSource.toString(), shortTmpName, size, true); @@ -276,7 +289,7 @@ void convertToCapped(OperationContext* opCtx, RenameCollectionOptions options; options.dropTarget = true; options.stayTemp = false; - uassertStatusOK(renameCollection(opCtx, longTmpName, collectionName, options)); + uassertStatusOK(renameCollection(opCtx, longTmpName, ns, options)); } } // namespace mongo diff --git a/src/mongo/db/catalog/database.h b/src/mongo/db/catalog/database.h index 2b2edbac1dd..f27e2c8f3be 100644 --- a/src/mongo/db/catalog/database.h +++ b/src/mongo/db/catalog/database.h @@ -163,10 +163,10 @@ public: * Returns NamespaceExists if we are unable to generate a collection name that does not conflict * with an existing collection in this database. * - * The database must be locked in MODE_X when calling this function. + * The database must be locked in MODE_IX when calling this function. */ virtual StatusWith<NamespaceString> makeUniqueCollectionNamespace( - OperationContext* opCtx, StringData collectionNameModel) = 0; + OperationContext* opCtx, StringData collectionNameModel) const = 0; /** * If we are in a replset, every replicated collection must have an _id index. As we scan each diff --git a/src/mongo/db/catalog/database_impl.cpp b/src/mongo/db/catalog/database_impl.cpp index 1fc42feb943..20456310cb0 100644 --- a/src/mongo/db/catalog/database_impl.cpp +++ b/src/mongo/db/catalog/database_impl.cpp @@ -100,6 +100,12 @@ Status validateDBNameForWindows(StringData dbname) { str::stream() << "db name \"" << dbname << "\" is a reserved name"); return Status::OK(); } + +// Random number generator used to create unique collection namespaces suitable for temporary +// collections. +PseudoRandom uniqueCollectionNamespacePseudoRandom(Date_t::now().asInt64()); + +Mutex uniqueCollectionNamespaceMutex = MONGO_MAKE_LATCH("DatabaseUniqueCollectionNamespaceMutex"); } // namespace Status DatabaseImpl::validateDBName(StringData dbname) { @@ -126,8 +132,7 @@ DatabaseImpl::DatabaseImpl(const StringData name, uint64_t epoch) : _name(name.toString()), _epoch(epoch), _profileName(_name + ".system.profile"), - _viewsName(_name + "." + DurableViewCatalog::viewsCollectionName().toString()), - _uniqueCollectionNamespacePseudoRandom(Date_t::now().asInt64()) { + _viewsName(_name + "." + DurableViewCatalog::viewsCollectionName().toString()) { auto durableViewCatalog = std::make_unique<DurableViewCatalogImpl>(this); auto viewCatalog = std::make_unique<ViewCatalog>(std::move(durableViewCatalog)); @@ -763,7 +768,7 @@ Collection* DatabaseImpl::createCollection(OperationContext* opCtx, } StatusWith<NamespaceString> DatabaseImpl::makeUniqueCollectionNamespace( - OperationContext* opCtx, StringData collectionNameModel) { + OperationContext* opCtx, StringData collectionNameModel) const { invariant(opCtx->lockState()->isDbLockedForMode(name(), MODE_IX)); // There must be at least one percent sign in the collection name model. @@ -782,11 +787,13 @@ StatusWith<NamespaceString> DatabaseImpl::makeUniqueCollectionNamespace( "abcdefghijklmnopqrstuvwxyz"_sd; invariant((10U + 26U * 2) == charsToChooseFrom.size()); + stdx::lock_guard<Latch> lk(uniqueCollectionNamespaceMutex); + auto replacePercentSign = [&, this](char c) { if (c != '%') { return c; } - auto i = _uniqueCollectionNamespacePseudoRandom.nextInt32(charsToChooseFrom.size()); + auto i = uniqueCollectionNamespacePseudoRandom.nextInt32(charsToChooseFrom.size()); return charsToChooseFrom[i]; }; diff --git a/src/mongo/db/catalog/database_impl.h b/src/mongo/db/catalog/database_impl.h index 0b5e55d151e..5f8cd5472b2 100644 --- a/src/mongo/db/catalog/database_impl.h +++ b/src/mongo/db/catalog/database_impl.h @@ -31,8 +31,6 @@ #include "mongo/db/catalog/database.h" -#include "mongo/platform/random.h" - namespace mongo { class DatabaseImpl final : public Database { @@ -124,8 +122,8 @@ public: * Returns a NamespaceExists error status if multiple attempts fail to generate a possible * unique name. */ - StatusWith<NamespaceString> makeUniqueCollectionNamespace(OperationContext* opCtx, - StringData collectionNameModel) final; + StatusWith<NamespaceString> makeUniqueCollectionNamespace( + OperationContext* opCtx, StringData collectionNameModel) const final; void checkForIdIndexesAndDropPendingCollections(OperationContext* opCtx) const final; @@ -179,10 +177,6 @@ private: // collections may be created in this Database. // This variable may only be read/written while the database is locked in MODE_X. AtomicWord<bool> _dropPending{false}; - - // Random number generator used to create unique collection namespaces suitable for temporary - // collections. - PseudoRandom _uniqueCollectionNamespacePseudoRandom; }; } // namespace mongo |