summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Chan <jason.chan@10gen.com>2019-05-09 13:13:33 -0400
committerJason Chan <jason.chan@10gen.com>2019-05-09 13:16:13 -0400
commitf8f40d2bc140d4e8cfbc09d934ccb17682ea0d7f (patch)
tree7f02fca52cf3dbd331b05c86733238a4b3df603b
parent29b29b2af6883b99f58c7a90a95f57221874214f (diff)
downloadmongo-f8f40d2bc140d4e8cfbc09d934ccb17682ea0d7f.tar.gz
SERVER-40790 Test downgrading from FCV4.2 to FCV4.0 with a large running transaction will fail on commit.
-rw-r--r--jstests/core/txns/downgrade_fcv_while_large_partial_txn_in_progress.js83
-rw-r--r--src/mongo/db/commands/feature_compatibility_version.cpp9
-rw-r--r--src/mongo/db/op_observer_impl.cpp14
3 files changed, 104 insertions, 2 deletions
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<repl::ReplOperation>::const_iterator packTransactionStatementsForApplyOps(
@@ -1024,7 +1024,17 @@ std::vector<repl::ReplOperation>::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;
}