summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Shuvalov <andrew.shuvalov@mongodb.com>2020-12-10 17:47:15 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-12-16 23:08:42 +0000
commitd5743ba8411a50534d33bc2e940377fb003dccea (patch)
tree27a3c8d0d84d192b300cb908b765668fd73cef04
parent97a76a9bab552e35e9bc3249d33a4c82ff354fee (diff)
downloadmongo-d5743ba8411a50534d33bc2e940377fb003dccea.tar.gz
SERVER-52947: Tenant migration donor should attach TransientTransactionError when returning TenantMigrationCommitted or TenantMigrationAborted on a transaction operation
-rw-r--r--jstests/replsets/tenant_migration_concurrent_reads.js64
-rw-r--r--jstests/replsets/tenant_migration_concurrent_writes.js11
-rw-r--r--src/mongo/db/error_labels.cpp10
-rw-r--r--src/mongo/db/error_labels_test.cpp39
4 files changed, 110 insertions, 14 deletions
diff --git a/jstests/replsets/tenant_migration_concurrent_reads.js b/jstests/replsets/tenant_migration_concurrent_reads.js
index 9b7642d0a21..21e3edb3ee2 100644
--- a/jstests/replsets/tenant_migration_concurrent_reads.js
+++ b/jstests/replsets/tenant_migration_concurrent_reads.js
@@ -47,11 +47,22 @@ function resumeMigrationAfterBlockingRead(host, targetBlockedReads) {
{configureFailPoint: "pauseTenantMigrationAfterBlockingStarts", mode: "off"}));
}
-function runCommand(db, cmd, expectedError) {
+function runCommand(db, cmd, expectedError, isTransaction) {
const res = db.runCommand(cmd);
if (expectedError) {
assert.commandFailedWithCode(res, expectedError);
+ // The 'TransientTransactionError' label is attached only in a scope of a transaction.
+ if (isTransaction &&
+ (expectedError == ErrorCodes.TenantMigrationAborted ||
+ expectedError == ErrorCodes.TenantMigrationCommitted)) {
+ assert(res["errorLabels"] != null, "Error labels are absent from " + tojson(res));
+ const expectedErrorLabels = ['TransientTransactionError'];
+ assert.sameMembers(res["errorLabels"],
+ expectedErrorLabels,
+ "Error labels " + tojson(res["errorLabels"]) +
+ " are different from expected " + expectedErrorLabels);
+ }
} else {
assert.commandWorked(res);
}
@@ -92,12 +103,17 @@ function testReadIsRejectedIfSentAfterMigrationHasCommitted(testCase, dbName, co
if (testCase.requiresReadTimestamp) {
runCommand(db,
testCase.command(collName, donorDoc.blockTimestamp),
- ErrorCodes.TenantMigrationCommitted);
+ ErrorCodes.TenantMigrationCommitted,
+ testCase.isTransaction);
runCommand(db,
testCase.command(collName, donorDoc.commitOrAbortOpTime.ts),
- ErrorCodes.TenantMigrationCommitted);
+ ErrorCodes.TenantMigrationCommitted,
+ testCase.isTransaction);
} else {
- runCommand(db, testCase.command(collName), ErrorCodes.TenantMigrationCommitted);
+ runCommand(db,
+ testCase.command(collName),
+ ErrorCodes.TenantMigrationCommitted,
+ testCase.isTransaction);
}
});
}
@@ -133,10 +149,16 @@ function testReadIsAcceptedIfSentAfterMigrationHasAborted(testCase, dbName, coll
nodes.forEach(node => {
const db = node.getDB(dbName);
if (testCase.requiresReadTimestamp) {
- runCommand(db, testCase.command(collName, donorDoc.blockTimestamp));
- runCommand(db, testCase.command(collName, donorDoc.commitOrAbortOpTime.ts));
+ runCommand(db,
+ testCase.command(collName, donorDoc.blockTimestamp),
+ null,
+ testCase.isTransaction);
+ runCommand(db,
+ testCase.command(collName, donorDoc.commitOrAbortOpTime.ts),
+ null,
+ testCase.isTransaction);
} else {
- runCommand(db, testCase.command(collName));
+ runCommand(db, testCase.command(collName), null, testCase.isTransaction);
}
});
}
@@ -177,7 +199,10 @@ function testReadBlocksIfMigrationIsInBlocking(testCase, dbName, collName) {
const nodes = testCase.isSupportedOnSecondaries ? donorRst.nodes : [donorPrimary];
nodes.forEach(node => {
const db = node.getDB(dbName);
- runCommand(db, command, testCase.isLinearizableRead ? null : ErrorCodes.MaxTimeMSExpired);
+ runCommand(db,
+ command,
+ testCase.isLinearizableRead ? null : ErrorCodes.MaxTimeMSExpired,
+ testCase.isTransaction);
});
blockingFp.off();
@@ -238,7 +263,7 @@ function testBlockedReadGetsUnblockedAndRejectedIfMigrationCommits(testCase, dbN
// is rejected.
nodes.forEach(node => {
const db = node.getDB(dbName);
- runCommand(db, command, ErrorCodes.TenantMigrationCommitted);
+ runCommand(db, command, ErrorCodes.TenantMigrationCommitted, testCase.isTransaction);
});
// Verify that the migration succeeded.
@@ -301,7 +326,7 @@ function testBlockedReadGetsUnblockedAndSucceedsIfMigrationAborts(testCase, dbNa
// unblocks.
nodes.forEach(node => {
const db = node.getDB(dbName);
- runCommand(db, command);
+ runCommand(db, command, null, testCase.isTransaction);
});
// Verify that the migration failed due to the simulated error.
@@ -340,6 +365,7 @@ const testCases = {
snapshotReadWithAtClusterTimeInTxn: {
isSupportedOnSecondaries: false,
requiresReadTimestamp: true,
+ isTransaction: true,
command: function(collName, readTimestamp) {
return {
find: collName,
@@ -353,6 +379,7 @@ const testCases = {
},
snapshotReadWithoutAtClusterTimeInTxn: {
isSupportedOnSecondaries: false,
+ isTransaction: true,
command: function(collName) {
return {
find: collName,
@@ -376,6 +403,23 @@ const testCases = {
};
},
},
+ readWithAfterClusterTimeInTxn: {
+ isSupportedOnSecondaries: false,
+ requiresReadTimestamp: true,
+ isTransaction: true,
+ command: function(collName, readTimestamp) {
+ return {
+ find: collName,
+ lsid: {id: UUID()},
+ txnNumber: NumberLong(0),
+ startTransaction: true,
+ autocommit: false,
+ readConcern: {
+ afterClusterTime: readTimestamp,
+ }
+ };
+ },
+ },
linearizableRead: {
isSupportedOnSecondaries: false,
isLinearizableRead: true,
diff --git a/jstests/replsets/tenant_migration_concurrent_writes.js b/jstests/replsets/tenant_migration_concurrent_writes.js
index f02a541fb5c..b5e1b6a118b 100644
--- a/jstests/replsets/tenant_migration_concurrent_writes.js
+++ b/jstests/replsets/tenant_migration_concurrent_writes.js
@@ -217,6 +217,17 @@ function runCommand(testOpts, expectedError) {
if (expectedError) {
assert.commandFailedWithCode(res, expectedError);
+ // The 'TransientTransactionError' label is attached only in a scope of a transaction.
+ if (testOpts.testInTransaction &&
+ (expectedError == ErrorCodes.TenantMigrationAborted ||
+ expectedError == ErrorCodes.TenantMigrationCommitted)) {
+ assert(res["errorLabels"] != null, "Error labels are absent from " + tojson(res));
+ const expectedErrorLabels = ['TransientTransactionError'];
+ assert.sameMembers(res["errorLabels"],
+ expectedErrorLabels,
+ "Error labels " + tojson(res["errorLabels"]) +
+ " are different from expected " + expectedErrorLabels);
+ }
} else {
assert.commandWorked(res);
}
diff --git a/src/mongo/db/error_labels.cpp b/src/mongo/db/error_labels.cpp
index 2055ad6ef94..d6413300548 100644
--- a/src/mongo/db/error_labels.cpp
+++ b/src/mongo/db/error_labels.cpp
@@ -173,15 +173,17 @@ bool isTransientTransactionError(ErrorCodes::Error code,
case ErrorCodes::WriteConflict:
case ErrorCodes::LockTimeout:
case ErrorCodes::PreparedTransactionInProgress:
- isTransient = true;
- break;
+ case ErrorCodes::ShardInvalidatedForTargeting:
+ case ErrorCodes::StaleDbVersion:
+ case ErrorCodes::TenantMigrationAborted:
+ case ErrorCodes::TenantMigrationCommitted:
+ return true;
default:
isTransient = false;
break;
}
- isTransient |= ErrorCodes::isSnapshotError(code) || ErrorCodes::isNeedRetargettingError(code) ||
- code == ErrorCodes::ShardInvalidatedForTargeting || code == ErrorCodes::StaleDbVersion;
+ isTransient |= ErrorCodes::isSnapshotError(code) || ErrorCodes::isNeedRetargettingError(code);
if (isCommitOrAbort) {
// On NoSuchTransaction it's safe to retry the whole transaction only if the data cannot be
diff --git a/src/mongo/db/error_labels_test.cpp b/src/mongo/db/error_labels_test.cpp
index a9d4cc27f0c..2bf321fab1c 100644
--- a/src/mongo/db/error_labels_test.cpp
+++ b/src/mongo/db/error_labels_test.cpp
@@ -39,6 +39,45 @@
namespace mongo {
namespace {
+TEST(IsTransientTransactionErrorTest, WriteConflictIsTransient) {
+ ASSERT_TRUE(isTransientTransactionError(
+ ErrorCodes::WriteConflict, false /* hasWriteConcernError */, false /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest, LockTimeoutIsTransient) {
+ ASSERT_TRUE(isTransientTransactionError(
+ ErrorCodes::LockTimeout, false /* hasWriteConcernError */, false /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest, PreparedTransactionInProgressIsTransient) {
+ ASSERT_TRUE(isTransientTransactionError(ErrorCodes::PreparedTransactionInProgress,
+ false /* hasWriteConcernError */,
+ false /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest, TenantMigrationCommittedIsTransient) {
+ ASSERT_TRUE(isTransientTransactionError(ErrorCodes::TenantMigrationCommitted,
+ false /* hasWriteConcernError */,
+ false /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest, TenantMigrationAbortedIsTransient) {
+ ASSERT_TRUE(isTransientTransactionError(ErrorCodes::TenantMigrationAborted,
+ false /* hasWriteConcernError */,
+ false /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest, ShardInvalidatedForTargetingIsTransient) {
+ ASSERT_TRUE(isTransientTransactionError(ErrorCodes::ShardInvalidatedForTargeting,
+ false /* hasWriteConcernError */,
+ false /* isCommitOrAbort */));
+}
+
+TEST(IsTransientTransactionErrorTest, StaleDbVersionIsTransient) {
+ ASSERT_TRUE(isTransientTransactionError(
+ ErrorCodes::StaleDbVersion, false /* hasWriteConcernError */, false /* isCommitOrAbort */));
+}
+
TEST(IsTransientTransactionErrorTest, NetworkErrorsAreTransientBeforeCommit) {
ASSERT_TRUE(isTransientTransactionError(ErrorCodes::HostUnreachable,
false /* hasWriteConcernError */,