diff options
author | Kaloian Manassiev <kaloian.manassiev@mongodb.com> | 2019-07-15 12:52:12 -0400 |
---|---|---|
committer | Kaloian Manassiev <kaloian.manassiev@mongodb.com> | 2019-07-23 17:25:35 -0400 |
commit | e3c26cf2f4c6ea3d597f42360442c0b6320e84bc (patch) | |
tree | 37442818ec0c0555dfd203f330a2abd1fd7ace1a /src/mongo/db | |
parent | 7d687264de65258764dca70ce46754c4765912ce (diff) | |
download | mongo-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.cpp | 19 | ||||
-rw-r--r-- | src/mongo/db/s/transaction_coordinator.h | 2 | ||||
-rw-r--r-- | src/mongo/db/s/transaction_coordinator_test.cpp | 51 | ||||
-rw-r--r-- | src/mongo/db/s/transaction_coordinator_util.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/s/txn_two_phase_commit_cmds.cpp | 23 |
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 |