summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorKaloian Manassiev <kaloian.manassiev@mongodb.com>2019-07-15 12:52:12 -0400
committerKaloian Manassiev <kaloian.manassiev@mongodb.com>2019-07-23 17:25:35 -0400
commite3c26cf2f4c6ea3d597f42360442c0b6320e84bc (patch)
tree37442818ec0c0555dfd203f330a2abd1fd7ace1a /src/mongo/db
parent7d687264de65258764dca70ce46754c4765912ce (diff)
downloadmongo-e3c26cf2f4c6ea3d597f42360442c0b6320e84bc.tar.gz
SERVER-41369 Terminate the TransactionCoordinator retry logic if read concern majority is not available
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/s/transaction_coordinator.cpp19
-rw-r--r--src/mongo/db/s/transaction_coordinator.h2
-rw-r--r--src/mongo/db/s/transaction_coordinator_test.cpp51
-rw-r--r--src/mongo/db/s/transaction_coordinator_util.cpp2
-rw-r--r--src/mongo/db/s/txn_two_phase_commit_cmds.cpp23
5 files changed, 80 insertions, 17 deletions
diff --git a/src/mongo/db/s/transaction_coordinator.cpp b/src/mongo/db/s/transaction_coordinator.cpp
index 3085db606e4..eaf91e54c06 100644
--- a/src/mongo/db/s/transaction_coordinator.cpp
+++ b/src/mongo/db/s/transaction_coordinator.cpp
@@ -249,19 +249,28 @@ TransactionCoordinator::TransactionCoordinator(ServiceContext* serviceContext,
_serviceContext->getPreciseClockSource()->now());
}
- _decisionPromise.emplaceValue(_decision->getDecision());
-
switch (_decision->getDecision()) {
- case CommitDecision::kCommit:
+ case CommitDecision::kCommit: {
+ _decisionPromise.emplaceValue(CommitDecision::kCommit);
+
return txn::sendCommit(_serviceContext,
*_scheduler,
_lsid,
_txnNumber,
*_participants,
*_decision->getCommitTimestamp());
- case CommitDecision::kAbort:
+ }
+ case CommitDecision::kAbort: {
+ const auto& abortStatus = *_decision->getAbortStatus();
+
+ if (abortStatus == ErrorCodes::ReadConcernMajorityNotEnabled)
+ _decisionPromise.setError(abortStatus);
+ else
+ _decisionPromise.emplaceValue(CommitDecision::kAbort);
+
return txn::sendAbort(
_serviceContext, *_scheduler, _lsid, _txnNumber, *_participants);
+ }
default:
MONGO_UNREACHABLE;
};
@@ -324,7 +333,7 @@ void TransactionCoordinator::continueCommit(const TransactionCoordinatorDocument
_kickOffCommitPromise.emplaceValue();
}
-SharedSemiFuture<CommitDecision> TransactionCoordinator::getDecision() {
+SharedSemiFuture<CommitDecision> TransactionCoordinator::getDecision() const {
return _decisionPromise.getFuture();
}
diff --git a/src/mongo/db/s/transaction_coordinator.h b/src/mongo/db/s/transaction_coordinator.h
index 2bfb54e74ba..87b7252a6dc 100644
--- a/src/mongo/db/s/transaction_coordinator.h
+++ b/src/mongo/db/s/transaction_coordinator.h
@@ -98,7 +98,7 @@ public:
*
* TODO (SERVER-37364): Remove this when it is no longer needed by the coordinator service.
*/
- SharedSemiFuture<txn::CommitDecision> getDecision();
+ SharedSemiFuture<txn::CommitDecision> getDecision() const;
/**
* Returns a future which can be listened on for when all the asynchronous activity spawned by
diff --git a/src/mongo/db/s/transaction_coordinator_test.cpp b/src/mongo/db/s/transaction_coordinator_test.cpp
index 32134a82a71..7e88a292067 100644
--- a/src/mongo/db/s/transaction_coordinator_test.cpp
+++ b/src/mongo/db/s/transaction_coordinator_test.cpp
@@ -466,9 +466,29 @@ TEST_F(TransactionCoordinatorDriverTest,
ASSERT(decision.getDecision() == txn::CommitDecision::kAbort);
ASSERT(decision.getAbortStatus());
- ASSERT_EQ(ErrorCodes::InternalError, decision.getAbortStatus()->code());
+ ASSERT_EQ(50993, int(decision.getAbortStatus()->code()));
}
+TEST_F(TransactionCoordinatorDriverTest,
+ SendPrepareReturnsErrorWhenOneShardReturnsReadConcernMajorityNotEnabled) {
+ txn::AsyncWorkScheduler aws(getServiceContext());
+ auto future = txn::sendPrepare(getServiceContext(), aws, _lsid, _txnNumber, kTwoShardIdList);
+
+ assertPrepareSentAndRespondWithSuccess(Timestamp(100, 1));
+ assertCommandSentAndRespondWith(
+ PrepareTransaction::kCommandName,
+ BSON("ok" << 0 << "code" << ErrorCodes::ReadConcernMajorityNotEnabled << "errmsg"
+ << "Read concern majority not enabled"),
+ WriteConcernOptions::Majority);
+
+ auto decision = future.get().decision();
+
+ ASSERT(decision.getDecision() == txn::CommitDecision::kAbort);
+ ASSERT(decision.getAbortStatus());
+ ASSERT_EQ(ErrorCodes::ReadConcernMajorityNotEnabled, decision.getAbortStatus()->code());
+}
+
+
class TransactionCoordinatorDriverPersistenceTest : public TransactionCoordinatorDriverTest {
protected:
void setUp() override {
@@ -901,6 +921,35 @@ TEST_F(TransactionCoordinatorTest,
coordinator.onCompletion().get();
}
+TEST_F(TransactionCoordinatorTest,
+ RunCommitProducesReadConcernMajorityNotEnabledIfEitherShardReturnsIt) {
+ TransactionCoordinator coordinator(
+ getServiceContext(),
+ _lsid,
+ _txnNumber,
+ std::make_unique<txn::AsyncWorkScheduler>(getServiceContext()),
+ Date_t::max());
+ coordinator.runCommit(kTwoShardIdList);
+ auto commitDecisionFuture = coordinator.getDecision();
+
+ // One participant votes commit and other encounters retryable error
+ onCommands({[&](const executor::RemoteCommandRequest& request) { return kPrepareOk; },
+ [&](const executor::RemoteCommandRequest& request) {
+ return BSON("ok" << 0 << "code" << ErrorCodes::ReadConcernMajorityNotEnabled
+ << "errmsg"
+ << "Read concern majority not enabled");
+ }});
+
+ assertAbortSentAndRespondWithSuccess();
+ assertAbortSentAndRespondWithSuccess();
+
+ ASSERT_THROWS_CODE(
+ commitDecisionFuture.get(), AssertionException, ErrorCodes::ReadConcernMajorityNotEnabled);
+
+ coordinator.onCompletion().get();
+}
+
+
class TransactionCoordinatorMetricsTest : public TransactionCoordinatorTestBase {
public:
void setUp() override {
diff --git a/src/mongo/db/s/transaction_coordinator_util.cpp b/src/mongo/db/s/transaction_coordinator_util.cpp
index 0d3bde20418..f49da0ac61f 100644
--- a/src/mongo/db/s/transaction_coordinator_util.cpp
+++ b/src/mongo/db/s/transaction_coordinator_util.cpp
@@ -548,7 +548,7 @@ Future<PrepareResponse> sendPrepareToShard(ServiceContext* service,
auto prepareTimestampField = response.data["prepareTimestamp"];
if (prepareTimestampField.eoo() ||
prepareTimestampField.timestamp().isNull()) {
- Status abortStatus(ErrorCodes::InternalError,
+ Status abortStatus(ErrorCodes::Error(50993),
str::stream()
<< "Coordinator shard received an OK response "
"to prepareTransaction without a "
diff --git a/src/mongo/db/s/txn_two_phase_commit_cmds.cpp b/src/mongo/db/s/txn_two_phase_commit_cmds.cpp
index 1dd07473d67..3cb6b8c1cbe 100644
--- a/src/mongo/db/s/txn_two_phase_commit_cmds.cpp
+++ b/src/mongo/db/s/txn_two_phase_commit_cmds.cpp
@@ -73,20 +73,23 @@ public:
uassertStatusOK(ShardingState::get(opCtx)->canAcceptShardedCommands());
}
- // We automatically fail 'prepareTransaction' against a primary that has
- // 'enableMajorityReadConcern' set to 'false'.
- uassert(50993,
+ // If a node has majority read concern disabled, replication must use the legacy
+ // 'rollbackViaRefetch' algortithm, which does not support prepareTransaction oplog
+ // entries
+ uassert(ErrorCodes::ReadConcernMajorityNotEnabled,
"'prepareTransaction' is not supported with 'enableMajorityReadConcern=false'",
serverGlobalParams.enableMajorityReadConcern);
- // We do not allow preparing a transaction if the replica set has any arbiters.
+ // Replica sets with arbiters are able to continually accept majority writes without
+ // actually being able to commit them (e.g. PSA with a downed secondary), which in turn
+ // will impact the liveness of 2PC transactions
const auto replCoord = repl::ReplicationCoordinator::get(opCtx);
- uassert(50995,
+ uassert(ErrorCodes::ReadConcernMajorityNotEnabled,
"'prepareTransaction' is not supported for replica sets with arbiters",
!replCoord->setContainsArbiter());
- // We do not allow the prepareTransaction command to run on a standalone.
- uassert(51239,
+ // Standalone nodes do not support transactions at all
+ uassert(ErrorCodes::ReadConcernMajorityNotEnabled,
"'prepareTransaction' is not supported on standalone nodes.",
replCoord->isReplEnabled());
@@ -256,8 +259,10 @@ public:
if (coordinatorDecisionFuture) {
auto swCommitDecision = coordinatorDecisionFuture->getNoThrow(opCtx);
- // The coordinator can only return NoSuchTransaction if cancelIfCommitNotYetStarted
- // was called, which can happen in one of 3 cases:
+ // The coordinator can only throw NoSuchTransaction (as opposed to propagating an
+ // Abort decision due to NoSuchTransaction reported by a shard) if
+ // cancelIfCommitNotYetStarted was called, which can happen in one of 3 cases:
+ //
// 1) The deadline to receive coordinateCommit passed
// 2) Transaction with a newer txnNumber started on the session before
// coordinateCommit was received