diff options
-rw-r--r-- | jstests/replsets/tenant_migration_concurrent_reads.js | 64 | ||||
-rw-r--r-- | jstests/replsets/tenant_migration_concurrent_writes.js | 11 | ||||
-rw-r--r-- | src/mongo/db/error_labels.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/error_labels_test.cpp | 39 |
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 */, |