diff options
-rw-r--r-- | jstests/noPassthrough/step_down_during_drop_database_while_aborting_index_builds.js | 82 | ||||
-rw-r--r-- | src/mongo/db/catalog/drop_database.cpp | 14 |
2 files changed, 96 insertions, 0 deletions
diff --git a/jstests/noPassthrough/step_down_during_drop_database_while_aborting_index_builds.js b/jstests/noPassthrough/step_down_during_drop_database_while_aborting_index_builds.js new file mode 100644 index 00000000000..3ebcd2d60ea --- /dev/null +++ b/jstests/noPassthrough/step_down_during_drop_database_while_aborting_index_builds.js @@ -0,0 +1,82 @@ +/** + * Tests that performing a stepdown on the primary during a dropDatabase command doesn't result in + * any crashes when setting the drop-pending flag back to false. + * + * @tags: [requires_replication] + */ +(function() { +"use strict"; + +load("jstests/noPassthrough/libs/index_build.js"); + +const dbName = "test"; +const collName = "coll"; + +const replSet = new ReplSetTest({nodes: 2}); +replSet.startSet(); +replSet.initiate(); + +const primary = replSet.getPrimary(); +if (!IndexBuildTest.supportsTwoPhaseIndexBuild(primary)) { + jsTestLog('Two phase index builds not enabled, skipping test.'); + replSet.stopSet(); + return; +} + +let testDB = primary.getDB(dbName); +const testColl = testDB.getCollection(collName); + +var bulk = testColl.initializeUnorderedBulkOp(); +for (var i = 0; i < 5; ++i) { + bulk.insert({x: i}); +} +assert.commandWorked(bulk.execute()); +replSet.awaitReplication(); + +IndexBuildTest.pauseIndexBuilds(testDB.getMongo()); +const awaitIndexBuild = IndexBuildTest.startIndexBuild( + testDB.getMongo(), testColl.getFullName(), {x: 1}, {}, [ErrorCodes.IndexBuildAborted]); +IndexBuildTest.waitForIndexBuildToScanCollection(testDB, collName, "x_1"); + +const failpoint = "dropDatabaseHangAfterWaitingForIndexBuilds"; +assert.commandWorked(primary.adminCommand({configureFailPoint: failpoint, mode: "alwaysOn"})); + +// Run the dropDatabase command and stepdown the primary while it is running. +const awaitDropDatabase = startParallelShell(() => { + assert.commandFailedWithCode(db.dropDatabase(), ErrorCodes.InterruptedDueToReplStateChange); +}, testDB.getMongo().port); + +checkLog.containsJson(primary, 4612302); +IndexBuildTest.resumeIndexBuilds(testDB.getMongo()); + +awaitIndexBuild(); + +// Ensure the dropDatabase command has begun before stepping down. +checkLog.containsJson(primary, 4612300); + +assert.commandWorked(testDB.adminCommand({replSetStepDown: 60, force: true})); +replSet.waitForState(primary, ReplSetTest.State.SECONDARY); + +assert.commandWorked(primary.adminCommand({configureFailPoint: failpoint, mode: "off"})); + +awaitDropDatabase(); + +const newPrimary = replSet.getPrimary(); +assert(primary.port != newPrimary.port); + +// The {x: 1} index was aborted and should not be present even though the dropDatabase command was +// interrupted. Only the _id index will exist. +let indexesRes = assert.commandWorked(newPrimary.getDB(dbName).runCommand({listIndexes: collName})); +assert.eq(1, indexesRes.cursor.firstBatch.length); + +indexesRes = + assert.commandWorked(replSet.getSecondary().getDB(dbName).runCommand({listIndexes: collName})); +assert.eq(1, indexesRes.cursor.firstBatch.length); + +// Run dropDatabase on the new primary. The secondary (formerly the primary) should be able to +// drop the database too. +newPrimary.getDB(dbName).dropDatabase(); +replSet.awaitReplication(); + +replSet.stopSet(); +})(); diff --git a/src/mongo/db/catalog/drop_database.cpp b/src/mongo/db/catalog/drop_database.cpp index 64681932fd1..823343c641c 100644 --- a/src/mongo/db/catalog/drop_database.cpp +++ b/src/mongo/db/catalog/drop_database.cpp @@ -180,6 +180,18 @@ Status _dropDatabase(OperationContext* opCtx, const std::string& dbName, bool ab indexBuildsCoord->abortDatabaseIndexBuildsNoWait( opCtx, dbName, "dropDatabase command"); + // Create a scope guard to reset the drop-pending state on the database to false if + // there is a replica state change that kills this operation while the locks were + // yielded. + auto dropPendingGuardWhileUnlocked = makeGuard([dbName, opCtx, &dropPendingGuard] { + UninterruptibleLockGuard noInterrupt(opCtx->lockState()); + AutoGetDb autoDB(opCtx, dbName, MODE_IX); + if (auto db = autoDB.getDb()) { + db->setDropPending(opCtx, false); + } + dropPendingGuard.dismiss(); + }); + // Now that the abort signals were sent out to the active index builders for this // database, 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 @@ -197,6 +209,8 @@ Status _dropDatabase(OperationContext* opCtx, const std::string& dbName, bool ab autoDB.emplace(opCtx, dbName, MODE_X); db = autoDB->getDb(); + dropPendingGuardWhileUnlocked.dismiss(); + // 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. |