summaryrefslogtreecommitdiff
path: root/src/mongo/db/transaction/transaction_api_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/transaction/transaction_api_test.cpp')
-rw-r--r--src/mongo/db/transaction/transaction_api_test.cpp165
1 files changed, 165 insertions, 0 deletions
diff --git a/src/mongo/db/transaction/transaction_api_test.cpp b/src/mongo/db/transaction/transaction_api_test.cpp
index b9290d0cd86..4bdbb16a141 100644
--- a/src/mongo/db/transaction/transaction_api_test.cpp
+++ b/src/mongo/db/transaction/transaction_api_test.cpp
@@ -80,6 +80,19 @@ const BSONObj kRetryableWriteConcernError =
const BSONObj kResWithRetryableWriteConcernError =
BSON("ok" << 1 << "writeConcernError" << kRetryableWriteConcernError);
+const BSONObj kResWithTransientCommitErrorAndRetryableWriteConcernError =
+ BSON("ok" << 0 << "code" << ErrorCodes::LockTimeout << kErrorLabelsFieldName
+ << BSON_ARRAY(ErrorLabel::kTransientTransaction) << "writeConcernError"
+ << kRetryableWriteConcernError);
+
+const BSONObj kResWithNonTransientCommitErrorAndRetryableWriteConcernError =
+ BSON("ok" << 0 << "code" << ErrorCodes::NoSuchTransaction << "writeConcernError"
+ << kRetryableWriteConcernError);
+
+const BSONObj kResWithNonTransientCommitErrorAndNonRetryableWriteConcernError =
+ BSON("ok" << 0 << "code" << ErrorCodes::NoSuchTransaction << "writeConcernError"
+ << kWriteConcernError);
+
class MockResourceYielder : public ResourceYielder {
public:
void yield(OperationContext*) {
@@ -194,6 +207,15 @@ public:
MONGO_UNREACHABLE;
}
+ virtual BSONObj runCommandCheckedSync(const DatabaseName& dbName, BSONObj cmd) const override {
+ MONGO_UNREACHABLE;
+ }
+
+ virtual SemiFuture<BSONObj> runCommandChecked(const DatabaseName& dbName,
+ BSONObj cmd) const override {
+ MONGO_UNREACHABLE;
+ }
+
virtual SemiFuture<BatchedCommandResponse> runCRUDOp(
const BatchedCommandRequest& cmd, std::vector<StmtId> stmtIds) const override {
MONGO_UNREACHABLE;
@@ -502,6 +524,15 @@ public:
MONGO_UNREACHABLE;
}
+ virtual BSONObj runCommandCheckedSync(const DatabaseName& dbName, BSONObj cmd) const override {
+ MONGO_UNREACHABLE;
+ }
+
+ virtual SemiFuture<BSONObj> runCommandChecked(const DatabaseName& dbName,
+ BSONObj cmd) const override {
+ MONGO_UNREACHABLE;
+ }
+
virtual SemiFuture<BatchedCommandResponse> runCRUDOp(
const BatchedCommandRequest& cmd, std::vector<StmtId> stmtIds) const override {
MONGO_UNREACHABLE;
@@ -1070,6 +1101,49 @@ TEST_F(TxnAPITest, OwnSession_CommitError) {
ASSERT_EQ(lastRequest.firstElementFieldNameStringData(), "commitTransaction"_sd);
}
+TEST_F(TxnAPITest, DoesNotRetryOnNonTransientCommitErrorWithNonRetryableCommitWCError) {
+ auto swResult = txnWithRetries().runNoThrow(
+ opCtx(), [&](const txn_api::TransactionClient& txnClient, ExecutorPtr txnExec) {
+ mockClient()->setNextCommandResponse(kOKInsertResponse);
+ auto insertRes =
+ txnClient
+ .runCommand(DatabaseName::createDatabaseName_forTest(boost::none, "user"_sd),
+ BSON("insert"
+ << "foo"
+ << "documents" << BSON_ARRAY(BSON("x" << 1))))
+ .get();
+ ASSERT_OK(getStatusFromWriteCommandReply(insertRes));
+
+ ASSERT_EQ(insertRes["n"].Int(), 1); // Verify the mocked response was returned.
+ assertTxnMetadata(
+ mockClient()->getLastSentRequest(), 0 /* txnNumber */, true /* startTransaction */);
+ assertSessionIdMetadata(mockClient()->getLastSentRequest(), LsidAssertion::kStandalone);
+
+ // The commit response.
+ mockClient()->setNextCommandResponse(
+ kResWithNonTransientCommitErrorAndNonRetryableWriteConcernError);
+
+ return SemiFuture<void>::makeReady();
+ });
+ ASSERT(swResult.getStatus().isOK());
+ ASSERT_EQ(swResult.getValue().cmdStatus, ErrorCodes::NoSuchTransaction);
+ ASSERT_EQ(swResult.getValue().wcError.toStatus(), ErrorCodes::WriteConcernFailed);
+ ASSERT_EQ(swResult.getValue().getEffectiveStatus(), ErrorCodes::NoSuchTransaction);
+
+ ASSERT_EQ(1, InternalTransactionMetrics::get(opCtx())->getStarted());
+ ASSERT_EQ(0, InternalTransactionMetrics::get(opCtx())->getRetriedTransactions());
+ ASSERT_EQ(0, InternalTransactionMetrics::get(opCtx())->getRetriedCommits());
+ ASSERT_EQ(0, InternalTransactionMetrics::get(opCtx())->getSucceeded());
+
+ auto lastRequest = mockClient()->getLastSentRequest();
+ assertTxnMetadata(lastRequest,
+ 0 /* txnNumber */,
+ boost::none /* startTransaction */,
+ boost::none /* readConcern */,
+ WriteConcernOptions().toBSON() /* writeConcern */);
+ ASSERT_EQ(lastRequest.firstElementFieldNameStringData(), "commitTransaction"_sd);
+}
+
TEST_F(TxnAPITest, OwnSession_TransientCommitError) {
int attempt = -1;
auto swResult = txnWithRetries().runNoThrow(
@@ -1243,6 +1317,97 @@ TEST_F(TxnAPITest, OwnSession_RetryableCommitWCError) {
ASSERT_EQ(lastRequest.firstElementFieldNameStringData(), "commitTransaction"_sd);
}
+TEST_F(TxnAPITest, RetriesOnNonTransientCommitWithErrorRetryableCommitWCError) {
+ auto swResult = txnWithRetries().runNoThrow(
+ opCtx(), [&](const txn_api::TransactionClient& txnClient, ExecutorPtr txnExec) {
+ mockClient()->setNextCommandResponse(kOKInsertResponse);
+ auto insertRes =
+ txnClient
+ .runCommand(DatabaseName::createDatabaseName_forTest(boost::none, "user"_sd),
+ BSON("insert"
+ << "foo"
+ << "documents" << BSON_ARRAY(BSON("x" << 1))))
+ .get();
+ ASSERT_OK(getStatusFromWriteCommandReply(insertRes));
+
+ ASSERT_EQ(insertRes["n"].Int(), 1); // Verify the mocked response was returned.
+ assertTxnMetadata(
+ mockClient()->getLastSentRequest(), 0 /* txnNumber */, true /* startTransaction */);
+ assertSessionIdMetadata(mockClient()->getLastSentRequest(), LsidAssertion::kStandalone);
+
+ // The commit responses.
+ mockClient()->setNextCommandResponse(
+ kResWithNonTransientCommitErrorAndRetryableWriteConcernError);
+ mockClient()->setNextCommandResponse(kOKCommandResponse);
+ return SemiFuture<void>::makeReady();
+ });
+ ASSERT(swResult.getStatus().isOK());
+ ASSERT(swResult.getValue().getEffectiveStatus().isOK());
+
+ ASSERT_EQ(1, InternalTransactionMetrics::get(opCtx())->getStarted());
+ ASSERT_EQ(0, InternalTransactionMetrics::get(opCtx())->getRetriedTransactions());
+ ASSERT_EQ(1, InternalTransactionMetrics::get(opCtx())->getRetriedCommits());
+ ASSERT_EQ(1, InternalTransactionMetrics::get(opCtx())->getSucceeded());
+
+ auto lastRequest = mockClient()->getLastSentRequest();
+ assertTxnMetadata(lastRequest,
+ 0 /* txnNumber */,
+ boost::none /* startTransaction */,
+ boost::none /* readConcern */,
+ CommandHelpers::kMajorityWriteConcern.toBSON());
+ assertSessionIdMetadata(mockClient()->getLastSentRequest(), LsidAssertion::kStandalone);
+ ASSERT_EQ(lastRequest.firstElementFieldNameStringData(), "commitTransaction"_sd);
+}
+
+TEST_F(TxnAPITest, RetriesOnTransientCommitErrorWithRetryableWCError) {
+ int attempt = -1;
+ auto swResult = txnWithRetries().runNoThrow(
+ opCtx(), [&](const txn_api::TransactionClient& txnClient, ExecutorPtr txnExec) {
+ attempt += 1;
+ mockClient()->setNextCommandResponse(kOKInsertResponse);
+ auto insertRes =
+ txnClient
+ .runCommand(DatabaseName::createDatabaseName_forTest(boost::none, "user"_sd),
+ BSON("insert"
+ << "foo"
+ << "documents" << BSON_ARRAY(BSON("x" << 1))))
+ .get();
+ ASSERT_OK(getStatusFromWriteCommandReply(insertRes));
+
+ ASSERT_EQ(insertRes["n"].Int(), 1); // Verify the mocked response was returned.
+ assertTxnMetadata(mockClient()->getLastSentRequest(),
+ attempt /* txnNumber */,
+ true /* startTransaction */);
+ assertSessionIdMetadata(mockClient()->getLastSentRequest(), LsidAssertion::kStandalone);
+
+ // Set commit response. Initial commit response is a transient txn error so the
+ // transaction retries.
+ if (attempt == 0) {
+ mockClient()->setNextCommandResponse(
+ kResWithTransientCommitErrorAndRetryableWriteConcernError);
+ } else {
+ mockClient()->setNextCommandResponse(kOKCommandResponse);
+ }
+ return SemiFuture<void>::makeReady();
+ });
+ ASSERT(swResult.getStatus().isOK());
+ ASSERT(swResult.getValue().getEffectiveStatus().isOK());
+
+ ASSERT_EQ(1, InternalTransactionMetrics::get(opCtx())->getStarted());
+ ASSERT_EQ(1, InternalTransactionMetrics::get(opCtx())->getRetriedTransactions());
+ ASSERT_EQ(0, InternalTransactionMetrics::get(opCtx())->getRetriedCommits());
+ ASSERT_EQ(1, InternalTransactionMetrics::get(opCtx())->getSucceeded());
+
+ auto lastRequest = mockClient()->getLastSentRequest();
+ assertTxnMetadata(lastRequest,
+ attempt /* txnNumber */,
+ boost::none /* startTransaction */,
+ boost::none /* readConcern */,
+ WriteConcernOptions().toBSON() /* writeConcern */);
+ assertSessionIdMetadata(mockClient()->getLastSentRequest(), LsidAssertion::kStandalone);
+ ASSERT_EQ(lastRequest.firstElementFieldNameStringData(), "commitTransaction"_sd);
+}
+
TEST_F(TxnAPITest, RunNoErrors) {
txnWithRetries().run(opCtx(),
[&](const txn_api::TransactionClient& txnClient, ExecutorPtr txnExec) {