diff options
-rw-r--r-- | jstests/noPassthrough/drop_collection_aborts_in_progress_index_builds.js | 64 | ||||
-rw-r--r-- | src/mongo/db/catalog/drop_collection.cpp | 205 | ||||
-rw-r--r-- | src/mongo/db/catalog/drop_collection.h | 23 | ||||
-rw-r--r-- | src/mongo/db/catalog/rename_collection.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/commands/dbcommands.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/pipeline/process_interface/replica_set_node_process_interface.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/pipeline/process_interface/standalone_process_interface.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/repl/oplog.cpp | 8 | ||||
-rw-r--r-- | src/mongo/dbtests/rollbacktests.cpp | 24 |
9 files changed, 300 insertions, 67 deletions
diff --git a/jstests/noPassthrough/drop_collection_aborts_in_progress_index_builds.js b/jstests/noPassthrough/drop_collection_aborts_in_progress_index_builds.js new file mode 100644 index 00000000000..91c3e0be1d3 --- /dev/null +++ b/jstests/noPassthrough/drop_collection_aborts_in_progress_index_builds.js @@ -0,0 +1,64 @@ +/** + * Tests that the "drop" command can abort in-progress index builds. + */ +(function() { +"use strict"; + +load("jstests/noPassthrough/libs/index_build.js"); + +const mongodOptions = {}; +const conn = MongoRunner.runMongod(mongodOptions); + +if (!IndexBuildTest.supportsTwoPhaseIndexBuild(conn)) { + jsTest.log("Not running because two phase index builds are not supported."); + MongoRunner.stopMongod(conn); + return; +} + +const dbName = "drop_collection_aborts_in_progress_index_builds"; +const collName = "test"; + +TestData.dbName = dbName; +TestData.collName = collName; + +const testDB = conn.getDB(dbName); +testDB.getCollection(collName).drop(); + +assert.commandWorked(testDB.createCollection(collName)); +const coll = testDB.getCollection(collName); + +assert.commandWorked(coll.insert({a: 1})); +assert.commandWorked(coll.insert({b: 1})); + +assert.commandWorked(coll.createIndex({a: 1})); + +jsTest.log("Starting two index builds and freezing them."); +IndexBuildTest.pauseIndexBuilds(testDB.getMongo()); + +const awaitFirstIndexBuild = IndexBuildTest.startIndexBuild( + testDB.getMongo(), coll.getFullName(), {a: 1, b: 1}, {}, [ErrorCodes.IndexBuildAborted]); +IndexBuildTest.waitForIndexBuildToScanCollection(testDB, collName, "a_1_b_1"); + +const awaitSecondIndexBuild = IndexBuildTest.startIndexBuild( + testDB.getMongo(), coll.getFullName(), {b: 1}, {}, [ErrorCodes.IndexBuildAborted]); +IndexBuildTest.waitForIndexBuildToScanCollection(testDB, collName, "b_1"); + +jsTest.log("Dropping collection " + dbName + "." + collName + " with in-progress index builds"); +const awaitDrop = startParallelShell(() => { + const testDB = db.getSiblingDB(TestData.dbName); + assert.commandWorked(testDB.runCommand({drop: TestData.collName})); +}, conn.port); + +try { + checkLog.contains(testDB.getMongo(), + "About to abort all index builders on collection with UUID"); +} finally { + IndexBuildTest.resumeIndexBuilds(testDB.getMongo()); +} + +awaitFirstIndexBuild(); +awaitSecondIndexBuild(); +awaitDrop(); + +MongoRunner.stopMongod(conn); +}()); diff --git a/src/mongo/db/catalog/drop_collection.cpp b/src/mongo/db/catalog/drop_collection.cpp index 1f18d42eec0..ca7a93a945b 100644 --- a/src/mongo/db/catalog/drop_collection.cpp +++ b/src/mongo/db/catalog/drop_collection.cpp @@ -54,6 +54,20 @@ namespace mongo { MONGO_FAIL_POINT_DEFINE(hangDropCollectionBeforeLockAcquisition); MONGO_FAIL_POINT_DEFINE(hangDuringDropCollection); +Status _checkNssAndReplState(OperationContext* opCtx, Collection* coll) { + if (!coll) { + return Status(ErrorCodes::NamespaceNotFound, "ns not found"); + } + + if (opCtx->writesAreReplicated() && + !repl::ReplicationCoordinator::get(opCtx)->canAcceptWritesFor(opCtx, coll->ns())) { + return Status(ErrorCodes::NotMaster, + str::stream() << "Not primary while dropping collection " << coll->ns()); + } + + return Status::OK(); +} + Status _dropView(OperationContext* opCtx, Database* db, const NamespaceString& collectionName, @@ -104,6 +118,129 @@ Status _dropView(OperationContext* opCtx, return Status::OK(); } +Status _abortIndexBuildsAndDropCollection(OperationContext* opCtx, + const NamespaceString& startingNss, + DropCollectionSystemCollectionMode systemCollectionMode, + BSONObjBuilder& result) { + // We only need to hold an intent lock to send abort signals to the active index builder on this + // collection. + boost::optional<AutoGetDb> autoDb; + autoDb.emplace(opCtx, startingNss.db(), MODE_IX); + + boost::optional<Lock::CollectionLock> collLock; + collLock.emplace(opCtx, startingNss, MODE_IX); + + // Abandon the snapshot as the index catalog will compare the in-memory state to the disk state, + // which may have changed when we released the collection lock temporarily. + opCtx->recoveryUnit()->abandonSnapshot(); + + Collection* coll = + CollectionCatalog::get(opCtx).lookupCollectionByNamespace(opCtx, startingNss); + Status status = _checkNssAndReplState(opCtx, coll); + if (!status.isOK()) { + return status; + } + + if (MONGO_unlikely(hangDuringDropCollection.shouldFail())) { + LOGV2(518090, + "hangDuringDropCollection fail point enabled. Blocking until fail point is " + "disabled."); + hangDuringDropCollection.pauseWhileSet(); + } + + AutoStatsTracker statsTracker(opCtx, + startingNss, + Top::LockType::NotLocked, + AutoStatsTracker::LogMode::kUpdateCurOp, + autoDb->getDb()->getProfilingLevel()); + + IndexBuildsCoordinator* indexBuildsCoord = IndexBuildsCoordinator::get(opCtx); + const UUID collectionUUID = coll->uuid(); + const NamespaceStringOrUUID dbAndUUID{coll->ns().db().toString(), coll->uuid()}; + const int numIndexes = coll->getIndexCatalog()->numIndexesTotal(opCtx); + + while (true) { + // Send the abort signal to any active index builds on the collection. + indexBuildsCoord->abortCollectionIndexBuildsNoWait( + collectionUUID, + str::stream() << "Collection " << coll->ns() << "(" << collectionUUID + << ") is being dropped"); + + // Now that the abort signals were sent out to the active index builders for this + // collection, we need to release the lock temporarily to allow those index builders to + // process the abort signal. Holding a lock here will cause the index builders to block + // indefinitely. + collLock = boost::none; + autoDb = boost::none; + + indexBuildsCoord->awaitNoIndexBuildInProgressForCollection(collectionUUID); + + // Take an exclusive lock to finish the collection drop. + autoDb.emplace(opCtx, startingNss.db(), MODE_IX); + collLock.emplace(opCtx, dbAndUUID, MODE_X); + + // Abandon the snapshot as the index catalog will compare the in-memory state to the + // disk state, which may have changed when we released the collection lock temporarily. + opCtx->recoveryUnit()->abandonSnapshot(); + + coll = CollectionCatalog::get(opCtx).lookupCollectionByUUID(opCtx, collectionUUID); + status = _checkNssAndReplState(opCtx, coll); + if (!status.isOK()) { + return status; + } + + // Check if any new index builds were started while releasing the collection lock + // temporarily, if so, we need to abort the new index builders. + const bool abortAgain = indexBuildsCoord->inProgForCollection(collectionUUID); + if (!abortAgain) { + break; + } + + // We only need to hold an intent lock to send an abort signal to the active index + // builders on this collection. + collLock = boost::none; + autoDb = boost::none; + + autoDb.emplace(opCtx, startingNss.db(), MODE_IX); + collLock.emplace(opCtx, dbAndUUID, MODE_IX); + + // Abandon the snapshot as the index catalog will compare the in-memory state to the + // disk state, which may have changed when we released the collection lock temporarily. + opCtx->recoveryUnit()->abandonSnapshot(); + + coll = CollectionCatalog::get(opCtx).lookupCollectionByUUID(opCtx, collectionUUID); + status = _checkNssAndReplState(opCtx, coll); + if (!status.isOK()) { + return status; + } + } + + // It's possible for the given collection to be drop pending after obtaining the locks again, if + // that is the case, then the collection is already registered to be dropped. Return early. + const NamespaceString resolvedNss = coll->ns(); + if (resolvedNss.isDropPendingNamespace()) { + return Status::OK(); + } + + WriteUnitOfWork wunit(opCtx); + + invariant(coll->getIndexCatalog()->numIndexesInProgress(opCtx) == 0); + status = + systemCollectionMode == DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops + ? autoDb->getDb()->dropCollection(opCtx, resolvedNss, {}) + : autoDb->getDb()->dropCollectionEvenIfSystem(opCtx, resolvedNss, {}); + + if (!status.isOK()) { + return status; + } + wunit.commit(); + + result.append("nIndexesWas", numIndexes); + result.append("ns", resolvedNss.ns()); + + return Status::OK(); +} + Status _dropCollection(OperationContext* opCtx, Database* db, const NamespaceString& collectionName, @@ -113,8 +250,9 @@ Status _dropCollection(OperationContext* opCtx, Lock::CollectionLock collLock(opCtx, collectionName, MODE_X); Collection* coll = CollectionCatalog::get(opCtx).lookupCollectionByNamespace(opCtx, collectionName); - if (!coll) { - return Status(ErrorCodes::NamespaceNotFound, "ns not found"); + Status status = _checkNssAndReplState(opCtx, coll); + if (!status.isOK()) { + return status; } if (MONGO_unlikely(hangDuringDropCollection.shouldFail())) { @@ -130,18 +268,12 @@ Status _dropCollection(OperationContext* opCtx, AutoStatsTracker::LogMode::kUpdateCurOp, db->getProfilingLevel()); - if (opCtx->writesAreReplicated() && - !repl::ReplicationCoordinator::get(opCtx)->canAcceptWritesFor(opCtx, collectionName)) { - return Status(ErrorCodes::NotMaster, - str::stream() << "Not primary while dropping collection " << collectionName); - } - WriteUnitOfWork wunit(opCtx); int numIndexes = coll->getIndexCatalog()->numIndexesTotal(opCtx); BackgroundOperation::assertNoBgOpInProgForNs(collectionName.ns()); IndexBuildsCoordinator::get(opCtx)->assertNoIndexBuildInProgForCollection(coll->uuid()); - Status status = + status = systemCollectionMode == DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops ? db->dropCollection(opCtx, collectionName, dropOpTime) : db->dropCollectionEvenIfSystem(opCtx, collectionName, dropOpTime); @@ -160,9 +292,56 @@ Status _dropCollection(OperationContext* opCtx, Status dropCollection(OperationContext* opCtx, const NamespaceString& collectionName, BSONObjBuilder& result, - const repl::OpTime& dropOpTime, DropCollectionSystemCollectionMode systemCollectionMode) { if (!serverGlobalParams.quiet.load()) { + LOGV2(518070, "CMD: drop {collectionName}", "collectionName"_attr = collectionName); + } + + if (MONGO_unlikely(hangDropCollectionBeforeLockAcquisition.shouldFail())) { + LOGV2(518080, "Hanging drop collection before lock acquisition while fail point is set"); + hangDropCollectionBeforeLockAcquisition.pauseWhileSet(); + } + + try { + return writeConflictRetry(opCtx, "drop", collectionName.ns(), [&] { + { + AutoGetDb autoDb(opCtx, collectionName.db(), MODE_IX); + Database* db = autoDb.getDb(); + if (!db) { + return Status(ErrorCodes::NamespaceNotFound, "ns not found"); + } + + Collection* coll = CollectionCatalog::get(opCtx).lookupCollectionByNamespace( + opCtx, collectionName); + + if (!coll) { + return _dropView(opCtx, db, collectionName, result); + } + + // Only abort in-progress index builds if two phase index builds are supported. + bool supportsTwoPhaseIndexBuild = + IndexBuildsCoordinator::get(opCtx)->supportsTwoPhaseIndexBuild(); + if (!supportsTwoPhaseIndexBuild) { + return _dropCollection( + opCtx, db, collectionName, {}, systemCollectionMode, result); + } + } + + return _abortIndexBuildsAndDropCollection( + opCtx, collectionName, systemCollectionMode, result); + }); + } catch (ExceptionFor<ErrorCodes::NamespaceNotFound>&) { + // The shell requires that NamespaceNotFound error codes return the "ns not found" + // string. + return Status(ErrorCodes::NamespaceNotFound, "ns not found"); + } +} + +Status dropCollectionForApplyOps(OperationContext* opCtx, + const NamespaceString& collectionName, + const repl::OpTime& dropOpTime, + DropCollectionSystemCollectionMode systemCollectionMode) { + if (!serverGlobalParams.quiet.load()) { LOGV2(20332, "CMD: drop {collectionName}", "collectionName"_attr = collectionName); } @@ -179,11 +358,13 @@ Status dropCollection(OperationContext* opCtx, Collection* coll = CollectionCatalog::get(opCtx).lookupCollectionByNamespace(opCtx, collectionName); + + BSONObjBuilder unusedBuilder; if (!coll) { - return _dropView(opCtx, db, collectionName, result); + return _dropView(opCtx, db, collectionName, unusedBuilder); } else { return _dropCollection( - opCtx, db, collectionName, dropOpTime, systemCollectionMode, result); + opCtx, db, collectionName, dropOpTime, systemCollectionMode, unusedBuilder); } }); } diff --git a/src/mongo/db/catalog/drop_collection.h b/src/mongo/db/catalog/drop_collection.h index 2646c632aa1..5446bbef922 100644 --- a/src/mongo/db/catalog/drop_collection.h +++ b/src/mongo/db/catalog/drop_collection.h @@ -38,21 +38,28 @@ namespace repl { class OpTime; } // namespace repl -/** - * Drops the collection "collectionName" and populates "result" with statistics about what - * was removed. - * - * If we are applying an oplog entry for a collection drop on a secondary, 'dropOpTime' is set - * to the optime in the oplog entry. - */ enum class DropCollectionSystemCollectionMode { kDisallowSystemCollectionDrops, kAllowSystemCollectionDrops }; + +/** + * Drops the collection "collectionName" and populates "result" with statistics about what + * was removed. Aborts in-progress index builds on the collection if two phase index builds are + * supported. + */ Status dropCollection(OperationContext* opCtx, const NamespaceString& collectionName, BSONObjBuilder& result, - const repl::OpTime& dropOpTime, DropCollectionSystemCollectionMode systemCollectionMode); +/** + * Drops the collection "collectionName". When applying a 'drop' oplog entry on a secondary, the + * 'dropOpTime' will contain the optime of the oplog entry. + */ +Status dropCollectionForApplyOps(OperationContext* opCtx, + const NamespaceString& collectionName, + const repl::OpTime& dropOpTime, + DropCollectionSystemCollectionMode systemCollectionMode); + } // namespace mongo diff --git a/src/mongo/db/catalog/rename_collection.cpp b/src/mongo/db/catalog/rename_collection.cpp index 36b5e941a35..16af3071493 100644 --- a/src/mongo/db/catalog/rename_collection.cpp +++ b/src/mongo/db/catalog/rename_collection.cpp @@ -581,15 +581,13 @@ Status renameBetweenDBs(OperationContext* opCtx, // Dismissed on success auto tmpCollectionDropper = makeGuard([&] { - BSONObjBuilder unusedResult; Status status = Status::OK(); try { - status = - dropCollection(opCtx, - tmpName, - unusedResult, - {}, - DropCollectionSystemCollectionMode::kAllowSystemCollectionDrops); + status = dropCollectionForApplyOps( + opCtx, + tmpName, + {}, + DropCollectionSystemCollectionMode::kAllowSystemCollectionDrops); } catch (...) { status = exceptionToStatus(); } @@ -738,13 +736,8 @@ Status renameBetweenDBs(OperationContext* opCtx, return status; tmpCollectionDropper.dismiss(); - - BSONObjBuilder unusedResult; - return dropCollection(opCtx, - source, - unusedResult, - {}, - DropCollectionSystemCollectionMode::kAllowSystemCollectionDrops); + return dropCollectionForApplyOps( + opCtx, source, {}, DropCollectionSystemCollectionMode::kAllowSystemCollectionDrops); } } // namespace @@ -941,12 +934,11 @@ Status renameCollectionForApplyOps(OperationContext* opCtx, // Downgrade renameCollection to dropCollection. if (dropTargetNss) { - BSONObjBuilder unusedResult; - return dropCollection(opCtx, - *dropTargetNss, - unusedResult, - renameOpTime, - DropCollectionSystemCollectionMode::kAllowSystemCollectionDrops); + return dropCollectionForApplyOps( + opCtx, + *dropTargetNss, + renameOpTime, + DropCollectionSystemCollectionMode::kAllowSystemCollectionDrops); } return Status(ErrorCodes::NamespaceNotFound, diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp index a7015ebf7ec..5b880adfdd9 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -255,7 +255,6 @@ public: dropCollection(opCtx, nsToDrop, result, - {}, DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops)); return true; } diff --git a/src/mongo/db/pipeline/process_interface/replica_set_node_process_interface.cpp b/src/mongo/db/pipeline/process_interface/replica_set_node_process_interface.cpp index bed008e597e..002e261099e 100644 --- a/src/mongo/db/pipeline/process_interface/replica_set_node_process_interface.cpp +++ b/src/mongo/db/pipeline/process_interface/replica_set_node_process_interface.cpp @@ -167,9 +167,8 @@ void ReplicaSetNodeProcessInterface::createCollection(OperationContext* opCtx, void ReplicaSetNodeProcessInterface::dropCollection(OperationContext* opCtx, const NamespaceString& ns) { - BSONObjBuilder result; - uassertStatusOK(mongo::dropCollection( - opCtx, ns, result, {}, DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops)); + uassertStatusOK(mongo::dropCollectionForApplyOps( + opCtx, ns, {}, DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops)); } std::unique_ptr<Pipeline, PipelineDeleter> diff --git a/src/mongo/db/pipeline/process_interface/standalone_process_interface.cpp b/src/mongo/db/pipeline/process_interface/standalone_process_interface.cpp index f4a805a595f..a5d38eb6fc1 100644 --- a/src/mongo/db/pipeline/process_interface/standalone_process_interface.cpp +++ b/src/mongo/db/pipeline/process_interface/standalone_process_interface.cpp @@ -145,9 +145,8 @@ void StandaloneProcessInterface::createCollection(OperationContext* opCtx, void StandaloneProcessInterface::dropCollection(OperationContext* opCtx, const NamespaceString& ns) { - BSONObjBuilder result; - uassertStatusOK(mongo::dropCollection( - opCtx, ns, result, {}, DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops)); + uassertStatusOK(mongo::dropCollectionForApplyOps( + opCtx, ns, {}, DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops)); } std::unique_ptr<Pipeline, PipelineDeleter> StandaloneProcessInterface::attachCursorSourceToPipeline( diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index 8150dfbeb70..d2d92c32693 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -795,7 +795,6 @@ const StringMap<ApplyOpMetadata> kOpsMap = { {ErrorCodes::NamespaceNotFound}}}, {"drop", {[](OperationContext* opCtx, const OplogEntry& entry, OplogApplication::Mode mode) -> Status { - BSONObjBuilder resultWeDontCareAbout; const auto& cmd = entry.getObject(); auto nss = extractNsFromUUIDorNs(opCtx, entry.getNss(), entry.getUuid(), cmd); if (nss.isDropPendingNamespace()) { @@ -812,11 +811,8 @@ const StringMap<ApplyOpMetadata> kOpsMap = { if (!opCtx->writesAreReplicated()) { opTime = entry.getOpTime(); } - return dropCollection(opCtx, - nss, - resultWeDontCareAbout, - opTime, - DropCollectionSystemCollectionMode::kAllowSystemCollectionDrops); + return dropCollectionForApplyOps( + opCtx, nss, opTime, DropCollectionSystemCollectionMode::kAllowSystemCollectionDrops); }, {ErrorCodes::NamespaceNotFound}}}, // deleteIndex(es) is deprecated but still works as of April 10, 2015 diff --git a/src/mongo/dbtests/rollbacktests.cpp b/src/mongo/dbtests/rollbacktests.cpp index 72affc48683..ebd8ce2e5cb 100644 --- a/src/mongo/dbtests/rollbacktests.cpp +++ b/src/mongo/dbtests/rollbacktests.cpp @@ -337,13 +337,11 @@ public: { WriteUnitOfWork uow(&opCtx); - BSONObjBuilder result; - ASSERT_OK( - dropCollection(&opCtx, - target, - result, - {}, - DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops)); + ASSERT_OK(dropCollectionForApplyOps( + &opCtx, + target, + {}, + DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops)); ASSERT_OK(renameCollection(&opCtx, source, target)); ASSERT(!collectionExists(&opCtx, &ctx, source.ns())); ASSERT(collectionExists(&opCtx, &ctx, target.ns())); @@ -398,13 +396,11 @@ public: { WriteUnitOfWork uow(&opCtx); - BSONObjBuilder result; - ASSERT_OK( - dropCollection(&opCtx, - nss, - result, - {}, - DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops)); + ASSERT_OK(dropCollectionForApplyOps( + &opCtx, + nss, + {}, + DropCollectionSystemCollectionMode::kDisallowSystemCollectionDrops)); ASSERT(!collectionExists(&opCtx, &ctx, nss.ns())); CollectionOptions collectionOptions = assertGet(CollectionOptions::parse(BSONObj(), CollectionOptions::parseForCommand)); |