diff options
author | Daniel Gottlieb <daniel.gottlieb@mongodb.com> | 2020-05-29 19:33:20 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-06-01 03:49:16 +0000 |
commit | 7be1baa039fa78f583171ea8cc630a93c038e719 (patch) | |
tree | 3b94030ac9a18dd3676c71f4bd74e042907fed03 | |
parent | 903bcf6579085c135f9506933d8f671f54255eb0 (diff) | |
download | mongo-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.js | 51 | ||||
-rw-r--r-- | src/mongo/db/storage/wiredtiger/wiredtiger_record_store.cpp | 3 |
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(). |