summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Gottlieb <daniel.gottlieb@mongodb.com>2020-05-29 19:33:20 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-06-01 03:49:16 +0000
commit7be1baa039fa78f583171ea8cc630a93c038e719 (patch)
tree3b94030ac9a18dd3676c71f4bd74e042907fed03
parent903bcf6579085c135f9506933d8f671f54255eb0 (diff)
downloadmongo-7be1baa039fa78f583171ea8cc630a93c038e719.tar.gz
SERVER-48453: Prevent reusing record ids within a process/rollback lifetime.
(cherry picked from commit 82be4b070e723683916733d903a678ae0e29ad91)
-rw-r--r--jstests/noPassthrough/drop_create_rollback.js51
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp3
2 files changed, 54 insertions, 0 deletions
diff --git a/jstests/noPassthrough/drop_create_rollback.js b/jstests/noPassthrough/drop_create_rollback.js
new file mode 100644
index 00000000000..60d6aa9f286
--- /dev/null
+++ b/jstests/noPassthrough/drop_create_rollback.js
@@ -0,0 +1,51 @@
+/**
+ * Tests that the _mdb_catalog does not reuse RecordIds after a catalog restart.
+ * @tags: [requires_replication, requires_wiredtiger, requires_persistence]
+ */
+(function() {
+'use strict';
+
+TestData.rollbackShutdowns = true;
+TestData.logComponentVerbosity = {
+ storage: {recovery: 2}
+};
+load('jstests/replsets/libs/rollback_test.js');
+
+const rollbackTest = new RollbackTest();
+let primary = rollbackTest.getPrimary();
+// Do a majority write to guarantee the stable timestamp contains this create. Otherwise startup
+// replication recovery will recreate the collection, initializing the _mdb_catalog RecordId
+// generator.
+assert.commandWorked(
+ primary.getDB("foo").runCommand({create: "timestamped", writeConcern: {w: "majority"}}));
+
+// This restart forces the _mdb_catalog to refresh, uninitializing the auto-incrementing RecordId
+// generator.
+jsTestLog({msg: "Restarting primary.", primary: primary, nodeId: primary.nodeId});
+const SIGTERM = 15; // clean shutdown
+rollbackTest.restartNode(primary.nodeId, SIGTERM);
+
+let rollbackNode = rollbackTest.transitionToRollbackOperations();
+jsTestLog({
+ msg: "The restarted primary must be the node that goes into rollback.",
+ primary: primary,
+ rollback: rollbackNode
+});
+assert.eq(primary, rollbackNode);
+// The `timestamped` collection is positioned as the last record in the _mdb_catalog. An unpatched
+// MongoDB dropping this collection will result in its RecordId being reused.
+assert.commandWorked(rollbackNode.getDB("foo").runCommand({drop: "timestamped"}));
+// Reusing the RecordId with an untimestamped write (due to being in the `local` database) results
+// in an illegal update chain. A successful rollback should see the timestamped collection. But an
+// unpatched MongoDB would have the untimestamped update on the same update chain, "locking in" the
+// drop.
+assert.commandWorked(rollbackNode.getDB("local").createCollection("untimestamped"));
+
+rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
+rollbackTest.transitionToSyncSourceOperationsDuringRollback();
+rollbackTest.transitionToSteadyStateOperations();
+
+assert.contains("timestamped", rollbackNode.getDB("foo").getCollectionNames());
+assert.contains("untimestamped", rollbackNode.getDB("local").getCollectionNames());
+rollbackTest.stop();
+})();
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp
index 5b307679f90..89661f27c58 100644
--- a/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp
@@ -1022,6 +1022,9 @@ bool WiredTigerRecordStore::findRecord(OperationContext* opCtx,
void WiredTigerRecordStore::deleteRecord(OperationContext* opCtx, const RecordId& id) {
dassert(opCtx->lockState()->isWriteLocked());
invariant(opCtx->lockState()->inAWriteUnitOfWork() || opCtx->lockState()->isNoop());
+ // SERVER-48453: Initialize the next record id counter before deleting. This ensures we won't
+ // reuse record ids, which can be problematic for the _mdb_catalog.
+ _initNextIdIfNeeded(opCtx);
// Deletes should never occur on a capped collection because truncation uses
// WT_SESSION::truncate().