summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/noPassthrough/step_down_during_drop_database_while_aborting_index_builds.js82
-rw-r--r--src/mongo/db/catalog/drop_database.cpp14
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.