summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Wlodarek <gregory.wlodarek@mongodb.com>2020-05-05 13:42:17 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-05-05 21:22:38 +0000
commit1e525750c99b70e21af0b20d4c29159f8329c008 (patch)
tree782f9ab99642417f9e97f021eb76b48c4649e7ad
parent0684c1761e399146a3a1760162d017d747bc3d3e (diff)
downloadmongo-1e525750c99b70e21af0b20d4c29159f8329c008.tar.gz
SERVER-47319 dropIndexes should not invariant if a similar index was created while yielding locks to abort different indexes
(cherry picked from commit 60b2655e069a30dfa1656809b1ae25d77617292c)
-rw-r--r--jstests/noPassthrough/drop_indexes_on_recreated_index.js70
-rw-r--r--src/mongo/db/catalog/drop_indexes.cpp52
2 files changed, 105 insertions, 17 deletions
diff --git a/jstests/noPassthrough/drop_indexes_on_recreated_index.js b/jstests/noPassthrough/drop_indexes_on_recreated_index.js
new file mode 100644
index 00000000000..2f159248e79
--- /dev/null
+++ b/jstests/noPassthrough/drop_indexes_on_recreated_index.js
@@ -0,0 +1,70 @@
+/**
+ * Verifies that the dropIndexes command does not invariant if it sees a similar index build
+ * completed that it successfully aborted.
+ */
+(function() {
+"use strict";
+
+load("jstests/libs/fail_point_util.js");
+load("jstests/noPassthrough/libs/index_build.js");
+
+const dbName = "test";
+const collName = "dropIndexesOnRecreatedIndex";
+
+const conn = MongoRunner.runMongod({});
+assert(conn);
+
+const db = conn.getDB(dbName);
+assert.commandWorked(db.createCollection(collName));
+
+const coll = db.getCollection(collName);
+assert.commandWorked(coll.insert({a: 1}));
+
+const indexSpec = {
+ a: 1
+};
+
+IndexBuildTest.pauseIndexBuilds(conn);
+
+// Start an index build on {a: 1}.
+let awaitIndexBuild = IndexBuildTest.startIndexBuild(
+ conn, coll.getFullName(), indexSpec, {}, [ErrorCodes.IndexBuildAborted]);
+IndexBuildTest.waitForIndexBuildToStart(db, collName, "a_1");
+
+const failPoint = "hangAfterAbortingIndexes";
+let res = assert.commandWorked(db.adminCommand({configureFailPoint: failPoint, mode: "alwaysOn"}));
+let timesEntered = res.count;
+
+TestData.dbName = dbName;
+TestData.collName = collName;
+TestData.indexSpec = indexSpec;
+
+// Abort {a: 1} while it is being built.
+let awaitDropIndexes = startParallelShell(() => {
+ assert.commandWorked(db.getSiblingDB(TestData.dbName)
+ .getCollection(TestData.collName)
+ .dropIndexes(TestData.indexSpec));
+}, conn.port);
+
+// Wait until {a: 1} is aborted, but before the dropIndexes command finishes.
+awaitIndexBuild();
+assert.commandWorked(conn.adminCommand({
+ waitForFailPoint: failPoint,
+ timesEntered: timesEntered + 1,
+ maxTimeMS: kDefaultWaitForFailPointTimeout
+}));
+
+// Recreate {a: 1} and let it finish.
+IndexBuildTest.resumeIndexBuilds(conn);
+assert.commandWorked(coll.createIndex(indexSpec));
+
+// Allow dropIndexes to finish. The dropIndexes should drop the newly recreated {a: 1} index.
+assert.commandWorked(db.adminCommand({configureFailPoint: failPoint, mode: "off"}));
+awaitDropIndexes();
+
+// The collection should only have the _id index.
+let indexes = coll.getIndexes();
+assert.eq(1, indexes.length);
+
+MongoRunner.stopMongod(conn);
+}());
diff --git a/src/mongo/db/catalog/drop_indexes.cpp b/src/mongo/db/catalog/drop_indexes.cpp
index b862c5549cd..3b947e81851 100644
--- a/src/mongo/db/catalog/drop_indexes.cpp
+++ b/src/mongo/db/catalog/drop_indexes.cpp
@@ -51,6 +51,8 @@
namespace mongo {
namespace {
+MONGO_FAIL_POINT_DEFINE(hangAfterAbortingIndexes);
+
// Field name in dropIndexes command for indexes to drop.
constexpr auto kIndexFieldName = "index"_sd;
@@ -345,6 +347,11 @@ Status dropIndexes(OperationContext* opCtx,
abortedIndexBuilders.insert(
abortedIndexBuilders.end(), justAborted.begin(), justAborted.end());
+ if (MONGO_unlikely(hangAfterAbortingIndexes.shouldFail())) {
+ LOGV2(4731900, "Hanging on hangAfterAbortingIndexes fail point");
+ hangAfterAbortingIndexes.pauseWhileSet();
+ }
+
// Take an exclusive lock on the collection now to be able to perform index catalog writes
// when removing ready indexes from disk.
autoColl.emplace(opCtx, dbAndUUID, MODE_X);
@@ -379,25 +386,36 @@ Status dropIndexes(OperationContext* opCtx,
}
}
- // If the "*" wildcard was not specified, verify that all the index names belonging to the
- // index builder were aborted.
+ // Drop any ready indexes that were created while we yielded our locks while aborting using
+ // similar index specs.
if (!isWildcard && !abortedIndexBuilders.empty()) {
- // This is necessary to check shard version.
- OldClientContext ctx(opCtx, collection->ns().ns());
-
- // Iterate through all aborted indexes and verify none of them are ready. This would
- // indicate a flaw with the abort logic that allows indexes to complete despite the
- // dropIndexes command reporting they were aborted.
- auto indexCatalog = collection->getIndexCatalog();
- const bool includeUnfinished = false;
- const bool noneReady = std::none_of(indexNames.begin(), indexNames.end(), [&](auto name) {
- return indexCatalog->findIndexByName(opCtx, name, includeUnfinished);
- });
+ return writeConflictRetry(opCtx, "dropIndexes", dbAndUUID.toString(), [&] {
+ WriteUnitOfWork wuow(opCtx);
- invariant(noneReady,
- str::stream() << "Found completed indexes despite aborting index build: "
- << abortedIndexBuilders.front());
- return Status::OK();
+ // This is necessary to check shard version.
+ OldClientContext ctx(opCtx, collection->ns().ns());
+
+ // Iterate through all the aborted indexes and drop any indexes that are ready in the
+ // index catalog. This would indicate that while we yielded our locks during the abort
+ // phase, a new identical index was created.
+ auto indexCatalog = collection->getIndexCatalog();
+ const bool includeUnfinished = false;
+ for (const auto& indexName : indexNames) {
+ auto desc = indexCatalog->findIndexByName(opCtx, indexName, includeUnfinished);
+ if (!desc) {
+ // A similar index wasn't created while we yielded the locks during abort.
+ continue;
+ }
+
+ Status status = dropIndexByDescriptor(opCtx, collection, indexCatalog, desc);
+ if (!status.isOK()) {
+ return status;
+ }
+ }
+
+ wuow.commit();
+ return Status::OK();
+ });
}
if (!abortedIndexBuilders.empty()) {