From f8f40d2bc140d4e8cfbc09d934ccb17682ea0d7f Mon Sep 17 00:00:00 2001 From: Jason Chan Date: Thu, 9 May 2019 13:13:33 -0400 Subject: SERVER-40790 Test downgrading from FCV4.2 to FCV4.0 with a large running transaction will fail on commit. --- ...rade_fcv_while_large_partial_txn_in_progress.js | 83 ++++++++++++++++++++++ .../db/commands/feature_compatibility_version.cpp | 9 +++ src/mongo/db/op_observer_impl.cpp | 14 +++- 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 jstests/core/txns/downgrade_fcv_while_large_partial_txn_in_progress.js diff --git a/jstests/core/txns/downgrade_fcv_while_large_partial_txn_in_progress.js b/jstests/core/txns/downgrade_fcv_while_large_partial_txn_in_progress.js new file mode 100644 index 00000000000..b549a6ced66 --- /dev/null +++ b/jstests/core/txns/downgrade_fcv_while_large_partial_txn_in_progress.js @@ -0,0 +1,83 @@ +/** + * Tests that downgrading from FCV4.2 to FCV4.0 while a large partial transaction is in progress + * will fail on commit. This tests the case where a race condition may lead to the running + * transaction committing before it could be aborted due to downgrade. + */ + +(function() { + "use strict"; + + const dbName = "test"; + const collName = "downgrade_fcv_while_large_partial_txn_in_progress"; + const testDB = db.getSiblingDB(dbName); + + const paramResult = + testDB.adminCommand({"getParameter": 1, useMultipleOplogEntryFormatForTransactions: 1}); + if (!paramResult["useMultipleOplogEntryFormatForTransactions"]) { + // TODO: SERVER-39810 Remove this early return once the new oplog format for large + // transactions is made the default. + jsTestLog( + "Skipping the test because useMultipleOplogEntryFormatForTransactions is not set to true."); + return; + } + + assert.commandWorked(db.adminCommand({ + configureFailPoint: "hangBeforeAbortingRunningTransactionsOnFCVDowngrade", + mode: "alwaysOn" + })); + + // As we are not able to send a single request larger than 16MB, we insert two documents + // of 10MB each to create a "large" transaction. + const kSize10MB = 10 * 1024 * 1024; + function createLargeDocument(id) { + return {_id: id, longString: new Array(kSize10MB).join("a")}; + } + + testDB[collName].drop({writeConcern: {w: "majority"}}); + assert.commandWorked(testDB.createCollection(collName, {writeConcern: {w: "majority"}})); + + const session = db.getMongo().startSession({causalConsistency: false}); + const sessionDB = session.getDatabase(dbName); + const sessionColl = sessionDB.getCollection(collName); + + let doc1 = createLargeDocument(1); + let doc2 = createLargeDocument(2); + + jsTestLog("Start a transaction and insert documents with sizes that add up to more than 16MB."); + session.startTransaction(); + assert.commandWorked(sessionColl.insert(doc1)); + assert.commandWorked(sessionColl.insert(doc2)); + + let downgradeFCV = startParallelShell(function() { + load("jstests/libs/feature_compatibility_version.js"); + + const testDB = db.getSiblingDB("test"); + const adminDB = db.getSiblingDB("admin"); + try { + jsTestLog("Downgrade to FCV4.0."); + assert.commandWorked( + testDB.adminCommand({setFeatureCompatibilityVersion: lastStableFCV})); + checkFCV(adminDB, lastStableFCV); + } finally { + jsTestLog("Restore back to FCV4.2."); + assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: latestFCV})); + checkFCV(adminDB, latestFCV); + } + }); + + // Wait until the in-memory FCV state has been changed to 4.0. + assert.soon(function() { + const adminDB = db.getSiblingDB("admin"); + let res = adminDB.runCommand({getParameter: 1, featureCompatibilityVersion: 1}); + assert.commandWorked(res); + return "4.0" === res.featureCompatibilityVersion.version; + }, "Failed to detect the FCV change to 4.0 from server status."); + + jsTestLog("Attempt to commit the large transaction using the FCV4.0 oplog format."); + assert.commandFailedWithCode(session.commitTransaction_forTesting(), + ErrorCodes.TransactionTooLarge); + + assert.commandWorked(db.adminCommand( + {configureFailPoint: "hangBeforeAbortingRunningTransactionsOnFCVDowngrade", mode: "off"})); + downgradeFCV(); +}()); diff --git a/src/mongo/db/commands/feature_compatibility_version.cpp b/src/mongo/db/commands/feature_compatibility_version.cpp index cf04d8518ba..cab903e15d3 100644 --- a/src/mongo/db/commands/feature_compatibility_version.cpp +++ b/src/mongo/db/commands/feature_compatibility_version.cpp @@ -63,6 +63,8 @@ using repl::UnreplicatedWritesBlock; Lock::ResourceMutex FeatureCompatibilityVersion::fcvLock("featureCompatibilityVersionLock"); +MONGO_FAIL_POINT_DEFINE(hangBeforeAbortingRunningTransactionsOnFCVDowngrade); + void FeatureCompatibilityVersion::setTargetUpgrade(OperationContext* opCtx) { // Sets both 'version' and 'targetVersion' fields. _runUpdateCommand(opCtx, [](auto updateMods) { @@ -175,6 +177,13 @@ void FeatureCompatibilityVersion::onInsertOrUpdate(OperationContext* opCtx, cons } if (newVersion != ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo42) { + if (MONGO_FAIL_POINT(hangBeforeAbortingRunningTransactionsOnFCVDowngrade)) { + log() << "featureCompatibilityVersion - " + "hangBeforeAbortingRunningTransactionsOnFCVDowngrade fail point enabled. " + "Blocking until fail point is disabled."; + MONGO_FAIL_POINT_PAUSE_WHILE_SET( + hangBeforeAbortingRunningTransactionsOnFCVDowngrade); + } // Abort all open transactions when downgrading the featureCompatibilityVersion. SessionKiller::Matcher matcherAllSessions( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp index b363dc0ec40..0965db6074d 100644 --- a/src/mongo/db/op_observer_impl.cpp +++ b/src/mongo/db/op_observer_impl.cpp @@ -997,7 +997,7 @@ namespace { // 16MB limit or the maximum number of transaction statements allowed in one entry. // // If 'limitSize' is false, then it attempts to include all given operations, regardless of whether -// or not they fit. If the ops don't fit, BSONObjectTooLarge will be thrown in that case. +// or not they fit. If the ops don't fit, TransactionTooLarge will be thrown in that case. // // Returns an iterator to the first statement that wasn't packed into the applyOps object. std::vector::const_iterator packTransactionStatementsForApplyOps( @@ -1024,7 +1024,17 @@ std::vector::const_iterator packTransactionStatementsForApp break; opsArray.append(stmt.toBSON()); } - opsArray.done(); + try { + // BSONArrayBuilder will throw a BSONObjectTooLarge exception if we exceeded the max BSON + // size. + opsArray.done(); + } catch (const AssertionException& e) { + // Change the error code to TransactionTooLarge if it is BSONObjectTooLarge. + uassert(ErrorCodes::TransactionTooLarge, + e.reason(), + e.code() != ErrorCodes::BSONObjectTooLarge); + throw; + } return stmtIter; } -- cgit v1.2.1