diff options
Diffstat (limited to 'src/mongo')
34 files changed, 2081 insertions, 2560 deletions
diff --git a/src/mongo/db/catalog/index_catalog_entry_impl.cpp b/src/mongo/db/catalog/index_catalog_entry_impl.cpp index 422cec2a952..2a860ed856d 100644 --- a/src/mongo/db/catalog/index_catalog_entry_impl.cpp +++ b/src/mongo/db/catalog/index_catalog_entry_impl.cpp @@ -154,7 +154,7 @@ bool IndexCatalogEntryImpl::isReady(OperationContext* opCtx) const { // minimumSnapshotVersion on a collection. This means we are unprotected from reading // out-of-sync index catalog entries. To fix this, we uassert if we detect that the // in-memory catalog is out-of-sync with the on-disk catalog. - if (txnParticipant && txnParticipant->inMultiDocumentTransaction()) { + if (txnParticipant && txnParticipant.inMultiDocumentTransaction()) { if (!_catalogIsPresent(opCtx) || _catalogIsReady(opCtx) != _isReady) { uasserted(ErrorCodes::SnapshotUnavailable, str::stream() << "Unable to read from a snapshot due to pending collection" @@ -180,11 +180,11 @@ bool IndexCatalogEntryImpl::isMultikey(OperationContext* opCtx) const { // and the read-path will query this state before determining there is no interesting multikey // state. Note, it's always legal, though potentially wasteful, to return `true`. auto txnParticipant = TransactionParticipant::get(opCtx); - if (!txnParticipant || !txnParticipant->inMultiDocumentTransaction()) { + if (!txnParticipant || !txnParticipant.inMultiDocumentTransaction()) { return false; } - for (const MultikeyPathInfo& path : txnParticipant->getUncommittedMultikeyPathInfos()) { + for (const MultikeyPathInfo& path : txnParticipant.getUncommittedMultikeyPathInfos()) { if (path.nss == NamespaceString(_ns) && path.indexName == _descriptor->indexName()) { return true; } @@ -197,12 +197,12 @@ MultikeyPaths IndexCatalogEntryImpl::getMultikeyPaths(OperationContext* opCtx) c stdx::lock_guard<stdx::mutex> lk(_indexMultikeyPathsMutex); auto txnParticipant = TransactionParticipant::get(opCtx); - if (!txnParticipant || !txnParticipant->inMultiDocumentTransaction()) { + if (!txnParticipant || !txnParticipant.inMultiDocumentTransaction()) { return _indexMultikeyPaths; } MultikeyPaths ret = _indexMultikeyPaths; - for (const MultikeyPathInfo& path : txnParticipant->getUncommittedMultikeyPathInfos()) { + for (const MultikeyPathInfo& path : txnParticipant.getUncommittedMultikeyPathInfos()) { if (path.nss == NamespaceString(_ns) && path.indexName == _descriptor->indexName()) { MultikeyPathTracker::mergeMultikeyPaths(&ret, path.multikeyPaths); } @@ -330,8 +330,8 @@ void IndexCatalogEntryImpl::setMultikey(OperationContext* opCtx, // Keep multikey changes in memory to correctly service later reads using this index. auto txnParticipant = TransactionParticipant::get(opCtx); - if (txnParticipant && txnParticipant->inMultiDocumentTransaction()) { - txnParticipant->addUncommittedMultikeyPathInfo( + if (txnParticipant && txnParticipant.inMultiDocumentTransaction()) { + txnParticipant.addUncommittedMultikeyPathInfo( MultikeyPathInfo{_collection->ns(), _descriptor->indexName(), std::move(paths)}); } } diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp index a74bb8fa063..beaa98a29d4 100644 --- a/src/mongo/db/commands/find_and_modify.cpp +++ b/src/mongo/db/commands/find_and_modify.cpp @@ -308,7 +308,7 @@ public: maybeDisableValidation.emplace(opCtx); const auto txnParticipant = TransactionParticipant::get(opCtx); - const auto inTransaction = txnParticipant && txnParticipant->inMultiDocumentTransaction(); + const auto inTransaction = txnParticipant && txnParticipant.inMultiDocumentTransaction(); uassert(50781, str::stream() << "Cannot write to system collection " << nsString.ns() << " within a transaction.", @@ -324,7 +324,7 @@ public: const auto stmtId = 0; if (opCtx->getTxnNumber() && !inTransaction) { const auto txnParticipant = TransactionParticipant::get(opCtx); - if (auto entry = txnParticipant->checkStatementExecuted(stmtId)) { + if (auto entry = txnParticipant.checkStatementExecuted(opCtx, stmtId)) { RetryableWritesStats::get(opCtx)->incrementRetriedCommandsCount(); RetryableWritesStats::get(opCtx)->incrementRetriedStatementsCount(); parseOplogEntryForFindAndModify(opCtx, args, *entry, &result); diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp index e724e5698dc..4ea78429b54 100644 --- a/src/mongo/db/commands/find_cmd.cpp +++ b/src/mongo/db/commands/find_cmd.cpp @@ -255,12 +255,11 @@ public: uassert(ErrorCodes::InvalidOptions, "It is illegal to open a tailable cursor in a transaction", !txnParticipant || - !(txnParticipant->inMultiDocumentTransaction() && qr->isTailable())); + !(txnParticipant.inMultiDocumentTransaction() && qr->isTailable())); uassert(ErrorCodes::OperationNotSupportedInTransaction, "The 'readOnce' option is not supported within a transaction.", - !txnParticipant || - !txnParticipant->inActiveOrKilledMultiDocumentTransaction() || + !txnParticipant || !txnParticipant.inActiveOrKilledMultiDocumentTransaction() || !qr->isReadOnce()); uassert(ErrorCodes::InvalidOptions, diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp index 3d0ba7fadda..d2bd92e3036 100644 --- a/src/mongo/db/commands/run_aggregate.cpp +++ b/src/mongo/db/commands/run_aggregate.cpp @@ -349,7 +349,7 @@ boost::intrusive_ptr<ExpressionContext> makeExpressionContext( expCtx->tempDir = storageGlobalParams.dbpath + "/_tmp"; auto txnParticipant = TransactionParticipant::get(opCtx); expCtx->inMultiDocumentTransaction = - txnParticipant && txnParticipant->inMultiDocumentTransaction(); + txnParticipant && txnParticipant.inMultiDocumentTransaction(); return expCtx; } @@ -418,7 +418,7 @@ Status runAggregate(OperationContext* opCtx, auto txnParticipant = TransactionParticipant::get(opCtx); // If we are in a multi-document transaction, we intercept the 'readConcern' // assertion in order to provide a more descriptive error message and code. - if (txnParticipant && txnParticipant->inMultiDocumentTransaction()) { + if (txnParticipant && txnParticipant.inMultiDocumentTransaction()) { return {ErrorCodes::OperationNotSupportedInTransaction, ex.toStatus("Operation not permitted in transaction").reason()}; } diff --git a/src/mongo/db/commands/txn_cmds.cpp b/src/mongo/db/commands/txn_cmds.cpp index 7b84b3a210b..ca3cf1edf62 100644 --- a/src/mongo/db/commands/txn_cmds.cpp +++ b/src/mongo/db/commands/txn_cmds.cpp @@ -93,7 +93,7 @@ public: << opCtx->getTxnNumber() << " on session " << opCtx->getLogicalSessionId()->toBSON(); // commitTransaction is retryable. - if (txnParticipant->transactionIsCommitted()) { + if (txnParticipant.transactionIsCommitted()) { // We set the client last op to the last optime observed by the system to ensure that // we wait for the specified write concern on an optime greater than or equal to the // commit oplog entry. @@ -109,7 +109,7 @@ public: uassert(ErrorCodes::NoSuchTransaction, "Transaction isn't in progress", - txnParticipant->inMultiDocumentTransaction()); + txnParticipant.inMultiDocumentTransaction()); CurOpFailpointHelpers::waitWhileFailPointEnabled( &hangBeforeCommitingTxn, opCtx, "hangBeforeCommitingTxn"); @@ -117,10 +117,10 @@ public: auto optionalCommitTimestamp = cmd.getCommitTimestamp(); if (optionalCommitTimestamp) { // commitPreparedTransaction will throw if the transaction is not prepared. - txnParticipant->commitPreparedTransaction(opCtx, optionalCommitTimestamp.get(), {}); + txnParticipant.commitPreparedTransaction(opCtx, optionalCommitTimestamp.get(), {}); } else { // commitUnpreparedTransaction will throw if the transaction is prepared. - txnParticipant->commitUnpreparedTransaction(opCtx); + txnParticipant.commitUnpreparedTransaction(opCtx); } if (MONGO_FAIL_POINT(participantReturnNetworkErrorForCommitAfterExecutingCommitLogic)) { uasserted(ErrorCodes::HostUnreachable, @@ -172,12 +172,12 @@ public: uassert(ErrorCodes::NoSuchTransaction, "Transaction isn't in progress", - txnParticipant->inMultiDocumentTransaction()); + txnParticipant.inMultiDocumentTransaction()); CurOpFailpointHelpers::waitWhileFailPointEnabled( &hangBeforeAbortingTxn, opCtx, "hangBeforeAbortingTxn"); - txnParticipant->abortActiveTransaction(opCtx); + txnParticipant.abortActiveTransaction(opCtx); if (MONGO_FAIL_POINT(participantReturnNetworkErrorForAbortAfterExecutingAbortLogic)) { uasserted(ErrorCodes::HostUnreachable, diff --git a/src/mongo/db/commands/write_commands/write_commands.cpp b/src/mongo/db/commands/write_commands/write_commands.cpp index 5d10a84b0b3..df205c914b0 100644 --- a/src/mongo/db/commands/write_commands/write_commands.cpp +++ b/src/mongo/db/commands/write_commands/write_commands.cpp @@ -262,7 +262,7 @@ private: void _transactionChecks(OperationContext* opCtx) const { auto txnParticipant = TransactionParticipant::get(opCtx); - if (!txnParticipant || !txnParticipant->inMultiDocumentTransaction()) + if (!txnParticipant || !txnParticipant.inMultiDocumentTransaction()) return; uassert(50791, str::stream() << "Cannot write to system collection " << ns().toString() diff --git a/src/mongo/db/db_raii.cpp b/src/mongo/db/db_raii.cpp index 34908ecc23d..fa9bd68f338 100644 --- a/src/mongo/db/db_raii.cpp +++ b/src/mongo/db/db_raii.cpp @@ -334,7 +334,7 @@ LockMode getLockModeForQuery(OperationContext* opCtx, const boost::optional<Name // Use IX locks for autocommit:false multi-statement transactions; otherwise, use IS locks. auto txnParticipant = TransactionParticipant::get(opCtx); - if (txnParticipant && txnParticipant->inMultiDocumentTransaction()) { + if (txnParticipant && txnParticipant.inMultiDocumentTransaction()) { uassert(51071, "Cannot query system.views within a transaction", !nss || !nss->isSystemDotViews()); diff --git a/src/mongo/db/kill_sessions_local.cpp b/src/mongo/db/kill_sessions_local.cpp index 97329a27666..e998ad84cd4 100644 --- a/src/mongo/db/kill_sessions_local.cpp +++ b/src/mongo/db/kill_sessions_local.cpp @@ -86,16 +86,16 @@ void killSessionsAction( void killSessionsAbortUnpreparedTransactions(OperationContext* opCtx, const SessionKiller::Matcher& matcher, ErrorCodes::Error reason) { - killSessionsAction( - opCtx, - matcher, - [](const ObservableSession& session) { - return !TransactionParticipant::get(session.get())->transactionIsPrepared(); - }, - [](OperationContext* opCtx, const SessionToKill& session) { - TransactionParticipant::get(session.get())->abortArbitraryTransaction(); - }, - reason); + killSessionsAction(opCtx, + matcher, + [](const ObservableSession& session) { + return !TransactionParticipant::get(session).transactionIsPrepared(); + }, + [](OperationContext* opCtx, const SessionToKill& session) { + TransactionParticipant::get(session).abortTransactionIfNotPrepared( + opCtx); + }, + reason); } SessionKiller::Result killSessionsLocal(OperationContext* opCtx, @@ -119,31 +119,15 @@ void killAllExpiredTransactions(OperationContext* opCtx) { [when = opCtx->getServiceContext()->getPreciseClockSource()->now()]( const ObservableSession& session) { - return TransactionParticipant::get(session.get())->expired(); + return TransactionParticipant::get(session).expiredAsOf(when); }, [](OperationContext* opCtx, const SessionToKill& session) { - auto txnParticipant = TransactionParticipant::get(session.get()); - - LOG(0) - << "Aborting transaction with txnNumber " << txnParticipant->getActiveTxnNumber() + auto txnParticipant = TransactionParticipant::get(session); + log() + << "Aborting transaction with txnNumber " << txnParticipant.getActiveTxnNumber() << " on session " << session.getSessionId().getId() << " because it has been running for longer than 'transactionLifetimeLimitSeconds'"; - - // The try/catch block below is necessary because expiredAsOf() in the filterFn above - // could return true for expired, but unprepared transaction, but by the time we get to - // actually kill it, the participant could theoretically become prepared (being under - // the SessionCatalog mutex doesn't prevent the concurrently running thread from doing - // preparing the participant). - // - // Then when the execution reaches the killSessionFn, it would find the transaction is - // prepared and not allowed to be killed, which would cause the exception below - try { - txnParticipant->abortArbitraryTransaction(); - } catch (const DBException& ex) { - // TODO(schwerin): Can we catch a more specific exception? - warning() << "May have failed to abort expired transaction on session " - << session.getSessionId().getId() << " due to " << redact(ex.toStatus()); - } + txnParticipant.abortTransactionIfNotPrepared(opCtx); }, ErrorCodes::ExceededTimeLimit); } @@ -155,7 +139,7 @@ void killSessionsLocalShutdownAllTransactions(OperationContext* opCtx) { matcherAllSessions, [](const ObservableSession&) { return true; }, [](OperationContext* opCtx, const SessionToKill& session) { - TransactionParticipant::get(session.get())->shutdown(); + TransactionParticipant::get(session).shutdown(opCtx); }, ErrorCodes::InterruptedAtShutdown); } @@ -163,18 +147,18 @@ void killSessionsLocalShutdownAllTransactions(OperationContext* opCtx) { void killSessionsAbortAllPreparedTransactions(OperationContext* opCtx) { SessionKiller::Matcher matcherAllSessions( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); - killSessionsAction( - opCtx, - matcherAllSessions, - [](const ObservableSession& session) { - // Filter for sessions that have a prepared transaction. - return TransactionParticipant::get(session.get())->transactionIsPrepared(); - }, - [](OperationContext* opCtx, const SessionToKill& session) { - // Abort the prepared transaction and invalidate the session it is - // associated with. - TransactionParticipant::get(session.get())->abortPreparedTransactionForRollback(); - }); + killSessionsAction(opCtx, + matcherAllSessions, + [](const ObservableSession& session) { + // Filter for sessions that have a prepared transaction. + return TransactionParticipant::get(session).transactionIsPrepared(); + }, + [](OperationContext* opCtx, const SessionToKill& session) { + // Abort the prepared transaction and invalidate the session it is + // associated with. + TransactionParticipant::get(session).abortPreparedTransactionForRollback( + opCtx); + }); } void yieldLocksForPreparedTransactions(OperationContext* opCtx) { @@ -187,26 +171,28 @@ void yieldLocksForPreparedTransactions(OperationContext* opCtx) { // to yield their locks. SessionKiller::Matcher matcherAllSessions( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(newOpCtx.get())}); - killSessionsAction( - newOpCtx.get(), - matcherAllSessions, - [](const ObservableSession& session) { - return TransactionParticipant::get(session.get())->transactionIsPrepared(); - }, - [](OperationContext* killerOpCtx, const SessionToKill& session) { - auto const txnParticipant = TransactionParticipant::get(session.get()); - // Yield locks for prepared transactions. - // When scanning and killing operations, all prepared transactions are included in the - // list. Even though new sessions may be created after the scan, none of them can become - // prepared during stepdown, since the RSTL has been enqueued, preventing any new - // writes. - if (txnParticipant->transactionIsPrepared()) { - LOG(3) << "Yielding locks of prepared transaction. SessionId: " - << session.getSessionId().getId() - << " TxnNumber: " << txnParticipant->getActiveTxnNumber(); - txnParticipant->refreshLocksForPreparedTransaction(killerOpCtx, true); - } - }); + killSessionsAction(newOpCtx.get(), + matcherAllSessions, + [](const ObservableSession& session) { + return TransactionParticipant::get(session).transactionIsPrepared(); + }, + [](OperationContext* killerOpCtx, const SessionToKill& session) { + auto txnParticipant = TransactionParticipant::get(session); + // Yield locks for prepared transactions. + // When scanning and killing operations, all prepared transactions are + // included in the + // list. Even though new sessions may be created after the scan, none of + // them can become + // prepared during stepdown, since the RSTL has been enqueued, preventing + // any new + // writes. + if (txnParticipant.transactionIsPrepared()) { + LOG(3) << "Yielding locks of prepared transaction. SessionId: " + << session.getSessionId().getId() + << " TxnNumber: " << txnParticipant.getActiveTxnNumber(); + txnParticipant.refreshLocksForPreparedTransaction(killerOpCtx, true); + } + }); } } // namespace mongo diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp index 8e932a80356..829905e322e 100644 --- a/src/mongo/db/op_observer_impl.cpp +++ b/src/mongo/db/op_observer_impl.cpp @@ -127,16 +127,16 @@ void onWriteOpCompleted(OperationContext* opCtx, if (lastStmtIdWriteOpTime.isNull()) return; - const auto txnParticipant = TransactionParticipant::get(opCtx); + auto txnParticipant = TransactionParticipant::get(opCtx); if (!txnParticipant) return; - txnParticipant->onWriteOpCompletedOnPrimary(opCtx, - *opCtx->getTxnNumber(), - std::move(stmtIdsWritten), - lastStmtIdWriteOpTime, - lastStmtIdWriteDate, - txnState); + txnParticipant.onWriteOpCompletedOnPrimary(opCtx, + *opCtx->getTxnNumber(), + std::move(stmtIdsWritten), + lastStmtIdWriteOpTime, + lastStmtIdWriteDate, + txnState); } /** @@ -192,7 +192,7 @@ OpTimeBundle replLogUpdate(OperationContext* opCtx, const OplogUpdateEntryArgs& if (txnParticipant) { sessionInfo.setSessionId(*opCtx->getLogicalSessionId()); sessionInfo.setTxnNumber(*opCtx->getTxnNumber()); - oplogLink.prevOpTime = txnParticipant->getLastWriteOpTime(); + oplogLink.prevOpTime = txnParticipant.getLastWriteOpTime(); } OpTimeBundle opTimes; @@ -256,7 +256,7 @@ OpTimeBundle replLogDelete(OperationContext* opCtx, if (txnParticipant) { sessionInfo.setSessionId(*opCtx->getLogicalSessionId()); sessionInfo.setTxnNumber(*opCtx->getTxnNumber()); - oplogLink.prevOpTime = txnParticipant->getLastWriteOpTime(); + oplogLink.prevOpTime = txnParticipant.getLastWriteOpTime(); } OpTimeBundle opTimes; @@ -481,9 +481,9 @@ void OpObserverImpl::onInserts(OperationContext* opCtx, std::vector<InsertStatement>::const_iterator first, std::vector<InsertStatement>::const_iterator last, bool fromMigrate) { - const auto txnParticipant = TransactionParticipant::get(opCtx); + auto txnParticipant = TransactionParticipant::get(opCtx); const bool inMultiDocumentTransaction = txnParticipant && opCtx->writesAreReplicated() && - txnParticipant->inMultiDocumentTransaction(); + txnParticipant.inMultiDocumentTransaction(); Date_t lastWriteDate; @@ -499,7 +499,7 @@ void OpObserverImpl::onInserts(OperationContext* opCtx, } for (auto iter = first; iter != last; iter++) { auto operation = OplogEntry::makeInsertOperation(nss, uuid, iter->doc); - txnParticipant->addTransactionOperation(opCtx, operation); + txnParticipant.addTransactionOperation(opCtx, operation); } } else { lastWriteDate = getWallClockTimeForOpLog(opCtx); @@ -564,15 +564,15 @@ void OpObserverImpl::onUpdate(OperationContext* opCtx, const OplogUpdateEntryArg return; } - const auto txnParticipant = TransactionParticipant::get(opCtx); + auto txnParticipant = TransactionParticipant::get(opCtx); const bool inMultiDocumentTransaction = txnParticipant && opCtx->writesAreReplicated() && - txnParticipant->inMultiDocumentTransaction(); + txnParticipant.inMultiDocumentTransaction(); OpTimeBundle opTime; if (inMultiDocumentTransaction) { auto operation = OplogEntry::makeUpdateOperation( args.nss, args.uuid, args.updateArgs.update, args.updateArgs.criteria); - txnParticipant->addTransactionOperation(opCtx, operation); + txnParticipant.addTransactionOperation(opCtx, operation); } else { opTime = replLogUpdate(opCtx, args); onWriteOpCompleted(opCtx, @@ -625,15 +625,15 @@ void OpObserverImpl::onDelete(OperationContext* opCtx, auto& documentKey = documentKeyDecoration(opCtx); invariant(!documentKey.isEmpty()); - const auto txnParticipant = TransactionParticipant::get(opCtx); + auto txnParticipant = TransactionParticipant::get(opCtx); const bool inMultiDocumentTransaction = txnParticipant && opCtx->writesAreReplicated() && - txnParticipant->inMultiDocumentTransaction(); + txnParticipant.inMultiDocumentTransaction(); OpTimeBundle opTime; if (inMultiDocumentTransaction) { auto operation = OplogEntry::makeDeleteOperation(nss, uuid, deletedDoc ? deletedDoc.get() : documentKey); - txnParticipant->addTransactionOperation(opCtx, operation); + txnParticipant.addTransactionOperation(opCtx, operation); } else { opTime = replLogDelete(opCtx, nss, uuid, stmtId, fromMigrate, deletedDoc); onWriteOpCompleted(opCtx, @@ -1000,8 +1000,8 @@ OpTimeBundle logApplyOpsForTransaction(OperationContext* opCtx, sessionInfo.setSessionId(*opCtx->getLogicalSessionId()); sessionInfo.setTxnNumber(*opCtx->getTxnNumber()); - const auto txnParticipant = TransactionParticipant::get(opCtx); - oplogLink.prevOpTime = txnParticipant->getLastWriteOpTime(); + auto txnParticipant = TransactionParticipant::get(opCtx); + oplogLink.prevOpTime = txnParticipant.getLastWriteOpTime(); // Until we support multiple oplog entries per transaction, prevOpTime should always be null. invariant(oplogLink.prevOpTime.isNull()); @@ -1043,8 +1043,8 @@ void logCommitOrAbortForPreparedTransaction(OperationContext* opCtx, sessionInfo.setSessionId(*opCtx->getLogicalSessionId()); sessionInfo.setTxnNumber(*opCtx->getTxnNumber()); - const auto txnParticipant = TransactionParticipant::get(opCtx); - oplogLink.prevOpTime = txnParticipant->getLastWriteOpTime(); + auto txnParticipant = TransactionParticipant::get(opCtx); + oplogLink.prevOpTime = txnParticipant.getLastWriteOpTime(); const StmtId stmtId(1); const auto wallClockTime = getWallClockTimeForOpLog(opCtx); @@ -1123,7 +1123,6 @@ void OpObserverImpl::onTransactionPrepare(OperationContext* opCtx, const OplogSlot& prepareOpTime, std::vector<repl::ReplOperation>& statements) { invariant(opCtx->getTxnNumber()); - invariant(!prepareOpTime.opTime.isNull()); // Don't write oplog entry on secondaries. @@ -1159,11 +1158,11 @@ void OpObserverImpl::onTransactionAbort(OperationContext* opCtx, return; } - const auto txnParticipant = TransactionParticipant::get(opCtx); + auto txnParticipant = TransactionParticipant::get(opCtx); invariant(txnParticipant); if (!abortOplogEntryOpTime) { - invariant(!txnParticipant->transactionIsCommitted()); + invariant(!txnParticipant.transactionIsCommitted()); return; } diff --git a/src/mongo/db/op_observer_impl_test.cpp b/src/mongo/db/op_observer_impl_test.cpp index 46f0bcf1da2..48877eee1a0 100644 --- a/src/mongo/db/op_observer_impl_test.cpp +++ b/src/mongo/db/op_observer_impl_test.cpp @@ -438,17 +438,17 @@ public: * statement id. */ void simulateSessionWrite(OperationContext* opCtx, - TransactionParticipant* txnParticipant, + TransactionParticipant::Participant txnParticipant, NamespaceString nss, TxnNumber txnNum, StmtId stmtId) { - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + txnParticipant.beginOrContinue(opCtx, txnNum, boost::none, boost::none); { AutoGetCollection autoColl(opCtx, nss, MODE_IX); WriteUnitOfWork wuow(opCtx); auto opTime = repl::OpTime(Timestamp(10, 1), 1); // Dummy timestamp. - txnParticipant->onWriteOpCompletedOnPrimary( + txnParticipant.onWriteOpCompletedOnPrimary( opCtx, txnNum, {stmtId}, opTime, Date_t::now(), boost::none); wuow.commit(); } @@ -468,14 +468,14 @@ TEST_F(OpObserverSessionCatalogRollbackTest, auto opCtx = cc().makeOperationContext(); opCtx->setLogicalSessionId(sessionId); MongoDOperationContextSession ocs(opCtx.get()); - const auto txnParticipant = TransactionParticipant::get(opCtx.get()); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx.get()); + txnParticipant.refreshFromStorageIfNeeded(opCtx.get()); // Simulate a write occurring on that session simulateSessionWrite(opCtx.get(), txnParticipant, nss, txnNum, stmtId); // Check that the statement executed - ASSERT(txnParticipant->checkStatementExecutedNoOplogEntryFetch(stmtId)); + ASSERT(txnParticipant.checkStatementExecutedNoOplogEntryFetch(stmtId)); } // Because there are no sessions to rollback, the OpObserver should not invalidate the in-memory @@ -492,8 +492,8 @@ TEST_F(OpObserverSessionCatalogRollbackTest, auto opCtx = cc().makeOperationContext(); opCtx->setLogicalSessionId(sessionId); MongoDOperationContextSession ocs(opCtx.get()); - const auto txnParticipant = TransactionParticipant::get(opCtx.get()); - ASSERT(txnParticipant->checkStatementExecutedNoOplogEntryFetch(stmtId)); + auto txnParticipant = TransactionParticipant::get(opCtx.get()); + ASSERT(txnParticipant.checkStatementExecutedNoOplogEntryFetch(stmtId)); } } @@ -558,8 +558,8 @@ public: opCtx()->setTxnNumber(txnNum()); _sessionCheckout = std::make_unique<MongoDOperationContextSession>(opCtx()); - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), false, true); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, true); } void tearDown() override { @@ -599,12 +599,12 @@ protected: ASSERT_EQ(txnState != boost::none, txnRecordObj.hasField(SessionTxnRecord::kStateFieldName)); - const auto txnParticipant = TransactionParticipant::get(session()); + auto txnParticipant = TransactionParticipant::get(opCtx()); if (!opTime.isNull()) { ASSERT_EQ(opTime, txnRecord.getLastWriteOpTime()); - ASSERT_EQ(opTime, txnParticipant->getLastWriteOpTime()); + ASSERT_EQ(opTime, txnParticipant.getLastWriteOpTime()); } else { - ASSERT_EQ(txnRecord.getLastWriteOpTime(), txnParticipant->getLastWriteOpTime()); + ASSERT_EQ(txnRecord.getLastWriteOpTime(), txnParticipant.getLastWriteOpTime()); } } @@ -668,7 +668,7 @@ TEST_F(OpObserverLargeTransactionTest, TransactionTooLargeWhileCommitting) { auto uuid = CollectionUUID::gen(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // This size is crafted such that two operations of this size are not too big to fit in a single // oplog entry, but two operations plus oplog overhead are too big to fit in a single oplog @@ -681,13 +681,13 @@ TEST_F(OpObserverLargeTransactionTest, TransactionTooLargeWhileCommitting) { BSON( "_id" << 0 << "data" << BSONBinData(halfTransactionData.get(), kHalfTransactionSize, BinDataGeneral))); - txnParticipant->addTransactionOperation(opCtx(), operation); - txnParticipant->addTransactionOperation(opCtx(), operation); + txnParticipant.addTransactionOperation(opCtx(), operation); + txnParticipant.addTransactionOperation(opCtx(), operation); ASSERT_THROWS_CODE(opObserver().onTransactionCommit( opCtx(), boost::none, boost::none, - txnParticipant->retrieveCompletedTransactionOperations(opCtx())), + txnParticipant.retrieveCompletedTransactionOperations(opCtx())), AssertionException, ErrorCodes::TransactionTooLarge); } @@ -698,7 +698,7 @@ TEST_F(OpObserverTransactionTest, TransactionalPrepareTest) { auto uuid1 = CollectionUUID::gen(); auto uuid2 = CollectionUUID::gen(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); WriteUnitOfWork wuow(opCtx()); AutoGetCollection autoColl1(opCtx(), nss1, MODE_IX); @@ -729,12 +729,12 @@ TEST_F(OpObserverTransactionTest, TransactionalPrepareTest) { << "x")); opObserver().onDelete(opCtx(), nss1, uuid1, 0, false, boost::none); - txnParticipant->transitionToPreparedforTest(); + txnParticipant.transitionToPreparedforTest(opCtx()); { WriteUnitOfWork wuow(opCtx()); OplogSlot slot = repl::getNextOpTime(opCtx()); opObserver().onTransactionPrepare( - opCtx(), slot, txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + opCtx(), slot, txnParticipant.retrieveCompletedTransactionOperations(opCtx())); opCtx()->recoveryUnit()->setPrepareTimestamp(slot.opTime.getTimestamp()); } @@ -794,7 +794,7 @@ TEST_F(OpObserverTransactionTest, TransactionalPreparedCommitTest) { << "x"); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); std::vector<InsertStatement> insert; insert.emplace_back(0, doc); @@ -806,11 +806,11 @@ TEST_F(OpObserverTransactionTest, TransactionalPreparedCommitTest) { AutoGetCollection autoColl(opCtx(), nss, MODE_IX); opObserver().onInserts(opCtx(), nss, uuid, insert.begin(), insert.end(), false); - txnParticipant->transitionToPreparedforTest(); + txnParticipant.transitionToPreparedforTest(opCtx()); const auto prepareSlot = repl::getNextOpTime(opCtx()); prepareTimestamp = prepareSlot.opTime.getTimestamp(); opObserver().onTransactionPrepare( - opCtx(), prepareSlot, txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + opCtx(), prepareSlot, txnParticipant.retrieveCompletedTransactionOperations(opCtx())); commitSlot = repl::getNextOpTime(opCtx()); } @@ -819,12 +819,12 @@ TEST_F(OpObserverTransactionTest, TransactionalPreparedCommitTest) { opCtx()->setWriteUnitOfWork(nullptr); opCtx()->lockState()->unsetMaxLockTimeout(); - txnParticipant->transitionToCommittingWithPrepareforTest(); + txnParticipant.transitionToCommittingWithPrepareforTest(opCtx()); opObserver().onTransactionCommit( opCtx(), commitSlot, prepareTimestamp, - txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + txnParticipant.retrieveCompletedTransactionOperations(opCtx())); repl::OplogInterfaceLocal oplogInterface(opCtx(), NamespaceString::kRsOplogNamespace.ns()); auto oplogIter = oplogInterface.makeIterator(); @@ -867,7 +867,7 @@ TEST_F(OpObserverTransactionTest, TransactionalPreparedAbortTest) { << "x"); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); std::vector<InsertStatement> insert; insert.emplace_back(0, doc); @@ -878,10 +878,10 @@ TEST_F(OpObserverTransactionTest, TransactionalPreparedAbortTest) { AutoGetCollection autoColl(opCtx(), nss, MODE_IX); opObserver().onInserts(opCtx(), nss, uuid, insert.begin(), insert.end(), false); - txnParticipant->transitionToPreparedforTest(); + txnParticipant.transitionToPreparedforTest(opCtx()); const auto prepareSlot = repl::getNextOpTime(opCtx()); opObserver().onTransactionPrepare( - opCtx(), prepareSlot, txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + opCtx(), prepareSlot, txnParticipant.retrieveCompletedTransactionOperations(opCtx())); abortSlot = repl::getNextOpTime(opCtx()); } @@ -889,7 +889,7 @@ TEST_F(OpObserverTransactionTest, TransactionalPreparedAbortTest) { opCtx()->setWriteUnitOfWork(nullptr); opCtx()->lockState()->unsetMaxLockTimeout(); opObserver().onTransactionAbort(opCtx(), abortSlot); - txnParticipant->transitionToAbortedWithPrepareforTest(); + txnParticipant.transitionToAbortedWithPrepareforTest(opCtx()); repl::OplogInterfaceLocal oplogInterface(opCtx(), NamespaceString::kRsOplogNamespace.ns()); auto oplogIter = oplogInterface.makeIterator(); @@ -929,7 +929,7 @@ TEST_F(OpObserverTransactionTest, TransactionalUnpreparedAbortTest) { const NamespaceString nss("testDB", "testColl"); const auto uuid = CollectionUUID::gen(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); std::vector<InsertStatement> insert; insert.emplace_back(0, @@ -941,7 +941,7 @@ TEST_F(OpObserverTransactionTest, TransactionalUnpreparedAbortTest) { AutoGetCollection autoColl(opCtx(), nss, MODE_IX); opObserver().onInserts(opCtx(), nss, uuid, insert.begin(), insert.end(), false); - txnParticipant->transitionToAbortedWithoutPrepareforTest(); + txnParticipant.transitionToAbortedWithoutPrepareforTest(opCtx()); opObserver().onTransactionAbort(opCtx(), boost::none); } @@ -953,14 +953,14 @@ TEST_F(OpObserverTransactionTest, TransactionalUnpreparedAbortTest) { TEST_F(OpObserverTransactionTest, PreparingEmptyTransactionLogsEmptyApplyOps) { auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->transitionToPreparedforTest(); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.transitionToPreparedforTest(opCtx()); { WriteUnitOfWork wuow(opCtx()); OplogSlot slot = repl::getNextOpTime(opCtx()); opObserver().onTransactionPrepare( - opCtx(), slot, txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + opCtx(), slot, txnParticipant.retrieveCompletedTransactionOperations(opCtx())); opCtx()->recoveryUnit()->setPrepareTimestamp(slot.opTime.getTimestamp()); } @@ -977,8 +977,8 @@ TEST_F(OpObserverTransactionTest, PreparingEmptyTransactionLogsEmptyApplyOps) { TEST_F(OpObserverTransactionTest, PreparingTransactionWritesToTransactionTable) { auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->transitionToPreparedforTest(); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.transitionToPreparedforTest(opCtx()); repl::OpTime prepareOpTime; { @@ -986,41 +986,41 @@ TEST_F(OpObserverTransactionTest, PreparingTransactionWritesToTransactionTable) OplogSlot slot = repl::getNextOpTime(opCtx()); prepareOpTime = slot.opTime; opObserver().onTransactionPrepare( - opCtx(), slot, txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + opCtx(), slot, txnParticipant.retrieveCompletedTransactionOperations(opCtx())); opCtx()->recoveryUnit()->setPrepareTimestamp(slot.opTime.getTimestamp()); } ASSERT_EQ(prepareOpTime.getTimestamp(), opCtx()->recoveryUnit()->getPrepareTimestamp()); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); assertTxnRecord(txnNum(), prepareOpTime, DurableTxnStateEnum::kPrepared); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); } TEST_F(OpObserverTransactionTest, AbortingUnpreparedTransactionDoesNotWriteToTransactionTable) { auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); opObserver().onTransactionAbort(opCtx(), boost::none); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); // Abort the storage-transaction without calling the OpObserver. - txnParticipant->shutdown(); + txnParticipant.shutdown(opCtx()); assertNoTxnRecord(); } TEST_F(OpObserverTransactionTest, AbortingPreparedTransactionWritesToTransactionTable) { auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); OplogSlot abortSlot; { WriteUnitOfWork wuow(opCtx()); OplogSlot slot = repl::getNextOpTime(opCtx()); opObserver().onTransactionPrepare( - opCtx(), slot, txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + opCtx(), slot, txnParticipant.retrieveCompletedTransactionOperations(opCtx())); opCtx()->recoveryUnit()->setPrepareTimestamp(slot.opTime.getTimestamp()); - txnParticipant->transitionToPreparedforTest(); + txnParticipant.transitionToPreparedforTest(opCtx()); abortSlot = repl::getNextOpTime(opCtx()); } @@ -1028,12 +1028,12 @@ TEST_F(OpObserverTransactionTest, AbortingPreparedTransactionWritesToTransaction opCtx()->setWriteUnitOfWork(nullptr); opCtx()->lockState()->unsetMaxLockTimeout(); opObserver().onTransactionAbort(opCtx(), abortSlot); - txnParticipant->transitionToAbortedWithPrepareforTest(); + txnParticipant.transitionToAbortedWithPrepareforTest(opCtx()); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); // Abort the storage-transaction without calling the OpObserver. - txnParticipant->shutdown(); + txnParticipant.shutdown(opCtx()); assertTxnRecord(txnNum(), {}, DurableTxnStateEnum::kAborted); } @@ -1042,7 +1042,7 @@ TEST_F(OpObserverTransactionTest, CommittingUnpreparedNonEmptyTransactionWritesT const NamespaceString nss("testDB", "testColl"); const auto uuid = CollectionUUID::gen(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); std::vector<InsertStatement> insert; insert.emplace_back(0, @@ -1058,7 +1058,7 @@ TEST_F(OpObserverTransactionTest, CommittingUnpreparedNonEmptyTransactionWritesT opCtx(), boost::none, boost::none, - txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + txnParticipant.retrieveCompletedTransactionOperations(opCtx())); opCtx()->getWriteUnitOfWork()->commit(); assertTxnRecord(txnNum(), {}, DurableTxnStateEnum::kCommitted); @@ -1067,25 +1067,25 @@ TEST_F(OpObserverTransactionTest, CommittingUnpreparedNonEmptyTransactionWritesT TEST_F(OpObserverTransactionTest, CommittingUnpreparedEmptyTransactionDoesNotWriteToTransactionTable) { auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); opObserver().onTransactionCommit( opCtx(), boost::none, boost::none, - txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + txnParticipant.retrieveCompletedTransactionOperations(opCtx())); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); // Abort the storage-transaction without calling the OpObserver. - txnParticipant->shutdown(); + txnParticipant.shutdown(opCtx()); assertNoTxnRecord(); } TEST_F(OpObserverTransactionTest, CommittingPreparedTransactionWritesToTransactionTable) { auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); repl::OpTime prepareOpTime; { @@ -1093,9 +1093,9 @@ TEST_F(OpObserverTransactionTest, CommittingPreparedTransactionWritesToTransacti OplogSlot slot = repl::getNextOpTime(opCtx()); prepareOpTime = slot.opTime; opObserver().onTransactionPrepare( - opCtx(), slot, txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + opCtx(), slot, txnParticipant.retrieveCompletedTransactionOperations(opCtx())); opCtx()->recoveryUnit()->setPrepareTimestamp(slot.opTime.getTimestamp()); - txnParticipant->transitionToPreparedforTest(); + txnParticipant.transitionToPreparedforTest(opCtx()); } OplogSlot commitSlot = repl::getNextOpTime(opCtx()); @@ -1106,12 +1106,12 @@ TEST_F(OpObserverTransactionTest, CommittingPreparedTransactionWritesToTransacti opCtx()->setWriteUnitOfWork(nullptr); opCtx()->lockState()->unsetMaxLockTimeout(); - txnParticipant->transitionToCommittingWithPrepareforTest(); + txnParticipant.transitionToCommittingWithPrepareforTest(opCtx()); opObserver().onTransactionCommit( opCtx(), commitSlot, prepareOpTime.getTimestamp(), - txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + txnParticipant.retrieveCompletedTransactionOperations(opCtx())); assertTxnRecord(txnNum(), commitOpTime, DurableTxnStateEnum::kCommitted); } @@ -1122,7 +1122,7 @@ TEST_F(OpObserverTransactionTest, TransactionalInsertTest) { auto uuid1 = CollectionUUID::gen(); auto uuid2 = CollectionUUID::gen(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); std::vector<InsertStatement> inserts1; inserts1.emplace_back(0, @@ -1147,7 +1147,7 @@ TEST_F(OpObserverTransactionTest, TransactionalInsertTest) { opCtx(), boost::none, boost::none, - txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + txnParticipant.retrieveCompletedTransactionOperations(opCtx())); auto oplogEntryObj = getSingleOplogEntry(opCtx()); checkCommonFields(oplogEntryObj); OplogEntry oplogEntry = assertGet(OplogEntry::parse(oplogEntryObj)); @@ -1199,7 +1199,7 @@ TEST_F(OpObserverTransactionTest, TransactionalUpdateTest) { auto uuid1 = CollectionUUID::gen(); auto uuid2 = CollectionUUID::gen(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "update"); + txnParticipant.unstashTransactionResources(opCtx(), "update"); CollectionUpdateArgs updateArgs1; updateArgs1.stmtId = 0; @@ -1228,7 +1228,7 @@ TEST_F(OpObserverTransactionTest, TransactionalUpdateTest) { opCtx(), boost::none, boost::none, - txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + txnParticipant.retrieveCompletedTransactionOperations(opCtx())); auto oplogEntry = getSingleOplogEntry(opCtx()); checkCommonFields(oplogEntry); auto o = oplogEntry.getObjectField("o"); @@ -1266,7 +1266,7 @@ TEST_F(OpObserverTransactionTest, TransactionalDeleteTest) { auto uuid2 = CollectionUUID::gen(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "delete"); + txnParticipant.unstashTransactionResources(opCtx(), "delete"); WriteUnitOfWork wuow(opCtx()); AutoGetCollection autoColl1(opCtx(), nss1, MODE_IX); @@ -1285,7 +1285,7 @@ TEST_F(OpObserverTransactionTest, TransactionalDeleteTest) { opCtx(), boost::none, boost::none, - txnParticipant->retrieveCompletedTransactionOperations(opCtx())); + txnParticipant.retrieveCompletedTransactionOperations(opCtx())); auto oplogEntry = getSingleOplogEntry(opCtx()); checkCommonFields(oplogEntry); auto o = oplogEntry.getObjectField("o"); diff --git a/src/mongo/db/ops/write_ops_exec.cpp b/src/mongo/db/ops/write_ops_exec.cpp index b093faea33f..3cd83ef34e6 100644 --- a/src/mongo/db/ops/write_ops_exec.cpp +++ b/src/mongo/db/ops/write_ops_exec.cpp @@ -200,7 +200,7 @@ void assertCanWrite_inlock(OperationContext* opCtx, const NamespaceString& ns) { void makeCollection(OperationContext* opCtx, const NamespaceString& ns) { auto txnParticipant = TransactionParticipant::get(opCtx); - auto inTransaction = txnParticipant && txnParticipant->inMultiDocumentTransaction(); + auto inTransaction = txnParticipant && txnParticipant.inMultiDocumentTransaction(); uassert(ErrorCodes::OperationNotSupportedInTransaction, str::stream() << "Cannot create namespace " << ns.ns() << " in multi-document transaction.", @@ -238,16 +238,15 @@ bool handleError(OperationContext* opCtx, } auto txnParticipant = TransactionParticipant::get(opCtx); - if (txnParticipant && txnParticipant->inActiveOrKilledMultiDocumentTransaction()) { + if (txnParticipant && txnParticipant.inActiveOrKilledMultiDocumentTransaction()) { if (isTransientTransactionError( ex.code(), false /* hasWriteConcernError */, false /* isCommitTransaction */)) { // Tell the client to try the whole txn again, by returning ok: 0 with errorLabels. throw; } - // If we are in a transaction, we must fail the whole batch. out->results.emplace_back(ex.toStatus()); - txnParticipant->abortActiveTransaction(opCtx); + txnParticipant.abortActiveTransaction(opCtx); return false; } @@ -333,7 +332,7 @@ void insertDocuments(OperationContext* opCtx, if (supportsDocLocking()) { auto replCoord = repl::ReplicationCoordinator::get(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - auto inTransaction = txnParticipant && txnParticipant->inMultiDocumentTransaction(); + auto inTransaction = txnParticipant && txnParticipant.inMultiDocumentTransaction(); if (!inTransaction && !replCoord->isOplogDisabledFor(opCtx, collection->ns())) { // Populate 'slots' with new optimes for each insert. @@ -406,7 +405,7 @@ bool insertBatchAndHandleErrors(OperationContext* opCtx, try { acquireCollection(); auto txnParticipant = TransactionParticipant::get(opCtx); - auto inTxn = txnParticipant && txnParticipant->inActiveOrKilledMultiDocumentTransaction(); + auto inTxn = txnParticipant && txnParticipant.inActiveOrKilledMultiDocumentTransaction(); if (!collection->getCollection()->isCapped() && !inTxn && batch.size() > 1) { // First try doing it all together. If all goes well, this is all we need to do. // See Collection::_insertDocuments for why we do all capped inserts one-at-a-time. @@ -494,7 +493,7 @@ WriteResult performInserts(OperationContext* opCtx, // transaction. auto txnParticipant = TransactionParticipant::get(opCtx); invariant(!opCtx->lockState()->inAWriteUnitOfWork() || - (txnParticipant && txnParticipant->inActiveOrKilledMultiDocumentTransaction())); + (txnParticipant && txnParticipant.inActiveOrKilledMultiDocumentTransaction())); auto& curOp = *CurOp::get(opCtx); ON_BLOCK_EXIT([&] { // This is the only part of finishCurOp we need to do for inserts because they reuse the @@ -548,8 +547,8 @@ WriteResult performInserts(OperationContext* opCtx, } else { const auto stmtId = getStmtIdForWriteOp(opCtx, wholeOp, stmtIdIndex++); if (opCtx->getTxnNumber()) { - if (!txnParticipant->inMultiDocumentTransaction() && - txnParticipant->checkStatementExecutedNoOplogEntryFetch(stmtId)) { + if (!txnParticipant.inMultiDocumentTransaction() && + txnParticipant.checkStatementExecutedNoOplogEntryFetch(stmtId)) { containsRetry = true; RetryableWritesStats::get(opCtx)->incrementRetriedStatementsCount(); out.results.emplace_back(makeWriteResultForInsertOrDeleteRetry()); @@ -698,7 +697,7 @@ static SingleWriteResult performSingleUpdateOpWithDupKeyRetry(OperationContext* auto txnParticipant = TransactionParticipant::get(opCtx); uassert(ErrorCodes::InvalidOptions, "Cannot use (or request) retryable writes with multi=true", - (txnParticipant && txnParticipant->inMultiDocumentTransaction()) || + (txnParticipant && txnParticipant.inMultiDocumentTransaction()) || !opCtx->getTxnNumber() || !op.getMulti()); UpdateRequest request(ns); @@ -752,7 +751,7 @@ WriteResult performUpdates(OperationContext* opCtx, const write_ops::Update& who // transaction. auto txnParticipant = TransactionParticipant::get(opCtx); invariant(!opCtx->lockState()->inAWriteUnitOfWork() || - (txnParticipant && txnParticipant->inActiveOrKilledMultiDocumentTransaction())); + (txnParticipant && txnParticipant.inActiveOrKilledMultiDocumentTransaction())); uassertStatusOK(userAllowedWriteNS(wholeOp.getNamespace())); DisableDocumentValidationIfTrue docValidationDisabler( @@ -769,8 +768,8 @@ WriteResult performUpdates(OperationContext* opCtx, const write_ops::Update& who for (auto&& singleOp : wholeOp.getUpdates()) { const auto stmtId = getStmtIdForWriteOp(opCtx, wholeOp, stmtIdIndex++); if (opCtx->getTxnNumber()) { - if (!txnParticipant->inMultiDocumentTransaction()) { - if (auto entry = txnParticipant->checkStatementExecuted(stmtId)) { + if (!txnParticipant.inMultiDocumentTransaction()) { + if (auto entry = txnParticipant.checkStatementExecuted(opCtx, stmtId)) { containsRetry = true; RetryableWritesStats::get(opCtx)->incrementRetriedStatementsCount(); out.results.emplace_back(parseOplogEntryForUpdate(*entry)); @@ -812,7 +811,7 @@ static SingleWriteResult performSingleDeleteOp(OperationContext* opCtx, auto txnParticipant = TransactionParticipant::get(opCtx); uassert(ErrorCodes::InvalidOptions, "Cannot use (or request) retryable writes with limit=0", - (txnParticipant && txnParticipant->inMultiDocumentTransaction()) || + (txnParticipant && txnParticipant.inMultiDocumentTransaction()) || !opCtx->getTxnNumber() || !op.getMulti()); globalOpCounters.gotDelete(); @@ -905,7 +904,7 @@ WriteResult performDeletes(OperationContext* opCtx, const write_ops::Delete& who // transaction. auto txnParticipant = TransactionParticipant::get(opCtx); invariant(!opCtx->lockState()->inAWriteUnitOfWork() || - (txnParticipant && txnParticipant->inActiveOrKilledMultiDocumentTransaction())); + (txnParticipant && txnParticipant.inActiveOrKilledMultiDocumentTransaction())); uassertStatusOK(userAllowedWriteNS(wholeOp.getNamespace())); DisableDocumentValidationIfTrue docValidationDisabler( @@ -922,8 +921,8 @@ WriteResult performDeletes(OperationContext* opCtx, const write_ops::Delete& who for (auto&& singleOp : wholeOp.getDeletes()) { const auto stmtId = getStmtIdForWriteOp(opCtx, wholeOp, stmtIdIndex++); if (opCtx->getTxnNumber()) { - if (!txnParticipant->inMultiDocumentTransaction() && - txnParticipant->checkStatementExecutedNoOplogEntryFetch(stmtId)) { + if (!txnParticipant.inMultiDocumentTransaction() && + txnParticipant.checkStatementExecutedNoOplogEntryFetch(stmtId)) { containsRetry = true; RetryableWritesStats::get(opCtx)->incrementRetriedStatementsCount(); out.results.emplace_back(makeWriteResultForInsertOrDeleteRetry()); diff --git a/src/mongo/db/pipeline/process_interface_shardsvr.cpp b/src/mongo/db/pipeline/process_interface_shardsvr.cpp index 45197fd9c4e..befc7a78231 100644 --- a/src/mongo/db/pipeline/process_interface_shardsvr.cpp +++ b/src/mongo/db/pipeline/process_interface_shardsvr.cpp @@ -170,7 +170,7 @@ unique_ptr<Pipeline, PipelineDeleter> MongoInterfaceShardServer::attachCursorSou // cache, and attempt to do a network request while holding locks. // TODO: SERVER-39162 allow $lookup in sharded transactions. auto txnParticipant = TransactionParticipant::get(expCtx->opCtx); - const bool inTxn = txnParticipant && txnParticipant->inMultiDocumentTransaction(); + const bool inTxn = txnParticipant && txnParticipant.inMultiDocumentTransaction(); const bool isSharded = [&]() { if (inTxn || !ShardingState::get(expCtx->opCtx)->enabled()) { diff --git a/src/mongo/db/pipeline/process_interface_standalone.cpp b/src/mongo/db/pipeline/process_interface_standalone.cpp index e394a715592..f282b1928fb 100644 --- a/src/mongo/db/pipeline/process_interface_standalone.cpp +++ b/src/mongo/db/pipeline/process_interface_standalone.cpp @@ -537,7 +537,7 @@ BSONObj MongoInterfaceStandalone::_reportCurrentOpForClient( if (clientOpCtx) { if (auto txnParticipant = TransactionParticipant::get(clientOpCtx)) { - txnParticipant->reportUnstashedState(clientOpCtx, &builder); + txnParticipant.reportUnstashedState(clientOpCtx, &builder); } // Append lock stats before returning. @@ -569,7 +569,7 @@ void MongoInterfaceStandalone::_reportCurrentOpsForIdleSessions(OperationContext sessionCatalog->scanSessions( {std::move(sessionFilter)}, [&](const ObservableSession& session) { - auto op = TransactionParticipant::get(session.get())->reportStashedState(); + auto op = TransactionParticipant::get(session).reportStashedState(opCtx); if (!op.isEmpty()) { ops->emplace_back(op); } diff --git a/src/mongo/db/repl/apply_ops.cpp b/src/mongo/db/repl/apply_ops.cpp index 99c5febc9ba..df196691c2f 100644 --- a/src/mongo/db/repl/apply_ops.cpp +++ b/src/mongo/db/repl/apply_ops.cpp @@ -298,7 +298,7 @@ Status _applyPrepareTransaction(OperationContext* opCtx, MongoDOperationContextSessionWithoutRefresh sessionCheckout(opCtx); auto transaction = TransactionParticipant::get(opCtx); - transaction->unstashTransactionResources(opCtx, "prepareTransaction"); + transaction.unstashTransactionResources(opCtx, "prepareTransaction"); // Apply the operations via applysOps functionality. int numApplied = 0; @@ -315,8 +315,8 @@ Status _applyPrepareTransaction(OperationContext* opCtx, return status; } invariant(!entry.getOpTime().isNull()); - transaction->prepareTransaction(opCtx, entry.getOpTime()); - transaction->stashTransactionResources(opCtx); + transaction.prepareTransaction(opCtx, entry.getOpTime()); + transaction.stashTransactionResources(opCtx); return Status::OK(); } diff --git a/src/mongo/db/repl/do_txn.cpp b/src/mongo/db/repl/do_txn.cpp index 6da0da73846..8cf1ea35f32 100644 --- a/src/mongo/db/repl/do_txn.cpp +++ b/src/mongo/db/repl/do_txn.cpp @@ -263,7 +263,7 @@ Status doTxn(OperationContext* opCtx, BSONObjBuilder* result) { auto txnParticipant = TransactionParticipant::get(opCtx); uassert(ErrorCodes::InvalidOptions, "doTxn must be run within a transaction", txnParticipant); - invariant(txnParticipant->inMultiDocumentTransaction()); + invariant(txnParticipant.inMultiDocumentTransaction()); invariant(opCtx->getWriteUnitOfWork()); uassert( ErrorCodes::InvalidOptions, "doTxn supports only CRUD opts.", _areOpsCrudOnly(doTxnCmd)); @@ -294,10 +294,10 @@ Status doTxn(OperationContext* opCtx, numApplied = 0; uassertStatusOK(_doTxn(opCtx, dbName, doTxnCmd, &intermediateResult, &numApplied)); - txnParticipant->commitUnpreparedTransaction(opCtx); + txnParticipant.commitUnpreparedTransaction(opCtx); result->appendElements(intermediateResult.obj()); } catch (const DBException& ex) { - txnParticipant->abortActiveUnpreparedOrStashPreparedTransaction(opCtx); + txnParticipant.abortActiveUnpreparedOrStashPreparedTransaction(opCtx); BSONArrayBuilder ab; ++numApplied; for (int j = 0; j < numApplied; j++) diff --git a/src/mongo/db/repl/do_txn_test.cpp b/src/mongo/db/repl/do_txn_test.cpp index 39356ad3f00..8bd9f3c6d39 100644 --- a/src/mongo/db/repl/do_txn_test.cpp +++ b/src/mongo/db/repl/do_txn_test.cpp @@ -167,8 +167,8 @@ void DoTxnTest::setUp() { _ocs.emplace(_opCtx.get()); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), false, true); - txnParticipant->unstashTransactionResources(opCtx(), "doTxn"); + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, true); + txnParticipant.unstashTransactionResources(opCtx(), "doTxn"); } void DoTxnTest::tearDown() { diff --git a/src/mongo/db/repl/mock_repl_coord_server_fixture.cpp b/src/mongo/db/repl/mock_repl_coord_server_fixture.cpp index ef2ff337799..72311bd0ff4 100644 --- a/src/mongo/db/repl/mock_repl_coord_server_fixture.cpp +++ b/src/mongo/db/repl/mock_repl_coord_server_fixture.cpp @@ -92,14 +92,6 @@ void MockReplCoordServerFixture::setUp() { stdx::make_unique<repl::DropPendingCollectionReaper>(repl::StorageInterface::get(service))); } -void MockReplCoordServerFixture::tearDown() { - // ServiceContextMongoDTest::tearDown() will try to create it's own opCtx, and it's not - // allowed to have 2 present per client, so destroy this one. - _opCtx.reset(); - - ServiceContextMongoDTest::tearDown(); -} - void MockReplCoordServerFixture::insertOplogEntry(const repl::OplogEntry& entry) { AutoGetCollection autoColl(opCtx(), NamespaceString::kRsOplogNamespace, MODE_IX); auto coll = autoColl.getCollection(); diff --git a/src/mongo/db/repl/mock_repl_coord_server_fixture.h b/src/mongo/db/repl/mock_repl_coord_server_fixture.h index eb91bda6989..9bac2e16d74 100644 --- a/src/mongo/db/repl/mock_repl_coord_server_fixture.h +++ b/src/mongo/db/repl/mock_repl_coord_server_fixture.h @@ -48,7 +48,6 @@ class StorageInterfaceMock; class MockReplCoordServerFixture : public ServiceContextMongoDTest { public: void setUp() override; - void tearDown() override; /** * Helper method for inserting new entries to the oplog. This completely bypasses diff --git a/src/mongo/db/repl/oplog.cpp b/src/mongo/db/repl/oplog.cpp index cd8abb1f2d3..bb31fced7b3 100644 --- a/src/mongo/db/repl/oplog.cpp +++ b/src/mongo/db/repl/oplog.cpp @@ -633,7 +633,7 @@ std::vector<OpTime> logInsertOps(OperationContext* opCtx, if (txnParticipant) { sessionInfo.setSessionId(*opCtx->getLogicalSessionId()); sessionInfo.setTxnNumber(*opCtx->getTxnNumber()); - oplogLink.prevOpTime = txnParticipant->getLastWriteOpTime(); + oplogLink.prevOpTime = txnParticipant.getLastWriteOpTime(); } auto timestamps = stdx::make_unique<Timestamp[]>(count); diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp index b1fdd0ecdad..f32cc8b7142 100644 --- a/src/mongo/db/repl/replication_coordinator_impl.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl.cpp @@ -2179,7 +2179,7 @@ Status ReplicationCoordinatorImpl::checkCanServeReadsFor_UNSAFE(OperationContext } auto txnParticipant = TransactionParticipant::get(opCtx); - if (txnParticipant && txnParticipant->inMultiDocumentTransaction()) { + if (txnParticipant && txnParticipant.inMultiDocumentTransaction()) { if (!_readWriteAbility->canAcceptNonLocalWrites_UNSAFE() && !getTestCommandsEnabled()) { return Status(ErrorCodes::NotMaster, "Multi-document transactions are only allowed on replica set primaries."); diff --git a/src/mongo/db/repl/transaction_oplog_application.cpp b/src/mongo/db/repl/transaction_oplog_application.cpp index 41c62b5e528..5ae5575ae5c 100644 --- a/src/mongo/db/repl/transaction_oplog_application.cpp +++ b/src/mongo/db/repl/transaction_oplog_application.cpp @@ -113,8 +113,8 @@ Status applyCommitTransaction(OperationContext* opCtx, auto transaction = TransactionParticipant::get(opCtx); invariant(transaction); - transaction->unstashTransactionResources(opCtx, "commitTransaction"); - transaction->commitPreparedTransaction( + transaction.unstashTransactionResources(opCtx, "commitTransaction"); + transaction.commitPreparedTransaction( opCtx, commitCommand.getCommitTimestamp(), entry.getOpTime()); return Status::OK(); } @@ -147,8 +147,8 @@ Status applyAbortTransaction(OperationContext* opCtx, MongoDOperationContextSessionWithoutRefresh sessionCheckout(opCtx); auto transaction = TransactionParticipant::get(opCtx); - transaction->unstashTransactionResources(opCtx, "abortTransaction"); - transaction->abortActiveTransaction(opCtx); + transaction.unstashTransactionResources(opCtx, "abortTransaction"); + transaction.abortActiveTransaction(opCtx); return Status::OK(); } diff --git a/src/mongo/db/s/session_catalog_migration_destination.cpp b/src/mongo/db/s/session_catalog_migration_destination.cpp index 8f60227431c..572b10eddcf 100644 --- a/src/mongo/db/s/session_catalog_migration_destination.cpp +++ b/src/mongo/db/s/session_catalog_migration_destination.cpp @@ -251,10 +251,10 @@ ProcessOplogResult processSessionOplog(const BSONObj& oplogBSON, opCtx->setTxnNumber(result.txnNum); MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - txnParticipant->beginOrContinue(result.txnNum, boost::none, boost::none); + txnParticipant.beginOrContinue(opCtx, result.txnNum, boost::none, boost::none); try { - if (txnParticipant->checkStatementExecuted(stmtId)) { + if (txnParticipant.checkStatementExecuted(opCtx, stmtId)) { // Skip the incoming statement because it has already been logged locally return lastResult; } @@ -278,7 +278,7 @@ ProcessOplogResult processSessionOplog(const BSONObj& oplogBSON, ? oplogEntry.getObject() : BSON(SessionCatalogMigrationDestination::kSessionMigrateOplogTag << 1)); auto oplogLink = extractPrePostImageTs(lastResult, oplogEntry); - oplogLink.prevOpTime = txnParticipant->getLastWriteOpTime(); + oplogLink.prevOpTime = txnParticipant.getLastWriteOpTime(); writeConflictRetry( opCtx, @@ -319,7 +319,7 @@ ProcessOplogResult processSessionOplog(const BSONObj& oplogBSON, // Do not call onWriteOpCompletedOnPrimary if we inserted a pre/post image, because the // next oplog will contain the real operation if (!result.isPrePostImage) { - txnParticipant->onMigrateCompletedOnPrimary( + txnParticipant.onMigrateCompletedOnPrimary( opCtx, result.txnNum, {stmtId}, oplogOpTime, *oplogEntry.getWallClockTime()); } diff --git a/src/mongo/db/s/session_catalog_migration_destination_test.cpp b/src/mongo/db/s/session_catalog_migration_destination_test.cpp index 5211c61447c..3b3bb1e3381 100644 --- a/src/mongo/db/s/session_catalog_migration_destination_test.cpp +++ b/src/mongo/db/s/session_catalog_migration_destination_test.cpp @@ -170,7 +170,7 @@ public: opCtx->setTxnNumber(txnNum); MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + txnParticipant.beginOrContinue(opCtx, txnNum, boost::none, boost::none); } void checkOplog(const repl::OplogEntry& originalOplog, const repl::OplogEntry& oplogToCheck) { @@ -196,7 +196,7 @@ public: void checkStatementExecuted(OperationContext* opCtx, TxnNumber txnNumber, StmtId stmtId) { auto txnParticipant = TransactionParticipant::get(opCtx); - auto oplog = txnParticipant->checkStatementExecuted(stmtId); + auto oplog = txnParticipant.checkStatementExecuted(opCtx, stmtId); ASSERT_TRUE(oplog); } @@ -205,7 +205,7 @@ public: StmtId stmtId, const repl::OplogEntry& expectedOplog) { const auto txnParticipant = TransactionParticipant::get(opCtx); - auto oplog = txnParticipant->checkStatementExecuted(stmtId); + auto oplog = txnParticipant.checkStatementExecuted(opCtx, stmtId); ASSERT_TRUE(oplog); checkOplogWithNestedOplog(expectedOplog, *oplog); } @@ -241,7 +241,8 @@ public: innerOpCtx.get(), insertBuilder.obj(), true, true, true, true); MongoDOperationContextSession sessionTxnState(innerOpCtx.get()); auto txnParticipant = TransactionParticipant::get(innerOpCtx.get()); - txnParticipant->beginOrContinue(*sessionInfo.getTxnNumber(), boost::none, boost::none); + txnParticipant.beginOrContinue( + innerOpCtx.get(), *sessionInfo.getTxnNumber(), boost::none, boost::none); const auto reply = performInserts(innerOpCtx.get(), insertRequest); ASSERT(reply.results.size() == 1); @@ -355,7 +356,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, OplogEntriesWithSameTxn) { MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplogWithNestedOplog(oplog3, historyIter.next(opCtx)); @@ -419,7 +420,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, ShouldOnlyStoreHistoryOfLatestTxn MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplogWithNestedOplog(oplog3, historyIter.next(opCtx)); @@ -473,7 +474,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, OplogEntriesWithSameTxnInSeparate MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplogWithNestedOplog(oplog3, historyIter.next(opCtx)); @@ -543,8 +544,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, OplogEntriesWithDifferentSession) MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplogWithNestedOplog(oplog1, historyIter.next(opCtx)); ASSERT_FALSE(historyIter.hasNext()); @@ -562,7 +562,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, OplogEntriesWithDifferentSession) auto txnParticipant = TransactionParticipant::get(opCtx2.get()); - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplogWithNestedOplog(oplog3, historyIter.next(opCtx2.get())); @@ -627,8 +627,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, ShouldNotNestAlreadyNestedOplog) MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplog(oplog2, historyIter.next(opCtx)); @@ -681,7 +680,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, ShouldBeAbleToHandlePreImageFindA auto txnParticipant = TransactionParticipant::get(opCtx); - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); auto nextOplog = historyIter.next(opCtx); @@ -771,8 +770,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, ShouldBeAbleToHandlePostImageFind MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); auto nextOplog = historyIter.next(opCtx); @@ -866,7 +864,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, ShouldBeAbleToHandleFindAndModify auto txnParticipant = TransactionParticipant::get(opCtx); - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); auto nextOplog = historyIter.next(opCtx); @@ -968,7 +966,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, OlderTxnShouldBeIgnored) { auto txnParticipant = TransactionParticipant::get(opCtx); - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); auto oplog = historyIter.next(opCtx); ASSERT_BSONOBJ_EQ(BSON("_id" @@ -1031,8 +1029,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, NewerTxnWriteShouldNotBeOverwritt MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); auto oplog = historyIter.next(opCtx); ASSERT_BSONOBJ_EQ(BSON("_id" @@ -1214,7 +1211,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, auto txnParticipant = TransactionParticipant::get(opCtx); - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplogWithNestedOplog(oplog2, historyIter.next(opCtx)); @@ -1519,8 +1516,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, ShouldIgnoreAlreadyExecutedStatem MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplogWithNestedOplog(oplog3, historyIter.next(opCtx)); @@ -1587,7 +1583,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, OplogEntriesWithIncompleteHistory auto txnParticipant = TransactionParticipant::get(opCtx); - TransactionHistoryIterator historyIter(txnParticipant->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplogWithNestedOplog(oplogEntries[2], historyIter.next(opCtx)); @@ -1601,7 +1597,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, OplogEntriesWithIncompleteHistory checkStatementExecuted(opCtx, 2, 23, oplogEntries[0]); checkStatementExecuted(opCtx, 2, 5, oplogEntries[2]); - ASSERT_THROWS(txnParticipant->checkStatementExecuted(38), AssertionException); + ASSERT_THROWS(txnParticipant.checkStatementExecuted(opCtx, 38), AssertionException); } TEST_F(SessionCatalogMigrationDestinationTest, @@ -1619,8 +1615,8 @@ TEST_F(SessionCatalogMigrationDestinationTest, MongoDOperationContextSession ocs(opCtx); auto txnParticipant = TransactionParticipant::get(opCtx); - txnParticipant->refreshFromStorageIfNeeded(); - txnParticipant->beginOrContinue(3, boost::none, boost::none); + txnParticipant.refreshFromStorageIfNeeded(opCtx); + txnParticipant.beginOrContinue(opCtx, 3, boost::none, boost::none); } OperationSessionInfo sessionInfo2; @@ -1676,7 +1672,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, setUpSessionWithTxn(opCtx, *sessionInfo1.getSessionId(), 3); MongoDOperationContextSession ocs(opCtx); auto txnParticipant1 = TransactionParticipant::get(opCtx); - ASSERT(txnParticipant1->getLastWriteOpTime().isNull()); + ASSERT(txnParticipant1.getLastWriteOpTime().isNull()); } // Check session 2 was correctly updated @@ -1689,7 +1685,7 @@ TEST_F(SessionCatalogMigrationDestinationTest, MongoDOperationContextSession ocs(opCtx); auto txnParticipant2 = TransactionParticipant::get(opCtx); - TransactionHistoryIterator historyIter(txnParticipant2->getLastWriteOpTime()); + TransactionHistoryIterator historyIter(txnParticipant2.getLastWriteOpTime()); ASSERT_TRUE(historyIter.hasNext()); checkOplogWithNestedOplog(oplogEntries[2], historyIter.next(opCtx)); 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 db3c1d7c7cf..276c569fd66 100644 --- a/src/mongo/db/s/txn_two_phase_commit_cmds.cpp +++ b/src/mongo/db/s/txn_two_phase_commit_cmds.cpp @@ -88,7 +88,7 @@ public: "'prepareTransaction' is not supported for replica sets with arbiters", !replCoord->setContainsArbiter()); - const auto txnParticipant = TransactionParticipant::get(opCtx); + auto txnParticipant = TransactionParticipant::get(opCtx); uassert(ErrorCodes::CommandFailed, "prepareTransaction must be run within a transaction", txnParticipant); @@ -105,11 +105,11 @@ public: uassert(ErrorCodes::NoSuchTransaction, "Transaction isn't in progress", - txnParticipant->inMultiDocumentTransaction()); + txnParticipant.inMultiDocumentTransaction()); - if (txnParticipant->transactionIsPrepared()) { + if (txnParticipant.transactionIsPrepared()) { auto& replClient = repl::ReplClientInfo::forClient(opCtx->getClient()); - auto prepareOpTime = txnParticipant->getPrepareOpTime(); + auto prepareOpTime = txnParticipant.getPrepareOpTime(); // Set the client optime to be prepareOpTime if it's not already later than // prepareOpTime. This ensures that we wait for writeConcern and that prepareOpTime @@ -133,7 +133,7 @@ public: return PrepareTimestamp(prepareOpTime.getTimestamp()); } - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx, {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx, {}); if (MONGO_FAIL_POINT( participantReturnNetworkErrorForPrepareAfterExecutingPrepareLogic)) { uasserted(ErrorCodes::HostUnreachable, diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index a02c831bfb0..776bdbe5aef 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -372,13 +372,14 @@ void appendClusterAndOperationTime(OperationContext* opCtx, void invokeWithSessionCheckedOut(OperationContext* opCtx, CommandInvocation* invocation, - TransactionParticipant* txnParticipant, + TransactionParticipant::Participant txnParticipant, const OperationSessionInfoFromClient& sessionOptions, rpc::ReplyBuilderInterface* replyBuilder) { if (!opCtx->getClient()->isInDirectClient()) { - txnParticipant->beginOrContinue(*sessionOptions.getTxnNumber(), - sessionOptions.getAutocommit(), - sessionOptions.getStartTransaction()); + txnParticipant.beginOrContinue(opCtx, + *sessionOptions.getTxnNumber(), + sessionOptions.getAutocommit(), + sessionOptions.getStartTransaction()); // Create coordinator if needed. If "startTransaction" is present, it must be true. if (sessionOptions.getStartTransaction()) { // If this shard has been selected as the coordinator, set up the coordinator state @@ -386,18 +387,18 @@ void invokeWithSessionCheckedOut(OperationContext* opCtx, if (sessionOptions.getCoordinator() == boost::optional<bool>(true)) { createTransactionCoordinator(opCtx, *sessionOptions.getTxnNumber()); } - } else if (txnParticipant->inMultiDocumentTransaction()) { + } else if (txnParticipant.inMultiDocumentTransaction()) { const auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx); uassert(ErrorCodes::InvalidOptions, "Only the first command in a transaction may specify a readConcern", readConcernArgs.isEmpty()); } - txnParticipant->unstashTransactionResources(opCtx, invocation->definition()->getName()); + txnParticipant.unstashTransactionResources(opCtx, invocation->definition()->getName()); } auto guard = makeGuard([&txnParticipant, opCtx] { - txnParticipant->abortActiveUnpreparedOrStashPreparedTransaction(opCtx); + txnParticipant.abortActiveUnpreparedOrStashPreparedTransaction(opCtx); }); try { @@ -420,7 +421,7 @@ void invokeWithSessionCheckedOut(OperationContext* opCtx, // If this shard has completed an earlier statement for this transaction, it must already be // in the transaction's participant list, so it is guaranteed to learn its outcome. - txnParticipant->stashTransactionResources(opCtx); + txnParticipant.stashTransactionResources(opCtx); guard.dismiss(); throw; } @@ -433,7 +434,7 @@ void invokeWithSessionCheckedOut(OperationContext* opCtx, } // Stash or commit the transaction when the command succeeds. - txnParticipant->stashTransactionResources(opCtx); + txnParticipant.stashTransactionResources(opCtx); guard.dismiss(); } diff --git a/src/mongo/db/session.h b/src/mongo/db/session.h index 53b9343ea54..bd9d4d0dcbd 100644 --- a/src/mongo/db/session.h +++ b/src/mongo/db/session.h @@ -68,8 +68,8 @@ private: // A pointer back to the currently running operation on this Session, or nullptr if there // is no operation currently running for the Session. // - // May be read by holders of the SessionCatalog mutex. May only be set when clear or cleared - // when set, and the opCtx being set or cleared must have its client locked at the time. + // This field is only safe to read or write while holding the SessionCatalog::_mutex. In + // practice, it is only used inside of the SessionCatalog itself. OperationContext* _checkoutOpCtx{nullptr}; // Counter indicating the number of times ObservableSession::kill has been called on this diff --git a/src/mongo/db/session_catalog.cpp b/src/mongo/db/session_catalog.cpp index 2ea94f9d5c6..cade0e53405 100644 --- a/src/mongo/db/session_catalog.cpp +++ b/src/mongo/db/session_catalog.cpp @@ -89,10 +89,7 @@ SessionCatalog::ScopedCheckedOutSession SessionCatalog::_checkOutSession(Operati return !osession.currentOperation() && !osession._killed(); }); - { - stdx::lock_guard<Client> lockClient(*opCtx->getClient()); - sri->session._checkoutOpCtx = opCtx; - } + sri->session._checkoutOpCtx = opCtx; return ScopedCheckedOutSession( *this, std::move(sri), boost::none /* Not checked out for kill */); @@ -114,10 +111,7 @@ SessionCatalog::SessionToKill SessionCatalog::checkOutSessionForKill(OperationCo return !ObservableSession(ul, sri->session).currentOperation(); }); - { - stdx::lock_guard<Client> lockClient(*opCtx->getClient()); - sri->session._checkoutOpCtx = opCtx; - } + sri->session._checkoutOpCtx = opCtx; return SessionToKill(ScopedCheckedOutSession(*this, std::move(sri), std::move(killToken))); } @@ -162,10 +156,7 @@ void SessionCatalog::_releaseSession(std::shared_ptr<SessionCatalog::SessionRunt // operation context (meaning checked-out) invariant(_sessions[sri->session.getSessionId()] == sri); invariant(sri->session._checkoutOpCtx); - { - stdx::lock_guard<Client> lockClient(*sri->session._checkoutOpCtx->getClient()); - sri->session._checkoutOpCtx = nullptr; - } + sri->session._checkoutOpCtx = nullptr; sri->availableCondVar.notify_all(); if (killToken) { @@ -185,10 +176,9 @@ SessionCatalog::KillToken ObservableSession::kill(ErrorCodes::Error reason) cons // For currently checked-out sessions, interrupt the operation context so that the current owner // can release the session if (firstKiller && _session->_checkoutOpCtx) { - stdx::lock_guard<Client> lg(*_session->_checkoutOpCtx->getClient()); - + invariant(_clientLock); const auto serviceContext = _session->_checkoutOpCtx->getServiceContext(); - serviceContext->killOperation(lg, _session->_checkoutOpCtx, reason); + serviceContext->killOperation(_clientLock, _session->_checkoutOpCtx, reason); } return SessionCatalog::KillToken(getSessionId()); diff --git a/src/mongo/db/session_catalog.h b/src/mongo/db/session_catalog.h index 7309b3e7ec3..23d656579f5 100644 --- a/src/mongo/db/session_catalog.h +++ b/src/mongo/db/session_catalog.h @@ -248,8 +248,8 @@ public: /** * Increments the number of "killers" for this session and returns a 'kill token' to to be * passed later on to 'checkOutSessionForKill' method of the SessionCatalog in order to permit - * the caller to execute any kill cleanup tasks. This token is later on passed to - * '_markNotKilled' in order to decrement the number of "killers". + * the caller to execute any kill cleanup tasks. This token is later used to decrement the + * number of "killers". * * Marking session as killed is an internal property only that will cause any further calls to * 'checkOutSession' to block until 'checkOutSessionForKill' is called the same number of times @@ -277,23 +277,18 @@ private: return {}; } - ObservableSession(WithLock wl, Session& session) : _session(&session) {} + ObservableSession(WithLock wl, Session& session) + : _session(&session), _clientLock(_lockClientForSession(std::move(wl), _session)) {} /** * Returns whether 'kill' has been called on this session. */ bool _killed() const; - /** - * Used by the session catalog when checking a session back in after a call to 'kill'. See the - * comments for 'kill for more details. - */ - void _markNotKilled(WithLock sessionCatalogLock, SessionCatalog::KillToken killToken); - Session* _session; + stdx::unique_lock<Client> _clientLock; }; - /** * Scoped object, which checks out the session specified in the passed operation context and stores * it for later access by the command. The session is installed at construction time and is removed diff --git a/src/mongo/db/session_catalog_mongod.cpp b/src/mongo/db/session_catalog_mongod.cpp index ddc8c2aa6bc..7ed1d5359af 100644 --- a/src/mongo/db/session_catalog_mongod.cpp +++ b/src/mongo/db/session_catalog_mongod.cpp @@ -92,8 +92,7 @@ void killSessionTokensFunction( for (auto& sessionKillToken : *sessionKillTokens) { auto session = catalog->checkOutSessionForKill(opCtx, std::move(sessionKillToken)); - auto const txnParticipant = TransactionParticipant::get(session.get()); - txnParticipant->invalidate(); + TransactionParticipant::get(session).invalidate(opCtx); } })); } @@ -117,12 +116,12 @@ void MongoDSessionCatalog::onStepUp(OperationContext* opCtx) { SessionKiller::Matcher matcher( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); catalog->scanSessions(matcher, [&](const ObservableSession& session) { - const auto txnParticipant = TransactionParticipant::get(session.get()); - if (!txnParticipant->inMultiDocumentTransaction()) { + const auto txnParticipant = TransactionParticipant::get(session); + if (!txnParticipant.inMultiDocumentTransaction()) { sessionKillTokens->emplace_back(session.kill()); } - if (txnParticipant->transactionIsPrepared()) { + if (txnParticipant.transactionIsPrepared()) { sessionIdToReacquireLocks.emplace_back(session.getSessionId()); } }); @@ -136,11 +135,10 @@ void MongoDSessionCatalog::onStepUp(OperationContext* opCtx) { auto newOpCtx = cc().makeOperationContext(); newOpCtx->setLogicalSessionId(sessionId); MongoDOperationContextSession ocs(newOpCtx.get()); - auto txnParticipant = - TransactionParticipant::get(OperationContextSession::get(newOpCtx.get())); + auto txnParticipant = TransactionParticipant::get(newOpCtx.get()); LOG(3) << "Restoring locks of prepared transaction. SessionId: " << sessionId.getId() - << " TxnNumber: " << txnParticipant->getActiveTxnNumber(); - txnParticipant->refreshLocksForPreparedTransaction(newOpCtx.get(), false); + << " TxnNumber: " << txnParticipant.getActiveTxnNumber(); + txnParticipant.refreshLocksForPreparedTransaction(newOpCtx.get(), false); } } @@ -220,15 +218,15 @@ MongoDOperationContextSession::MongoDOperationContextSession(OperationContext* o : _operationContextSession(opCtx) { invariant(!opCtx->getClient()->isInDirectClient()); - const auto txnParticipant = TransactionParticipant::get(opCtx); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx); + txnParticipant.refreshFromStorageIfNeeded(opCtx); } MongoDOperationContextSession::~MongoDOperationContextSession() = default; void MongoDOperationContextSession::checkIn(OperationContext* opCtx) { if (auto txnParticipant = TransactionParticipant::get(opCtx)) { - txnParticipant->stashTransactionResources(opCtx); + txnParticipant.stashTransactionResources(opCtx); } OperationContextSession::checkIn(opCtx); @@ -238,7 +236,7 @@ void MongoDOperationContextSession::checkOut(OperationContext* opCtx, const std: OperationContextSession::checkOut(opCtx); if (auto txnParticipant = TransactionParticipant::get(opCtx)) { - txnParticipant->unstashTransactionResources(opCtx, cmdName); + txnParticipant.unstashTransactionResources(opCtx, cmdName); } } @@ -248,8 +246,8 @@ MongoDOperationContextSessionWithoutRefresh::MongoDOperationContextSessionWithou invariant(!opCtx->getClient()->isInDirectClient()); const auto clientTxnNumber = *opCtx->getTxnNumber(); - const auto txnParticipant = TransactionParticipant::get(opCtx); - txnParticipant->beginOrContinueTransactionUnconditionally(clientTxnNumber); + auto txnParticipant = TransactionParticipant::get(opCtx); + txnParticipant.beginOrContinueTransactionUnconditionally(opCtx, clientTxnNumber); } MongoDOperationContextSessionWithoutRefresh::~MongoDOperationContextSessionWithoutRefresh() = diff --git a/src/mongo/db/transaction_participant.cpp b/src/mongo/db/transaction_participant.cpp index 8443a5bbd1a..ce890615628 100644 --- a/src/mongo/db/transaction_participant.cpp +++ b/src/mongo/db/transaction_participant.cpp @@ -289,22 +289,20 @@ MONGO_FAIL_POINT_DEFINE(onPrimaryTransactionalWrite); const BSONObj TransactionParticipant::kDeadEndSentinel(BSON("$incompleteOplogHistory" << 1)); -TransactionParticipant::TransactionParticipant() = default; -TransactionParticipant::~TransactionParticipant() = default; +TransactionParticipant::Observer::Observer(const ObservableSession& osession) + : Observer(&getTransactionParticipant(osession.get())) {} -TransactionParticipant* TransactionParticipant::get(OperationContext* opCtx) { - auto session = OperationContextSession::get(opCtx); - if (!session) { - return nullptr; - } - - return get(session); -} +TransactionParticipant::Participant::Participant(OperationContext* opCtx) + : Observer([opCtx]() -> TransactionParticipant* { + if (auto session = OperationContextSession::get(opCtx)) { + return &getTransactionParticipant(session); + } + return nullptr; + }()) {} -TransactionParticipant* TransactionParticipant::get(Session* session) { - return &getTransactionParticipant(session); -} +TransactionParticipant::Participant::Participant(const SessionToKill& session) + : Observer(&getTransactionParticipant(session.get())) {} void TransactionParticipant::performNoopWriteForNoSuchTransaction(OperationContext* opCtx) { repl::ReplicationCoordinator* replCoord = @@ -337,51 +335,49 @@ void TransactionParticipant::performNoopWriteForNoSuchTransaction(OperationConte } } -const LogicalSessionId& TransactionParticipant::_sessionId() const { - const auto* owningSession = getTransactionParticipant.owner(this); +const LogicalSessionId& TransactionParticipant::Observer::_sessionId() const { + const auto* owningSession = getTransactionParticipant.owner(_tp); return owningSession->getSessionId(); } -OperationContext* TransactionParticipant::_opCtx() const { - const auto* owningSession = getTransactionParticipant.owner(this); - auto* opCtx = owningSession->currentOperation_forTest(); - invariant(opCtx); - return opCtx; -} - -void TransactionParticipant::_beginOrContinueRetryableWrite(WithLock wl, TxnNumber txnNumber) { - if (txnNumber > _activeTxnNumber) { +void TransactionParticipant::Participant::_beginOrContinueRetryableWrite(OperationContext* opCtx, + TxnNumber txnNumber) { + if (txnNumber > o().activeTxnNumber) { // New retryable write. - _setNewTxnNumber(wl, txnNumber); - _autoCommit = boost::none; + _setNewTxnNumber(opCtx, txnNumber); + p().autoCommit = boost::none; } else { // Retrying a retryable write. uassert(ErrorCodes::InvalidOptions, "Must specify autocommit=false on all operations of a multi-statement transaction.", - _txnState.isNone(wl)); - invariant(_autoCommit == boost::none); + o().txnState.isNone()); + invariant(p().autoCommit == boost::none); } } -void TransactionParticipant::_continueMultiDocumentTransaction(WithLock wl, TxnNumber txnNumber) { +void TransactionParticipant::Participant::_continueMultiDocumentTransaction(OperationContext* opCtx, + TxnNumber txnNumber) { uassert(ErrorCodes::NoSuchTransaction, str::stream() << "Given transaction number " << txnNumber << " does not match any in-progress transactions. The active transaction number is " - << _activeTxnNumber, - txnNumber == _activeTxnNumber && !_txnState.isNone(wl)); + << o().activeTxnNumber, + txnNumber == o().activeTxnNumber && !o().txnState.isNone()); - if (_txnState.isInProgress(wl) && !_txnResourceStash) { + if (o().txnState.isInProgress() && !o().txnResourceStash) { // This indicates that the first command in the transaction failed but did not implicitly // abort the transaction. It is not safe to continue the transaction, in particular because // we have not saved the readConcern from the first statement of the transaction. Mark the // transaction as active here, since _abortTransactionOnSession() will assume we are // aborting an active transaction since there are no stashed resources. - _transactionMetricsObserver.onUnstash( - ServerTransactionsMetrics::get(getGlobalServiceContext()), - getGlobalServiceContext()->getTickSource()); - _abortTransactionOnSession(wl); + { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).transactionMetricsObserver.onUnstash( + ServerTransactionsMetrics::get(opCtx->getServiceContext()), + opCtx->getServiceContext()->getTickSource()); + } + _abortTransactionOnSession(opCtx); uasserted(ErrorCodes::NoSuchTransaction, str::stream() << "Transaction " << txnNumber << " has been aborted."); @@ -390,54 +386,51 @@ void TransactionParticipant::_continueMultiDocumentTransaction(WithLock wl, TxnN return; } -void TransactionParticipant::_beginMultiDocumentTransaction(WithLock wl, TxnNumber txnNumber) { +void TransactionParticipant::Participant::_beginMultiDocumentTransaction(OperationContext* opCtx, + TxnNumber txnNumber) { // Aborts any in-progress txns. - _setNewTxnNumber(wl, txnNumber); - _autoCommit = false; + _setNewTxnNumber(opCtx, txnNumber); + p().autoCommit = false; - _txnState.transitionTo(wl, TransactionState::kInProgress); + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).txnState.transitionTo(TransactionState::kInProgress); // Start tracking various transactions metrics. // // We measure the start time in both microsecond and millisecond resolution. The TickSource // provides microsecond resolution to record the duration of the transaction. The start "wall // clock" time can be considered an approximation to the microsecond measurement. - auto now = getGlobalServiceContext()->getPreciseClockSource()->now(); - auto tickSource = getGlobalServiceContext()->getTickSource(); + auto now = opCtx->getServiceContext()->getPreciseClockSource()->now(); + auto tickSource = opCtx->getServiceContext()->getTickSource(); - _transactionExpireDate = now + Seconds(transactionLifetimeLimitSeconds.load()); + o(lk).transactionExpireDate = now + Seconds(transactionLifetimeLimitSeconds.load()); - { - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.onStart( - ServerTransactionsMetrics::get(getGlobalServiceContext()), - *_autoCommit, - tickSource, - now, - *_transactionExpireDate); - } - invariant(_transactionOperations.empty()); + o(lk).transactionMetricsObserver.onStart( + ServerTransactionsMetrics::get(opCtx->getServiceContext()), + *p().autoCommit, + tickSource, + now, + *o().transactionExpireDate); + invariant(p().transactionOperations.empty()); } -void TransactionParticipant::beginOrContinue(TxnNumber txnNumber, - boost::optional<bool> autocommit, - boost::optional<bool> startTransaction) { - stdx::lock_guard<stdx::mutex> lg(_mutex); - _checkValid(lg); - +void TransactionParticipant::Participant::beginOrContinue(OperationContext* opCtx, + TxnNumber txnNumber, + boost::optional<bool> autocommit, + boost::optional<bool> startTransaction) { uassert(ErrorCodes::TransactionTooOld, str::stream() << "Cannot start transaction " << txnNumber << " on session " << _sessionId() << " because a newer transaction " - << _activeTxnNumber + << o().activeTxnNumber << " has already started.", - txnNumber >= _activeTxnNumber); + txnNumber >= o().activeTxnNumber); // Requests without an autocommit field are interpreted as retryable writes. They cannot specify // startTransaction, which is verified earlier when parsing the request. if (!autocommit) { invariant(!startTransaction); - _beginOrContinueRetryableWrite(lg, txnNumber); + _beginOrContinueRetryableWrite(opCtx, txnNumber); return; } @@ -447,7 +440,7 @@ void TransactionParticipant::beginOrContinue(TxnNumber txnNumber, invariant(*autocommit == false); if (!startTransaction) { - _continueMultiDocumentTransaction(lg, txnNumber); + _continueMultiDocumentTransaction(opCtx, txnNumber); return; } @@ -456,7 +449,7 @@ void TransactionParticipant::beginOrContinue(TxnNumber txnNumber, // as true, which is verified earlier, when parsing the request. invariant(*startTransaction); - if (txnNumber == _activeTxnNumber) { + if (txnNumber == o().activeTxnNumber) { // Servers in a sharded cluster can start a new transaction at the active transaction number // to allow internal retries by routers on re-targeting errors, like // StaleShard/DatabaseVersion or SnapshotTooOld. @@ -473,29 +466,29 @@ void TransactionParticipant::beginOrContinue(TxnNumber txnNumber, str::stream() << "Cannot start a transaction at given transaction number " << txnNumber << " a transaction with the same number is in state " - << _txnState.toString(), - _txnState.isInSet(lg, restartableStates)); + << o().txnState.toString(), + o().txnState.isInSet(restartableStates)); } - _beginMultiDocumentTransaction(lg, txnNumber); + _beginMultiDocumentTransaction(opCtx, txnNumber); } -void TransactionParticipant::beginOrContinueTransactionUnconditionally(TxnNumber txnNumber) { - stdx::lock_guard<stdx::mutex> lg(_mutex); +void TransactionParticipant::Participant::beginOrContinueTransactionUnconditionally( + OperationContext* opCtx, TxnNumber txnNumber) { // We don't check or fetch any on-disk state, so treat the transaction as 'valid' for the // purposes of this method and continue the transaction unconditionally - _isValid = true; + p().isValid = true; - if (_activeTxnNumber != txnNumber) { - _beginMultiDocumentTransaction(lg, txnNumber); + if (o().activeTxnNumber != txnNumber) { + _beginMultiDocumentTransaction(opCtx, txnNumber); } } -void TransactionParticipant::_setSpeculativeTransactionOpTime( - WithLock, OperationContext* opCtx, SpeculativeTransactionOpTime opTimeChoice) { +void TransactionParticipant::Participant::_setSpeculativeTransactionOpTime( + OperationContext* opCtx, SpeculativeTransactionOpTime opTimeChoice) { repl::ReplicationCoordinator* replCoord = - repl::ReplicationCoordinator::get(opCtx->getClient()->getServiceContext()); + repl::ReplicationCoordinator::get(opCtx->getServiceContext()); opCtx->recoveryUnit()->setTimestampReadSource( opTimeChoice == SpeculativeTransactionOpTime::kAllCommitted ? RecoveryUnit::ReadSource::kAllCommittedSnapshot @@ -504,23 +497,22 @@ void TransactionParticipant::_setSpeculativeTransactionOpTime( auto readTimestamp = repl::StorageInterface::get(opCtx)->getPointInTimeReadTimestamp(opCtx); // Transactions do not survive term changes, so combining "getTerm" here with the // recovery unit timestamp does not cause races. - _speculativeTransactionReadOpTime = {readTimestamp, replCoord->getTerm()}; - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.onChooseReadTimestamp(readTimestamp); + p().speculativeTransactionReadOpTime = {readTimestamp, replCoord->getTerm()}; + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).transactionMetricsObserver.onChooseReadTimestamp(readTimestamp); } -void TransactionParticipant::_setSpeculativeTransactionReadTimestamp(WithLock, - OperationContext* opCtx, - Timestamp timestamp) { +void TransactionParticipant::Participant::_setSpeculativeTransactionReadTimestamp( + OperationContext* opCtx, Timestamp timestamp) { // Read concern code should have already set the timestamp on the recovery unit. invariant(timestamp == opCtx->recoveryUnit()->getPointInTimeReadTimestamp()); repl::ReplicationCoordinator* replCoord = repl::ReplicationCoordinator::get(opCtx->getClient()->getServiceContext()); opCtx->recoveryUnit()->preallocateSnapshot(); - _speculativeTransactionReadOpTime = {timestamp, replCoord->getTerm()}; - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.onChooseReadTimestamp(timestamp); + p().speculativeTransactionReadOpTime = {timestamp, replCoord->getTerm()}; + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).transactionMetricsObserver.onChooseReadTimestamp(timestamp); } TransactionParticipant::OplogSlotReserver::OplogSlotReserver(OperationContext* opCtx) @@ -591,9 +583,11 @@ TransactionParticipant::OplogSlotReserver::~OplogSlotReserver() { replCoord->attemptToAdvanceStableTimestamp(); } -TransactionParticipant::TxnResources::TxnResources(OperationContext* opCtx, StashStyle stashStyle) { - // We must lock the Client to change the Locker on the OperationContext. - stdx::lock_guard<Client> lk(*opCtx->getClient()); +TransactionParticipant::TxnResources::TxnResources(WithLock wl, + OperationContext* opCtx, + StashStyle stashStyle) noexcept { + // We must hold the Client lock to change the Locker on the OperationContext. Hence the + // WithLock. _ruState = opCtx->getWriteUnitOfWork()->release(); opCtx->setWriteUnitOfWork(nullptr); @@ -686,123 +680,122 @@ void TransactionParticipant::TxnResources::release(OperationContext* opCtx) { TransactionParticipant::SideTransactionBlock::SideTransactionBlock(OperationContext* opCtx) : _opCtx(opCtx) { if (_opCtx->getWriteUnitOfWork()) { + stdx::lock_guard<Client> lk(*_opCtx->getClient()); _txnResources = TransactionParticipant::TxnResources( - _opCtx, TxnResources::StashStyle::kSideTransaction); + lk, _opCtx, TxnResources::StashStyle::kSideTransaction); } } TransactionParticipant::SideTransactionBlock::~SideTransactionBlock() { if (_txnResources) { - // Restore the transaction state onto '_opCtx'. _txnResources->release(_opCtx); } } -void TransactionParticipant::_stashActiveTransaction(WithLock, OperationContext* opCtx) { - if (_inShutdown) { +void TransactionParticipant::Participant::_stashActiveTransaction(OperationContext* opCtx) { + if (p().inShutdown) { return; } - invariant(_activeTxnNumber == opCtx->getTxnNumber()); + invariant(o().activeTxnNumber == opCtx->getTxnNumber()); + + stdx::lock_guard<Client> lk(*opCtx->getClient()); { - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); auto tickSource = opCtx->getServiceContext()->getTickSource(); - _transactionMetricsObserver.onStash(ServerTransactionsMetrics::get(opCtx), tickSource); - _transactionMetricsObserver.onTransactionOperation( + o(lk).transactionMetricsObserver.onStash(ServerTransactionsMetrics::get(opCtx), tickSource); + o(lk).transactionMetricsObserver.onTransactionOperation( opCtx->getClient(), CurOp::get(opCtx)->debug().additiveMetrics, CurOp::get(opCtx)->debug().storageStats); } - invariant(!_txnResourceStash); + invariant(!o().txnResourceStash); auto stashStyle = opCtx->writesAreReplicated() ? TxnResources::StashStyle::kPrimary : TxnResources::StashStyle::kSecondary; - _txnResourceStash = TxnResources(opCtx, stashStyle); + o(lk).txnResourceStash = TxnResources(lk, opCtx, stashStyle); } -void TransactionParticipant::stashTransactionResources(OperationContext* opCtx) { +void TransactionParticipant::Participant::stashTransactionResources(OperationContext* opCtx) { if (opCtx->getClient()->isInDirectClient()) { return; } - invariant(opCtx->getTxnNumber()); - stdx::unique_lock<stdx::mutex> lg(_mutex); - - // Always check session's txnNumber, since it can be modified by migration, which does not - // check out the session. We intentionally do not error if the transaction is aborted, since we - // expect this function to be called at the end of the 'abortTransaction' command. - _checkIsActiveTransaction(lg, *opCtx->getTxnNumber(), false); - if (!_txnState.inMultiDocumentTransaction(lg)) { - // Not in a multi-document transaction: nothing to do. - return; + if (o().txnState.inMultiDocumentTransaction()) { + _stashActiveTransaction(opCtx); } - - _stashActiveTransaction(lg, opCtx); } -void TransactionParticipant::unstashTransactionResources(OperationContext* opCtx, - const std::string& cmdName) { +void TransactionParticipant::Participant::_releaseTransactionResourcesToOpCtx( + OperationContext* opCtx) { + // Transaction resources already exist for this transaction. Transfer them from the + // stash to the operation context. + // + // Because TxnResources::release must acquire the Client lock midway through, and because we + // must hold the Client clock to mutate txnResourceStash, we jump through some hoops here to + // move the TxnResources in txnResourceStash into a local variable that can be manipulated + // without holding the Client lock. + [&]() noexcept { + using std::swap; + boost::optional<TxnResources> trs; + stdx::lock_guard<Client> lk(*opCtx->getClient()); + swap(trs, o(lk).txnResourceStash); + return std::move(*trs); + } + ().release(opCtx); +} + +void TransactionParticipant::Participant::unstashTransactionResources(OperationContext* opCtx, + const std::string& cmdName) { invariant(!opCtx->getClient()->isInDirectClient()); invariant(opCtx->getTxnNumber()); - { - stdx::lock_guard<stdx::mutex> lg(_mutex); - - _checkValid(lg); - _checkIsActiveTransaction(lg, *opCtx->getTxnNumber(), false); - - // If this is not a multi-document transaction, there is nothing to unstash. - if (_txnState.isNone(lg)) { - invariant(!_txnResourceStash); - return; - } + // If this is not a multi-document transaction, there is nothing to unstash. + if (o().txnState.isNone()) { + invariant(!o().txnResourceStash); + return; + } - _checkIsCommandValidWithTxnState(lg, *opCtx->getTxnNumber(), cmdName); + _checkIsCommandValidWithTxnState(*opCtx->getTxnNumber(), cmdName); + if (o().txnResourceStash) { + _releaseTransactionResourcesToOpCtx(opCtx); + stdx::lock_guard<Client> lg(*opCtx->getClient()); + o(lg).transactionMetricsObserver.onUnstash(ServerTransactionsMetrics::get(opCtx), + opCtx->getServiceContext()->getTickSource()); + return; + } - if (_txnResourceStash) { - // Transaction resources already exist for this transaction. Transfer them from the - // stash to the operation context. - _txnResourceStash->release(opCtx); - _txnResourceStash = boost::none; - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.onUnstash(ServerTransactionsMetrics::get(opCtx), - opCtx->getServiceContext()->getTickSource()); - return; - } + // If we have no transaction resources then we cannot be prepared. If we're not in progress, + // we don't do anything else. + invariant(!o().txnState.isPrepared()); - // If we have no transaction resources then we cannot be prepared. If we're not in progress, - // we don't do anything else. - invariant(!_txnState.isPrepared(lg)); + if (!o().txnState.isInProgress()) { + // At this point we're either committed and this is a 'commitTransaction' command, or we + // are in the process of committing. + return; + } - if (!_txnState.isInProgress(lg)) { - // At this point we're either committed and this is a 'commitTransaction' command, or we - // are in the process of committing. - return; - } + // All locks of transactions must be acquired inside the global WUOW so that we can + // yield and restore all locks on state transition. Otherwise, we'd have to remember + // which locks are managed by WUOW. + invariant(!opCtx->lockState()->isLocked()); - // All locks of transactions must be acquired inside the global WUOW so that we can - // yield and restore all locks on state transition. Otherwise, we'd have to remember - // which locks are managed by WUOW. - invariant(!opCtx->lockState()->isLocked()); - - // Stashed transaction resources do not exist for this in-progress multi-document - // transaction. Set up the transaction resources on the opCtx. - opCtx->setWriteUnitOfWork(std::make_unique<WriteUnitOfWork>(opCtx)); - - // If maxTransactionLockRequestTimeoutMillis is set, then we will ensure no - // future lock request waits longer than maxTransactionLockRequestTimeoutMillis - // to acquire a lock. This is to avoid deadlocks and minimize non-transaction - // operation performance degradations. - auto maxTransactionLockMillis = maxTransactionLockRequestTimeoutMillis.load(); - if (opCtx->writesAreReplicated() && maxTransactionLockMillis >= 0) { - opCtx->lockState()->setMaxLockTimeout(Milliseconds(maxTransactionLockMillis)); - } + // Stashed transaction resources do not exist for this in-progress multi-document + // transaction. Set up the transaction resources on the opCtx. + opCtx->setWriteUnitOfWork(std::make_unique<WriteUnitOfWork>(opCtx)); - // On secondaries, max lock timeout must not be set. - invariant(opCtx->writesAreReplicated() || !opCtx->lockState()->hasMaxLockTimeout()); + // If maxTransactionLockRequestTimeoutMillis is set, then we will ensure no + // future lock request waits longer than maxTransactionLockRequestTimeoutMillis + // to acquire a lock. This is to avoid deadlocks and minimize non-transaction + // operation performance degradations. + auto maxTransactionLockMillis = maxTransactionLockRequestTimeoutMillis.load(); + if (opCtx->writesAreReplicated() && maxTransactionLockMillis >= 0) { + opCtx->lockState()->setMaxLockTimeout(Milliseconds(maxTransactionLockMillis)); } + // On secondaries, max lock timeout must not be set. + invariant(opCtx->writesAreReplicated() || !opCtx->lockState()->hasMaxLockTimeout()); + // Storage engine transactions may be started in a lazy manner. By explicitly // starting here we ensure that a point-in-time snapshot is established during the // first operation of a transaction. @@ -813,33 +806,28 @@ void TransactionParticipant::unstashTransactionResources(OperationContext* opCtx // not deadlock-safe to upgrade IS to IX. Lock::GlobalLock(opCtx, MODE_IX); - { - // Set speculative execution. This must be done after the global lock is acquired, because - // we need to check that we are primary. - stdx::lock_guard<stdx::mutex> lg(_mutex); - const auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx); - // TODO(SERVER-38203): We cannot wait for write concern on secondaries, so we do not set the - // speculative optime on secondaries either. This means that reads done in transactions on - // secondaries will not wait for the read snapshot to become majority-committed. - repl::ReplicationCoordinator* replCoord = - repl::ReplicationCoordinator::get(opCtx->getClient()->getServiceContext()); - if (replCoord->canAcceptWritesForDatabase( - opCtx, NamespaceString::kSessionTransactionsTableNamespace.db())) { - if (readConcernArgs.getArgsAtClusterTime()) { - _setSpeculativeTransactionReadTimestamp( - lg, opCtx, readConcernArgs.getArgsAtClusterTime()->asTimestamp()); - } else { - _setSpeculativeTransactionOpTime( - lg, - opCtx, - readConcernArgs.getOriginalLevel() == - repl::ReadConcernLevel::kSnapshotReadConcern - ? SpeculativeTransactionOpTime::kAllCommitted - : SpeculativeTransactionOpTime::kLastApplied); - } + // Set speculative execution. This must be done after the global lock is acquired, because + // we need to check that we are primary. + const auto& readConcernArgs = repl::ReadConcernArgs::get(opCtx); + // TODO(SERVER-38203): We cannot wait for write concern on secondaries, so we do not set the + // speculative optime on secondaries either. This means that reads done in transactions on + // secondaries will not wait for the read snapshot to become majority-committed. + repl::ReplicationCoordinator* replCoord = + repl::ReplicationCoordinator::get(opCtx->getServiceContext()); + if (replCoord->canAcceptWritesForDatabase( + opCtx, NamespaceString::kSessionTransactionsTableNamespace.db())) { + if (readConcernArgs.getArgsAtClusterTime()) { + _setSpeculativeTransactionReadTimestamp( + opCtx, readConcernArgs.getArgsAtClusterTime()->asTimestamp()); } else { - opCtx->recoveryUnit()->preallocateSnapshot(); + _setSpeculativeTransactionOpTime(opCtx, + readConcernArgs.getOriginalLevel() == + repl::ReadConcernLevel::kSnapshotReadConcern + ? SpeculativeTransactionOpTime::kAllCommitted + : SpeculativeTransactionOpTime::kLastApplied); } + } else { + opCtx->recoveryUnit()->preallocateSnapshot(); } // The Client lock must not be held when executing this failpoint as it will block currentOp @@ -850,28 +838,24 @@ void TransactionParticipant::unstashTransactionResources(OperationContext* opCtx } { - stdx::lock_guard<stdx::mutex> lg(_mutex); - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.onUnstash(ServerTransactionsMetrics::get(opCtx), - opCtx->getServiceContext()->getTickSource()); + stdx::lock_guard<Client> lg(*opCtx->getClient()); + o(lg).transactionMetricsObserver.onUnstash(ServerTransactionsMetrics::get(opCtx), + opCtx->getServiceContext()->getTickSource()); } } -void TransactionParticipant::refreshLocksForPreparedTransaction(OperationContext* opCtx, - bool yieldLocks) { +void TransactionParticipant::Participant::refreshLocksForPreparedTransaction( + OperationContext* opCtx, bool yieldLocks) { // The opCtx will be used to swap locks, so it cannot hold any lock. invariant(!opCtx->lockState()->isRSTLLocked()); invariant(!opCtx->lockState()->isLocked()); - stdx::unique_lock<stdx::mutex> lk(_mutex); // The node must have txn resource. - invariant(_txnResourceStash); - invariant(_txnState.isPrepared(lk)); + invariant(o().txnResourceStash); + invariant(o().txnState.isPrepared()); - // Transfer the txn resource from the stash to the operation context. - _txnResourceStash->release(opCtx); - _txnResourceStash = boost::none; + _releaseTransactionResourcesToOpCtx(opCtx); // Snapshot transactions don't conflict with PBWM lock on both primary and secondary. invariant(!opCtx->lockState()->shouldConflictWithSecondaryBatchApplication()); @@ -879,25 +863,17 @@ void TransactionParticipant::refreshLocksForPreparedTransaction(OperationContext // Transfer the txn resource back from the operation context to the stash. auto stashStyle = yieldLocks ? TxnResources::StashStyle::kSecondary : TxnResources::StashStyle::kPrimary; - _txnResourceStash = TxnResources(opCtx, stashStyle); + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).txnResourceStash = TxnResources(lk, opCtx, stashStyle); } -Timestamp TransactionParticipant::prepareTransaction(OperationContext* opCtx, - boost::optional<repl::OpTime> prepareOptime) { - stdx::unique_lock<stdx::mutex> lk(_mutex); - - // Always check session's txnNumber and '_txnState', since they can be modified by - // session kill and migration, which do not check out the session. - _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true); +Timestamp TransactionParticipant::Participant::prepareTransaction( + OperationContext* opCtx, boost::optional<repl::OpTime> prepareOptime) { auto abortGuard = makeGuard([&] { // Prepare transaction on secondaries should always succeed. invariant(!prepareOptime); - if (lk.owns_lock()) { - lk.unlock(); - } - try { // This shouldn't cause deadlocks with other prepared txns, because the acquisition // of RSTL lock inside abortActiveTransaction will be no-op since we already have it. @@ -915,14 +891,18 @@ Timestamp TransactionParticipant::prepareTransaction(OperationContext* opCtx, } }); - _txnState.transitionTo(lk, TransactionState::kPrepared); - boost::optional<OplogSlotReserver> oplogSlotReserver; OplogSlot prepareOplogSlot; + { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).txnState.transitionTo(TransactionState::kPrepared); + } + if (prepareOptime) { // On secondary, we just prepare the transaction and discard the buffered ops. prepareOplogSlot = OplogSlot(*prepareOptime, 0); - _prepareOpTime = *prepareOptime; + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).prepareOpTime = *prepareOptime; } else { // On primary, we reserve an optime, prepare the transaction and write the oplog entry. // @@ -933,10 +913,14 @@ Timestamp TransactionParticipant::prepareTransaction(OperationContext* opCtx, // corresponding oplog hole) will vanish. oplogSlotReserver.emplace(opCtx); prepareOplogSlot = oplogSlotReserver->getReservedOplogSlot(); - invariant(_prepareOpTime.isNull(), + invariant(o().prepareOpTime.isNull(), str::stream() << "This transaction has already reserved a prepareOpTime at: " - << _prepareOpTime.toString()); - _prepareOpTime = prepareOplogSlot.opTime; + << o().prepareOpTime.toString()); + + { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).prepareOpTime = prepareOplogSlot.opTime; + } if (MONGO_FAIL_POINT(hangAfterReservingPrepareTimestamp)) { // This log output is used in js tests so please leave it. @@ -949,30 +933,29 @@ Timestamp TransactionParticipant::prepareTransaction(OperationContext* opCtx, opCtx->recoveryUnit()->setPrepareTimestamp(prepareOplogSlot.opTime.getTimestamp()); opCtx->getWriteUnitOfWork()->prepare(); - // We need to unlock the session to run the opObserver onTransactionPrepare, which calls back - // into the session. - lk.unlock(); opCtx->getServiceContext()->getOpObserver()->onTransactionPrepare( opCtx, prepareOplogSlot, retrieveCompletedTransactionOperations(opCtx)); abortGuard.dismiss(); - invariant(!_oldestOplogEntryOpTime, + // For prepared transactions, we must update ServerTransactionMetrics with the prepare optime + // before the prepare oplog entry is written so that we don't incorrectly advance the stable + // timestamp. + invariant(!p().oldestOplogEntryOpTime, str::stream() << "This transaction's oldest oplog entry Timestamp has already " << "been set to: " - << _oldestOplogEntryOpTime->toString()); + << p().oldestOplogEntryOpTime->toString()); // Keep track of the Timestamp from the first oplog entry written by this transaction. - _oldestOplogEntryOpTime = prepareOplogSlot.opTime; + p().oldestOplogEntryOpTime = prepareOplogSlot.opTime; // Maintain the OpTime of the oldest active oplog entry for this transaction. We currently // only write an oplog entry for an in progress transaction when it is in the prepare state // but this will change when we allow multiple oplog entries per transaction. { - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - const auto tickSource = getGlobalServiceContext()->getTickSource(); - _transactionMetricsObserver.onPrepare(ServerTransactionsMetrics::get(opCtx), - *_oldestOplogEntryOpTime, - tickSource->getTicks()); + const auto ticks = opCtx->getServiceContext()->getTickSource()->getTicks(); + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).transactionMetricsObserver.onPrepare( + ServerTransactionsMetrics::get(opCtx), *p().oldestOplogEntryOpTime, ticks); } if (MONGO_FAIL_POINT(hangAfterSettingPrepareStartTime)) { @@ -990,21 +973,16 @@ Timestamp TransactionParticipant::prepareTransaction(OperationContext* opCtx, return prepareOplogSlot.opTime.getTimestamp(); } -void TransactionParticipant::addTransactionOperation(OperationContext* opCtx, - const repl::ReplOperation& operation) { - stdx::lock_guard<stdx::mutex> lk(_mutex); - - // Always check _getSession()'s txnNumber and '_txnState', since they can be modified by session - // kill and migration, which do not check out the session. - _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true); +void TransactionParticipant::Participant::addTransactionOperation( + OperationContext* opCtx, const repl::ReplOperation& operation) { // Ensure that we only ever add operations to an in progress transaction. - invariant(_txnState.isInProgress(lk), str::stream() << "Current state: " << _txnState); + invariant(o().txnState.isInProgress(), str::stream() << "Current state: " << o().txnState); - invariant(_autoCommit && !*_autoCommit && _activeTxnNumber != kUninitializedTxnNumber); + invariant(p().autoCommit && !*p().autoCommit && o().activeTxnNumber != kUninitializedTxnNumber); invariant(opCtx->lockState()->inAWriteUnitOfWork()); - _transactionOperations.push_back(operation); - _transactionOperationBytes += repl::OplogEntry::getReplOperationSize(operation); + p().transactionOperations.push_back(operation); + p().transactionOperationBytes += repl::OplogEntry::getReplOperationSize(operation); // _transactionOperationBytes is based on the in-memory size of the operation. With overhead, // we expect the BSON size of the operation to be larger, so it's possible to make a transaction // just a bit too large and have it fail only in the commit. It's still useful to fail early @@ -1013,91 +991,67 @@ void TransactionParticipant::addTransactionOperation(OperationContext* opCtx, str::stream() << "Total size of all transaction operations must be less than " << BSONObjMaxInternalSize << ". Actual size is " - << _transactionOperationBytes, + << p().transactionOperationBytes, useMultipleOplogEntryFormatForTransactions || - _transactionOperationBytes <= BSONObjMaxInternalSize); + p().transactionOperationBytes <= BSONObjMaxInternalSize); } -std::vector<repl::ReplOperation>& TransactionParticipant::retrieveCompletedTransactionOperations( +std::vector<repl::ReplOperation>& +TransactionParticipant::Participant::retrieveCompletedTransactionOperations( OperationContext* opCtx) { - stdx::lock_guard<stdx::mutex> lk(_mutex); - - // Always check session's txnNumber and '_txnState', since they can be modified by session kill - // and migration, which do not check out the session. - _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true); // Ensure that we only ever retrieve a transaction's completed operations when in progress, // committing with prepare, or prepared. - invariant(_txnState.isInSet(lk, - TransactionState::kInProgress | - TransactionState::kCommittingWithPrepare | - TransactionState::kPrepared), - str::stream() << "Current state: " << _txnState); + invariant(o().txnState.isInSet(TransactionState::kInProgress | + TransactionState::kCommittingWithPrepare | + TransactionState::kPrepared), + str::stream() << "Current state: " << o().txnState); - return _transactionOperations; + return p().transactionOperations; } -void TransactionParticipant::clearOperationsInMemory(OperationContext* opCtx) { - stdx::lock_guard<stdx::mutex> lk(_mutex); - - // Always check session's txnNumber and '_txnState', since they can be modified by session kill - // and migration, which do not check out the session. - _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true); - +void TransactionParticipant::Participant::clearOperationsInMemory(OperationContext* opCtx) { // Ensure that we only ever end a transaction when committing with prepare or in progress. - invariant(_txnState.isInSet( - lk, TransactionState::kCommittingWithPrepare | TransactionState::kInProgress), - str::stream() << "Current state: " << _txnState); - - invariant(_autoCommit); - _transactionOperationBytes = 0; - _transactionOperations.clear(); + invariant(o().txnState.isInSet(TransactionState::kCommittingWithPrepare | + TransactionState::kInProgress), + str::stream() << "Current state: " << o().txnState); + invariant(p().autoCommit); + p().transactionOperationBytes = 0; + p().transactionOperations.clear(); } -void TransactionParticipant::commitUnpreparedTransaction(OperationContext* opCtx) { - stdx::unique_lock<stdx::mutex> lk(_mutex); - _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true); - +void TransactionParticipant::Participant::commitUnpreparedTransaction(OperationContext* opCtx) { uassert(ErrorCodes::InvalidOptions, "commitTransaction must provide commitTimestamp to prepared transaction.", - !_txnState.isPrepared(lk)); + !o().txnState.isPrepared()); // TODO SERVER-37129: Remove this invariant once we allow transactions larger than 16MB. - invariant(!_oldestOplogEntryOpTime, + invariant(!p().oldestOplogEntryOpTime, str::stream() << "The oldest oplog entry Timestamp should not have been set because " << "this transaction is not prepared. But, it is currently " - << _oldestOplogEntryOpTime->toString()); + << p().oldestOplogEntryOpTime->toString()); - // We need to unlock the session to run the opObserver onTransactionCommit, which calls back - // into the session. - lk.unlock(); auto opObserver = opCtx->getServiceContext()->getOpObserver(); invariant(opObserver); opObserver->onTransactionCommit( opCtx, boost::none, boost::none, retrieveCompletedTransactionOperations(opCtx)); - clearOperationsInMemory(opCtx); + { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + // The oplog entry is written in the same WUOW with the data change for unprepared + // transactions. We can still consider the state is InProgress until now, since no + // externally visible changes have been made yet by the commit operation. If anything throws + // before this point in the function, entry point will abort the transaction. + o(lk).txnState.transitionTo(TransactionState::kCommittingWithoutPrepare); + } - lk.lock(); - _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true); - - // The oplog entry is written in the same WUOW with the data change for unprepared transactions. - // We can still consider the state is InProgress until now, since no externally visible changes - // have been made yet by the commit operation. If anything throws before this point in the - // function, entry point will abort the transaction. - _txnState.transitionTo(lk, TransactionState::kCommittingWithoutPrepare); - - lk.unlock(); _commitStorageTransaction(opCtx); - lk.lock(); - _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), false); - invariant(_txnState.isCommittingWithoutPrepare(lk), - str::stream() << "Current State: " << _txnState); - - _finishCommitTransaction(lk, opCtx); + invariant(o().txnState.isCommittingWithoutPrepare(), + str::stream() << "Current State: " << o().txnState); + _finishCommitTransaction(opCtx); } -void TransactionParticipant::commitPreparedTransaction( +void TransactionParticipant::Participant::commitPreparedTransaction( OperationContext* opCtx, Timestamp commitTimestamp, boost::optional<repl::OpTime> commitOplogEntryOpTime) { @@ -1112,22 +1066,22 @@ void TransactionParticipant::commitPreparedTransaction( replCoord->canAcceptWritesForDatabase(opCtx, "admin")); } - stdx::unique_lock<stdx::mutex> lk(_mutex); - _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true); - uassert(ErrorCodes::InvalidOptions, "commitTransaction cannot provide commitTimestamp to unprepared transaction.", - _txnState.isPrepared(lk)); + o().txnState.isPrepared()); uassert( ErrorCodes::InvalidOptions, "'commitTimestamp' cannot be null", !commitTimestamp.isNull()); uassert(ErrorCodes::InvalidOptions, "'commitTimestamp' must be greater than the 'prepareTimestamp'", - commitTimestamp > _prepareOpTime.getTimestamp()); + commitTimestamp > o().prepareOpTime.getTimestamp()); - _txnState.transitionTo(lk, TransactionState::kCommittingWithPrepare); - opCtx->recoveryUnit()->setCommitTimestamp(commitTimestamp); + { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).txnState.transitionTo(TransactionState::kCommittingWithPrepare); + } try { + opCtx->recoveryUnit()->setCommitTimestamp(commitTimestamp); UninterruptibleLockGuard noInterrupt(opCtx->lockState()); // On secondary, we generate a fake empty oplog slot, since it's not used by opObserver. @@ -1153,37 +1107,26 @@ void TransactionParticipant::commitPreparedTransaction( invariant(commitOplogEntryOpTime); } - // We need to unlock the session to run the opObserver onTransactionCommit, which calls back - // into the session. We also do not want to write to storage with the mutex locked. - lk.unlock(); _commitStorageTransaction(opCtx); auto opObserver = opCtx->getServiceContext()->getOpObserver(); invariant(opObserver); - { - // Once the transaction is committed, the oplog entry must be written. - UninterruptibleLockGuard lockGuard(opCtx->lockState()); - opObserver->onTransactionCommit(opCtx, - commitOplogSlot, - commitTimestamp, - retrieveCompletedTransactionOperations(opCtx)); - } + // Once the transaction is committed, the oplog entry must be written. + opObserver->onTransactionCommit( + opCtx, commitOplogSlot, commitTimestamp, retrieveCompletedTransactionOperations(opCtx)); clearOperationsInMemory(opCtx); - lk.lock(); - _checkIsActiveTransaction(lk, *opCtx->getTxnNumber(), true); - // If we are committing a prepared transaction, then we must have already recorded this // transaction's oldest oplog entry optime. - invariant(_oldestOplogEntryOpTime); + invariant(p().oldestOplogEntryOpTime); // If commitOplogEntryOpTime is a nullopt, then we grab the OpTime from the commitOplogSlot // which will only be set if we are primary. Otherwise, the commitOplogEntryOpTime must have // been passed in during secondary oplog application. - _finishOpTime = commitOplogEntryOpTime.value_or(commitOplogSlot.opTime); + p().finishOpTime = commitOplogEntryOpTime.value_or(commitOplogSlot.opTime); - _finishCommitTransaction(lk, opCtx); + _finishCommitTransaction(opCtx); } catch (...) { // It is illegal for committing a prepared transaction to fail for any reason, other than an // invalid command, so we crash instead. @@ -1194,7 +1137,7 @@ void TransactionParticipant::commitPreparedTransaction( } } -void TransactionParticipant::_commitStorageTransaction(OperationContext* opCtx) try { +void TransactionParticipant::Participant::_commitStorageTransaction(OperationContext* opCtx) try { invariant(opCtx->getWriteUnitOfWork()); invariant(opCtx->lockState()->isRSTLLocked()); opCtx->getWriteUnitOfWork()->commit(); @@ -1214,127 +1157,112 @@ void TransactionParticipant::_commitStorageTransaction(OperationContext* opCtx) std::terminate(); } -void TransactionParticipant::_finishCommitTransaction(WithLock lk, OperationContext* opCtx) { +void TransactionParticipant::Participant::_finishCommitTransaction(OperationContext* opCtx) { // If no writes have been done, set the client optime forward to the read timestamp so waiting // for write concern will ensure all read data was committed. // // TODO(SERVER-34881): Once the default read concern is speculative majority, only set the // client optime forward if the original read concern level is "majority" or "snapshot". auto& clientInfo = repl::ReplClientInfo::forClient(opCtx->getClient()); - if (_speculativeTransactionReadOpTime > clientInfo.getLastOp()) { - clientInfo.setLastOp(_speculativeTransactionReadOpTime); + if (p().speculativeTransactionReadOpTime > clientInfo.getLastOp()) { + clientInfo.setLastOp(p().speculativeTransactionReadOpTime); } - const bool isCommittingWithPrepare = _txnState.isCommittingWithPrepare(lk); - _txnState.transitionTo(lk, TransactionState::kCommitted); { - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); auto tickSource = opCtx->getServiceContext()->getTickSource(); - _transactionMetricsObserver.onCommit(ServerTransactionsMetrics::get(opCtx), - tickSource, - _oldestOplogEntryOpTime, - _finishOpTime, - &Top::get(getGlobalServiceContext()), - isCommittingWithPrepare); - _transactionMetricsObserver.onTransactionOperation( + const bool isCommittingWithPrepare = o().txnState.isCommittingWithPrepare(); + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).txnState.transitionTo(TransactionState::kCommitted); + + o(lk).transactionMetricsObserver.onCommit(ServerTransactionsMetrics::get(opCtx), + tickSource, + p().oldestOplogEntryOpTime, + p().finishOpTime, + &Top::get(getGlobalServiceContext()), + isCommittingWithPrepare); + o(lk).transactionMetricsObserver.onTransactionOperation( opCtx->getClient(), CurOp::get(opCtx)->debug().additiveMetrics, CurOp::get(opCtx)->debug().storageStats); } - // We must clear the recovery unit and locker so any post-transaction writes can run without // transactional settings such as a read timestamp. - _cleanUpTxnResourceOnOpCtx(lk, opCtx, TerminationCause::kCommitted); + _cleanUpTxnResourceOnOpCtx(opCtx, TerminationCause::kCommitted); } -void TransactionParticipant::shutdown() { - stdx::lock_guard<stdx::mutex> lock(_mutex); +void TransactionParticipant::Participant::shutdown(OperationContext* opCtx) { + stdx::lock_guard<Client> lock(*opCtx->getClient()); - _inShutdown = true; - _txnResourceStash = boost::none; + p().inShutdown = true; + o(lock).txnResourceStash = boost::none; } -void TransactionParticipant::abortArbitraryTransaction() { - stdx::lock_guard<stdx::mutex> lock(_mutex); - - if (!_txnState.isInProgress(lock)) { +void TransactionParticipant::Participant::abortTransactionIfNotPrepared(OperationContext* opCtx) { + if (!o().txnState.isInProgress()) { // We do not want to abort transactions that are prepared unless we get an // 'abortTransaction' command. return; } - _abortTransactionOnSession(lock); + _abortTransactionOnSession(opCtx); } -bool TransactionParticipant::expired() const { - stdx::lock_guard<stdx::mutex> lock(_mutex); - - return _txnState.isInProgress(lock) && _transactionExpireDate && - _transactionExpireDate < getGlobalServiceContext()->getPreciseClockSource()->now(); +bool TransactionParticipant::Observer::expiredAsOf(Date_t when) const { + return o().txnState.isInProgress() && o().transactionExpireDate && + o().transactionExpireDate < when; } -void TransactionParticipant::abortActiveTransaction(OperationContext* opCtx) { - stdx::unique_lock<stdx::mutex> lock(_mutex); - +void TransactionParticipant::Participant::abortActiveTransaction(OperationContext* opCtx) { // Re-acquire the RSTL to prevent state transitions while aborting the transaction. If the // transaction was prepared then we dropped it on preparing the transaction. We do not need to // reacquire the PBWM because if we're not the primary we will uassert anyways. Lock::ResourceLock rstl(opCtx->lockState(), resourceIdReplicationStateTransitionLock, MODE_IX); - if (_txnState.isPrepared(lock) && opCtx->writesAreReplicated()) { + if (o().txnState.isPrepared() && opCtx->writesAreReplicated()) { auto replCoord = repl::ReplicationCoordinator::get(opCtx); uassert(ErrorCodes::NotMaster, "Not primary so we cannot abort a prepared transaction", replCoord->canAcceptWritesForDatabase(opCtx, "admin")); } - // This function shouldn't throw if the transaction is already aborted. - _checkIsActiveTransaction(lock, *opCtx->getTxnNumber(), false); - _abortActiveTransaction( - std::move(lock), opCtx, TransactionState::kInProgress | TransactionState::kPrepared); + _abortActiveTransaction(opCtx, TransactionState::kInProgress | TransactionState::kPrepared); } -void TransactionParticipant::abortActiveUnpreparedOrStashPreparedTransaction( +void TransactionParticipant::Participant::abortActiveUnpreparedOrStashPreparedTransaction( OperationContext* opCtx) try { - stdx::unique_lock<stdx::mutex> lock(_mutex); - if (_txnState.isInSet(lock, TransactionState::kNone | TransactionState::kCommitted)) { + if (o().txnState.isInSet(TransactionState::kNone | TransactionState::kCommitted)) { // If there is no active transaction, do nothing. return; } - // We do this check to follow convention and maintain safety. If this were to throw we should - // have returned in the check above. As a result, throwing here is fatal. - _checkIsActiveTransaction(lock, *opCtx->getTxnNumber(), false); - // Stash the transaction if it's in prepared state. - if (_txnState.isInSet(lock, TransactionState::kPrepared)) { - _stashActiveTransaction(lock, opCtx); + if (o().txnState.isInSet(TransactionState::kPrepared)) { + _stashActiveTransaction(opCtx); return; } // TODO SERVER-37129: Remove this invariant once we allow transactions larger than 16MB. - invariant(!_oldestOplogEntryOpTime, + invariant(!p().oldestOplogEntryOpTime, str::stream() << "The oldest oplog entry Timestamp should not have been set because " << "this transaction is not prepared. But, it is currently " - << _oldestOplogEntryOpTime->toString()); + << p().oldestOplogEntryOpTime->toString()); - _abortActiveTransaction(std::move(lock), opCtx, TransactionState::kInProgress); + _abortActiveTransaction(opCtx, TransactionState::kInProgress); } catch (...) { // It is illegal for this to throw so we catch and log this here for diagnosability. severe() << "Caught exception during transaction " << opCtx->getTxnNumber() - << " abort or stash on " << _sessionId().toBSON() << " in state " << _txnState << ": " - << exceptionToStatus(); + << " abort or stash on " << _sessionId().toBSON() << " in state " << o().txnState + << ": " << exceptionToStatus(); std::terminate(); } -void TransactionParticipant::_abortActiveTransaction(stdx::unique_lock<stdx::mutex> lock, - OperationContext* opCtx, - TransactionState::StateSet expectedStates) { - invariant(!_txnResourceStash); - invariant(!_txnState.isCommittingWithPrepare(lock)); +void TransactionParticipant::Participant::_abortActiveTransaction( + OperationContext* opCtx, TransactionState::StateSet expectedStates) { + invariant(!o().txnResourceStash); + invariant(!o().txnState.isCommittingWithPrepare()); - if (!_txnState.isNone(lock)) { - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.onTransactionOperation( + if (!o().txnState.isNone()) { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).transactionMetricsObserver.onTransactionOperation( opCtx->getClient(), CurOp::get(opCtx)->debug().additiveMetrics, CurOp::get(opCtx)->debug().storageStats); @@ -1346,42 +1274,35 @@ void TransactionParticipant::_abortActiveTransaction(stdx::unique_lock<stdx::mut // OpObserver. boost::optional<OplogSlotReserver> oplogSlotReserver; boost::optional<OplogSlot> abortOplogSlot; - if (_txnState.isPrepared(lock) && opCtx->writesAreReplicated()) { + if (o().txnState.isPrepared() && opCtx->writesAreReplicated()) { oplogSlotReserver.emplace(opCtx); abortOplogSlot = oplogSlotReserver->getReservedOplogSlot(); } // Clean up the transaction resources on the opCtx even if the transaction resources on the // session were not aborted. This actually aborts the storage-transaction. - _cleanUpTxnResourceOnOpCtx(lock, opCtx, TerminationCause::kAborted); + _cleanUpTxnResourceOnOpCtx(opCtx, TerminationCause::kAborted); // Write the abort oplog entry. This must be done after aborting the storage transaction, so - // that the lock state is reset, and there is no max lock timeout on the locker. We need to - // unlock the session to run the opObserver onTransactionAbort, which calls back into the - // session. - lock.unlock(); - + // that the lock state is reset, and there is no max lock timeout on the locker. auto opObserver = opCtx->getServiceContext()->getOpObserver(); invariant(opObserver); opObserver->onTransactionAbort(opCtx, abortOplogSlot); - lock.lock(); - // We do not check if the active transaction number is correct here because we handle it below. - // Set the finishOpTime of this transaction if we have recorded this transaction's oldest oplog // entry optime. - if (_oldestOplogEntryOpTime) { - _finishOpTime = repl::ReplClientInfo::forClient(opCtx->getClient()).getLastOp(); + if (p().oldestOplogEntryOpTime) { + p().finishOpTime = repl::ReplClientInfo::forClient(opCtx->getClient()).getLastOp(); } // Only abort the transaction in session if it's in expected states. // When the state of active transaction on session is not expected, it means another // thread has already aborted the transaction on session. - if (_txnState.isInSet(lock, expectedStates)) { - invariant(opCtx->getTxnNumber() == _activeTxnNumber); - _abortTransactionOnSession(lock); - } else if (opCtx->getTxnNumber() == _activeTxnNumber) { - if (_txnState.isNone(lock)) { + if (o().txnState.isInSet(expectedStates)) { + invariant(opCtx->getTxnNumber() == o().activeTxnNumber); + _abortTransactionOnSession(opCtx); + } else if (opCtx->getTxnNumber() == o().activeTxnNumber) { + if (o().txnState.isNone()) { // The active transaction is not a multi-document transaction. invariant(opCtx->getWriteUnitOfWork() == nullptr); return; @@ -1392,56 +1313,57 @@ void TransactionParticipant::_abortActiveTransaction(stdx::unique_lock<stdx::mut | TransactionState::kCommittingWithPrepare // | TransactionState::kCommittingWithoutPrepare // | TransactionState::kCommitted; // - invariant(!_txnState.isInSet(lock, unabortableStates), - str::stream() << "Cannot abort transaction in " << _txnState.toString()); + invariant(!o().txnState.isInSet(unabortableStates), + str::stream() << "Cannot abort transaction in " << o().txnState.toString()); } else { // If _activeTxnNumber is higher than ours, it means the transaction is already aborted. - invariant(_txnState.isInSet(lock, - TransactionState::kNone | - TransactionState::kAbortedWithoutPrepare | - TransactionState::kAbortedWithPrepare)); + invariant(o().txnState.isInSet(TransactionState::kNone | + TransactionState::kAbortedWithoutPrepare | + TransactionState::kAbortedWithPrepare), + str::stream() << "actual state: " << o().txnState.toString()); } } -void TransactionParticipant::_abortTransactionOnSession(WithLock wl) { - const auto tickSource = getGlobalServiceContext()->getTickSource(); +void TransactionParticipant::Participant::_abortTransactionOnSession(OperationContext* opCtx) { + const auto tickSource = opCtx->getServiceContext()->getTickSource(); // If the transaction is stashed, then we have aborted an inactive transaction. - if (_txnResourceStash) { - // The transaction is stashed, so we abort the inactive transaction on session. + if (o().txnResourceStash) { { - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.onAbortInactive( - ServerTransactionsMetrics::get(getGlobalServiceContext()), + stdx::lock_guard<Client> lk(*opCtx->getClient()); + // The transaction is stashed, so we abort the inactive transaction on session. + o(lk).transactionMetricsObserver.onAbortInactive( + ServerTransactionsMetrics::get(opCtx->getServiceContext()), tickSource, - _oldestOplogEntryOpTime, - &Top::get(getGlobalServiceContext())); + p().oldestOplogEntryOpTime, + &Top::get(opCtx->getServiceContext())); } - _logSlowTransaction(wl, - &(_txnResourceStash->locker()->getLockerInfo(boost::none))->stats, + _logSlowTransaction(opCtx, + &(o().txnResourceStash->locker()->getLockerInfo(boost::none))->stats, TerminationCause::kAborted, - _txnResourceStash->getReadConcernArgs()); + o().txnResourceStash->getReadConcernArgs()); } else { - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.onAbortActive( - ServerTransactionsMetrics::get(getGlobalServiceContext()), + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).transactionMetricsObserver.onAbortActive( + ServerTransactionsMetrics::get(opCtx->getServiceContext()), tickSource, - _oldestOplogEntryOpTime, - _finishOpTime, - &Top::get(getGlobalServiceContext()), - _txnState.isPrepared(lm)); + p().oldestOplogEntryOpTime, + p().finishOpTime, + &Top::get(opCtx->getServiceContext()), + o().txnState.isPrepared()); } - const auto nextState = _txnState.isPrepared(wl) ? TransactionState::kAbortedWithPrepare - : TransactionState::kAbortedWithoutPrepare; - _resetTransactionState(wl, nextState); + const auto nextState = o().txnState.isPrepared() ? TransactionState::kAbortedWithPrepare + : TransactionState::kAbortedWithoutPrepare; + + stdx::lock_guard<Client> lk(*opCtx->getClient()); + _resetTransactionState(lk, nextState); } -void TransactionParticipant::_cleanUpTxnResourceOnOpCtx(WithLock wl, - OperationContext* opCtx, - TerminationCause terminationCause) { +void TransactionParticipant::Participant::_cleanUpTxnResourceOnOpCtx( + OperationContext* opCtx, TerminationCause terminationCause) { // Log the transaction if its duration is longer than the slowMS command threshold. _logSlowTransaction( - wl, + opCtx, &(opCtx->lockState()->getLockerInfo(CurOp::get(*opCtx)->getLockStatsBase()))->stats, terminationCause, repl::ReadConcernArgs::get(opCtx)); @@ -1461,64 +1383,42 @@ void TransactionParticipant::_cleanUpTxnResourceOnOpCtx(WithLock wl, opCtx->lockState()->unsetMaxLockTimeout(); } -void TransactionParticipant::_checkIsActiveTransaction(WithLock wl, - const TxnNumber& requestTxnNumber, - bool checkAbort) const { - uassert(ErrorCodes::ConflictingOperationInProgress, - str::stream() << "Cannot perform operations on requested transaction " - << requestTxnNumber - << " on session " - << _sessionId() - << " because a different transaction " - << _activeTxnNumber - << " is now active.", - requestTxnNumber == _activeTxnNumber); - - uassert(ErrorCodes::NoSuchTransaction, - str::stream() << "Transaction " << _activeTxnNumber << " has been aborted.", - !checkAbort || !_txnState.isAborted(wl)); -} - -void TransactionParticipant::_checkIsCommandValidWithTxnState(WithLock wl, - const TxnNumber& requestTxnNumber, - const std::string& cmdName) { - // Throw NoSuchTransaction error instead of TransactionAborted error since this is the entry - // point of transaction execution. +void TransactionParticipant::Participant::_checkIsCommandValidWithTxnState( + const TxnNumber& requestTxnNumber, const std::string& cmdName) const { uassert(ErrorCodes::NoSuchTransaction, str::stream() << "Transaction " << requestTxnNumber << " has been aborted.", - !_txnState.isAborted(wl)); + !o().txnState.isAborted()); // Cannot change committed transaction but allow retrying commitTransaction command. uassert(ErrorCodes::TransactionCommitted, str::stream() << "Transaction " << requestTxnNumber << " has been committed.", - cmdName == "commitTransaction" || !_txnState.isCommitted(wl)); + cmdName == "commitTransaction" || !o().txnState.isCommitted()); // Disallow operations other than abort, prepare or commit on a prepared transaction uassert(ErrorCodes::PreparedTransactionInProgress, str::stream() << "Cannot call any operation other than abort, prepare or commit on" << " a prepared transaction", - !_txnState.isPrepared(wl) || + !o().txnState.isPrepared() || preparedTxnCmdWhitelist.find(cmdName) != preparedTxnCmdWhitelist.cend()); } -BSONObj TransactionParticipant::reportStashedState() const { +BSONObj TransactionParticipant::Observer::reportStashedState(OperationContext* opCtx) const { BSONObjBuilder builder; - reportStashedState(&builder); + reportStashedState(opCtx, &builder); return builder.obj(); } -void TransactionParticipant::reportStashedState(BSONObjBuilder* builder) const { - stdx::lock_guard<stdx::mutex> lm(_mutex); - - if (_txnResourceStash && _txnResourceStash->locker()) { - if (auto lockerInfo = _txnResourceStash->locker()->getLockerInfo(boost::none)) { - invariant(_activeTxnNumber != kUninitializedTxnNumber); +void TransactionParticipant::Observer::reportStashedState(OperationContext* opCtx, + BSONObjBuilder* builder) const { + if (o().txnResourceStash && o().txnResourceStash->locker()) { + if (auto lockerInfo = o().txnResourceStash->locker()->getLockerInfo(boost::none)) { + invariant(o().activeTxnNumber != kUninitializedTxnNumber); builder->append("type", "idleSession"); builder->append("host", getHostNameCachedAndPort()); builder->append("desc", "inactive transaction"); const auto& lastClientInfo = - _transactionMetricsObserver.getSingleTransactionStats().getLastClientInfo(); + o().transactionMetricsObserver.getSingleTransactionStats().getLastClientInfo(); builder->append("client", lastClientInfo.clientHostAndPort); builder->append("connectionId", lastClientInfo.connectionId); builder->append("appName", lastClientInfo.appName); @@ -1531,7 +1431,7 @@ void TransactionParticipant::reportStashedState(BSONObjBuilder* builder) const { BSONObjBuilder transactionBuilder; _reportTransactionStats( - lm, &transactionBuilder, _txnResourceStash->getReadConcernArgs()); + opCtx, &transactionBuilder, o().txnResourceStash->getReadConcernArgs()); builder->append("transaction", transactionBuilder.obj()); builder->append("waitingForLock", false); @@ -1542,19 +1442,18 @@ void TransactionParticipant::reportStashedState(BSONObjBuilder* builder) const { } } -void TransactionParticipant::reportUnstashedState(OperationContext* opCtx, - BSONObjBuilder* builder) const { +void TransactionParticipant::Observer::reportUnstashedState(OperationContext* opCtx, + BSONObjBuilder* builder) const { // This method may only take the metrics mutex, as it is called with the Client mutex held. So // we cannot check the stashed state directly. Instead, a transaction is considered unstashed // if it is not actually a transaction (retryable write, no stash used), or is active (not // stashed), or has ended (any stash would be cleared). - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - const auto& singleTransactionStats = _transactionMetricsObserver.getSingleTransactionStats(); + const auto& singleTransactionStats = o().transactionMetricsObserver.getSingleTransactionStats(); if (!singleTransactionStats.isForMultiDocumentTransaction() || singleTransactionStats.isActive() || singleTransactionStats.isEnded()) { BSONObjBuilder transactionBuilder; - _reportTransactionStats(lm, &transactionBuilder, repl::ReadConcernArgs::get(opCtx)); + _reportTransactionStats(opCtx, &transactionBuilder, repl::ReadConcernArgs::get(opCtx)); builder->append("transaction", transactionBuilder.obj()); } } @@ -1660,8 +1559,7 @@ bool TransactionParticipant::TransactionState::_isLegalTransition(StateFlag oldS MONGO_UNREACHABLE; } -void TransactionParticipant::TransactionState::transitionTo(WithLock, - StateFlag newState, +void TransactionParticipant::TransactionState::transitionTo(StateFlag newState, TransitionValidation shouldValidate) { if (shouldValidate == TransitionValidation::kValidateTransition) { invariant(TransactionState::_isLegalTransition(_state, newState), @@ -1673,15 +1571,15 @@ void TransactionParticipant::TransactionState::transitionTo(WithLock, _state = newState; } -void TransactionParticipant::_reportTransactionStats(WithLock wl, - BSONObjBuilder* builder, - repl::ReadConcernArgs readConcernArgs) const { - const auto tickSource = getGlobalServiceContext()->getTickSource(); - _transactionMetricsObserver.getSingleTransactionStats().report( +void TransactionParticipant::Observer::_reportTransactionStats( + OperationContext* opCtx, BSONObjBuilder* builder, repl::ReadConcernArgs readConcernArgs) const { + const auto tickSource = opCtx->getServiceContext()->getTickSource(); + o().transactionMetricsObserver.getSingleTransactionStats().report( builder, readConcernArgs, tickSource, tickSource->getTicks()); } -std::string TransactionParticipant::_transactionInfoForLog( +std::string TransactionParticipant::Participant::_transactionInfoForLog( + OperationContext* opCtx, const SingleThreadedLockStats* lockStats, TerminationCause terminationCause, repl::ReadConcernArgs readConcernArgs) const { @@ -1696,15 +1594,15 @@ std::string TransactionParticipant::_transactionInfoForLog( _sessionId().serialize(&lsidBuilder); lsidBuilder.doneFast(); - parametersBuilder.append("txnNumber", _activeTxnNumber); - parametersBuilder.append("autocommit", _autoCommit ? *_autoCommit : true); + parametersBuilder.append("txnNumber", o().activeTxnNumber); + parametersBuilder.append("autocommit", p().autoCommit ? *p().autoCommit : true); readConcernArgs.appendInfo(¶metersBuilder); s << "parameters:" << parametersBuilder.obj().toString() << ","; - s << " readTimestamp:" << _speculativeTransactionReadOpTime.getTimestamp().toString() << ","; + s << " readTimestamp:" << p().speculativeTransactionReadOpTime.getTimestamp().toString() << ","; - const auto& singleTransactionStats = _transactionMetricsObserver.getSingleTransactionStats(); + const auto& singleTransactionStats = o().transactionMetricsObserver.getSingleTransactionStats(); s << singleTransactionStats.getOpDebug()->additiveMetrics.report(); @@ -1712,7 +1610,7 @@ std::string TransactionParticipant::_transactionInfoForLog( terminationCause == TerminationCause::kCommitted ? "committed" : "aborted"; s << " terminationCause:" << terminationCauseString; - auto tickSource = getGlobalServiceContext()->getTickSource(); + auto tickSource = opCtx->getServiceContext()->getTickSource(); auto curTick = tickSource->getTicks(); s << " timeActiveMicros:" @@ -1742,15 +1640,15 @@ std::string TransactionParticipant::_transactionInfoForLog( s << " wasPrepared:" << txnWasPrepared; if (txnWasPrepared) { s << " totalPreparedDurationMicros:" << totalPreparedDuration; - s << " prepareOpTime:" << _prepareOpTime.toString(); + s << " prepareOpTime:" << o().prepareOpTime.toString(); } - if (_oldestOplogEntryOpTime) { - s << " oldestOplogEntryOpTime:" << _oldestOplogEntryOpTime->toString(); + if (p().oldestOplogEntryOpTime) { + s << " oldestOplogEntryOpTime:" << p().oldestOplogEntryOpTime->toString(); } - if (_finishOpTime) { - s << " finishOpTime:" << _finishOpTime->toString(); + if (p().finishOpTime) { + s << " finishOpTime:" << p().finishOpTime->toString(); } // Total duration of the transaction. @@ -1760,83 +1658,80 @@ std::string TransactionParticipant::_transactionInfoForLog( return s.str(); } -void TransactionParticipant::_logSlowTransaction(WithLock wl, - const SingleThreadedLockStats* lockStats, - TerminationCause terminationCause, - repl::ReadConcernArgs readConcernArgs) { +void TransactionParticipant::Participant::_logSlowTransaction( + OperationContext* opCtx, + const SingleThreadedLockStats* lockStats, + TerminationCause terminationCause, + repl::ReadConcernArgs readConcernArgs) { // Only log multi-document transactions. - if (!_txnState.isNone(wl)) { - const auto tickSource = getGlobalServiceContext()->getTickSource(); + if (!o().txnState.isNone()) { + const auto tickSource = opCtx->getServiceContext()->getTickSource(); // Log the transaction if its duration is longer than the slowMS command threshold. - if (_transactionMetricsObserver.getSingleTransactionStats().getDuration( + if (o().transactionMetricsObserver.getSingleTransactionStats().getDuration( tickSource, tickSource->getTicks()) > Milliseconds(serverGlobalParams.slowMS)) { log(logger::LogComponent::kTransaction) << "transaction " - << _transactionInfoForLog(lockStats, terminationCause, readConcernArgs); + << _transactionInfoForLog(opCtx, lockStats, terminationCause, readConcernArgs); } } } -void TransactionParticipant::_setNewTxnNumber(WithLock wl, const TxnNumber& txnNumber) { +void TransactionParticipant::Participant::_setNewTxnNumber(OperationContext* opCtx, + const TxnNumber& txnNumber) { uassert(ErrorCodes::PreparedTransactionInProgress, "Cannot change transaction number while the session has a prepared transaction", - !_txnState.isInSet( - wl, TransactionState::kPrepared | TransactionState::kCommittingWithPrepare)); + !o().txnState.isInSet(TransactionState::kPrepared | + TransactionState::kCommittingWithPrepare)); LOG_FOR_TRANSACTION(4) << "New transaction started with txnNumber: " << txnNumber << " on session with lsid " << _sessionId().getId(); // Abort the existing transaction if it's not prepared, committed, or aborted. - if (_txnState.isInProgress(wl)) { - _abortTransactionOnSession(wl); + if (o().txnState.isInProgress()) { + _abortTransactionOnSession(opCtx); } - _activeTxnNumber = txnNumber; - _lastWriteOpTime = repl::OpTime(); + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).activeTxnNumber = txnNumber; + o(lk).lastWriteOpTime = repl::OpTime(); // Reset the retryable writes state - _resetRetryableWriteState(wl); + _resetRetryableWriteState(); // Reset the transactional state - _resetTransactionState(wl, TransactionState::kNone); + _resetTransactionState(lk, TransactionState::kNone); // Reset the transactions metrics - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.resetSingleTransactionStats(txnNumber); + o(lk).transactionMetricsObserver.resetSingleTransactionStats(txnNumber); } -void TransactionParticipant::refreshFromStorageIfNeeded() { - const auto opCtx = _opCtx(); +void TransactionParticipant::Participant::refreshFromStorageIfNeeded(OperationContext* opCtx) { invariant(!opCtx->getClient()->isInDirectClient()); invariant(!opCtx->lockState()->isLocked()); - if (_isValid) + if (p().isValid) return; auto activeTxnHistory = fetchActiveTransactionHistory(opCtx, _sessionId()); - - stdx::lock_guard<stdx::mutex> lg(_mutex); - const auto& lastTxnRecord = activeTxnHistory.lastTxnRecord; - if (lastTxnRecord) { - _activeTxnNumber = lastTxnRecord->getTxnNum(); - _lastWriteOpTime = lastTxnRecord->getLastWriteOpTime(); - _activeTxnCommittedStatements = std::move(activeTxnHistory.committedStatements); - _hasIncompleteHistory = activeTxnHistory.hasIncompleteHistory; + stdx::lock_guard<Client> lg(*opCtx->getClient()); + o(lg).activeTxnNumber = lastTxnRecord->getTxnNum(); + o(lg).lastWriteOpTime = lastTxnRecord->getLastWriteOpTime(); + p().activeTxnCommittedStatements = std::move(activeTxnHistory.committedStatements); + p().hasIncompleteHistory = activeTxnHistory.hasIncompleteHistory; if (activeTxnHistory.transactionCommitted) { - _txnState.transitionTo( - lg, + o(lg).txnState.transitionTo( TransactionState::kCommitted, TransactionState::TransitionValidation::kRelaxTransitionValidation); } } - _isValid = true; + p().isValid = true; } -void TransactionParticipant::onWriteOpCompletedOnPrimary( +void TransactionParticipant::Participant::onWriteOpCompletedOnPrimary( OperationContext* opCtx, TxnNumber txnNumber, std::vector<StmtId> stmtIdsWritten, @@ -1844,9 +1739,7 @@ void TransactionParticipant::onWriteOpCompletedOnPrimary( Date_t lastStmtIdWriteDate, boost::optional<DurableTxnStateEnum> txnState) { invariant(opCtx->lockState()->inAWriteUnitOfWork()); - invariant(txnNumber == _activeTxnNumber); - - stdx::unique_lock<stdx::mutex> ul(_mutex); + invariant(txnNumber == o().activeTxnNumber); // Sanity check that we don't double-execute statements for (const auto stmtId : stmtIdsWritten) { @@ -1860,97 +1753,89 @@ void TransactionParticipant::onWriteOpCompletedOnPrimary( const auto updateRequest = _makeUpdateRequest(lastStmtIdWriteOpTime, lastStmtIdWriteDate, txnState); - ul.unlock(); - repl::UnreplicatedWritesBlock doNotReplicateWrites(opCtx); updateSessionEntry(opCtx, updateRequest); - _registerUpdateCacheOnCommit(std::move(stmtIdsWritten), lastStmtIdWriteOpTime); + _registerUpdateCacheOnCommit(opCtx, std::move(stmtIdsWritten), lastStmtIdWriteOpTime); } -void TransactionParticipant::onMigrateCompletedOnPrimary(OperationContext* opCtx, - TxnNumber txnNumber, - std::vector<StmtId> stmtIdsWritten, - const repl::OpTime& lastStmtIdWriteOpTime, - Date_t oplogLastStmtIdWriteDate) { +void TransactionParticipant::Participant::onMigrateCompletedOnPrimary( + OperationContext* opCtx, + TxnNumber txnNumber, + std::vector<StmtId> stmtIdsWritten, + const repl::OpTime& lastStmtIdWriteOpTime, + Date_t oplogLastStmtIdWriteDate) { invariant(opCtx->lockState()->inAWriteUnitOfWork()); - invariant(txnNumber == _activeTxnNumber); - - stdx::unique_lock<stdx::mutex> ul(_mutex); - - _checkValid(ul); - _checkIsActiveTransaction(ul, txnNumber); + invariant(txnNumber == o().activeTxnNumber); // We do not migrate transaction oplog entries so don't set the txn state const auto txnState = boost::none; const auto updateRequest = _makeUpdateRequest(lastStmtIdWriteOpTime, oplogLastStmtIdWriteDate, txnState); - ul.unlock(); - repl::UnreplicatedWritesBlock doNotReplicateWrites(opCtx); updateSessionEntry(opCtx, updateRequest); - _registerUpdateCacheOnCommit(std::move(stmtIdsWritten), lastStmtIdWriteOpTime); + _registerUpdateCacheOnCommit(opCtx, std::move(stmtIdsWritten), lastStmtIdWriteOpTime); } -void TransactionParticipant::_invalidate(WithLock) { - _isValid = false; - _activeTxnNumber = kUninitializedTxnNumber; - _lastWriteOpTime = repl::OpTime(); +void TransactionParticipant::Participant::_invalidate(WithLock wl) { + p().isValid = false; + o(wl).activeTxnNumber = kUninitializedTxnNumber; + o(wl).lastWriteOpTime = repl::OpTime(); // Reset the transactions metrics. - stdx::lock_guard<stdx::mutex> lm(_metricsMutex); - _transactionMetricsObserver.resetSingleTransactionStats(_activeTxnNumber); + o(wl).transactionMetricsObserver.resetSingleTransactionStats(o().activeTxnNumber); } -void TransactionParticipant::_resetRetryableWriteState(WithLock) { - _activeTxnCommittedStatements.clear(); - _hasIncompleteHistory = false; +void TransactionParticipant::Participant::_resetRetryableWriteState() { + p().activeTxnCommittedStatements.clear(); + p().hasIncompleteHistory = false; } -void TransactionParticipant::_resetTransactionState(WithLock wl, - TransactionState::StateFlag state) { +void TransactionParticipant::Participant::_resetTransactionState( + WithLock wl, TransactionState::StateFlag state) { // If we are transitioning to kNone, we are either starting a new transaction or aborting a // prepared transaction for rollback. In the latter case, we will need to relax the invariant // that prevents transitioning from kPrepared to kNone. - if (_txnState.isPrepared(wl) && state == TransactionState::kNone) { - _txnState.transitionTo( - wl, state, TransactionState::TransitionValidation::kRelaxTransitionValidation); + if (o().txnState.isPrepared() && state == TransactionState::kNone) { + o(wl).txnState.transitionTo( + state, TransactionState::TransitionValidation::kRelaxTransitionValidation); } else { - _txnState.transitionTo(wl, state); + o(wl).txnState.transitionTo(state); } - _transactionOperationBytes = 0; - _transactionOperations.clear(); - _prepareOpTime = repl::OpTime(); - _oldestOplogEntryOpTime = boost::none; - _finishOpTime = boost::none; - _speculativeTransactionReadOpTime = repl::OpTime(); - _multikeyPathInfo.clear(); - _autoCommit = boost::none; + p().transactionOperationBytes = 0; + p().transactionOperations.clear(); + o(wl).prepareOpTime = repl::OpTime(); + p().oldestOplogEntryOpTime = boost::none; + p().finishOpTime = boost::none; + p().speculativeTransactionReadOpTime = repl::OpTime(); + p().multikeyPathInfo.clear(); + p().autoCommit = boost::none; // Release any locks held by this participant and abort the storage transaction. - _txnResourceStash = boost::none; + o(wl).txnResourceStash = boost::none; } -void TransactionParticipant::invalidate() { - stdx::lock_guard<stdx::mutex> lg(_mutex); +void TransactionParticipant::Participant::invalidate(OperationContext* opCtx) { + stdx::lock_guard<Client> lg(*opCtx->getClient()); uassert(ErrorCodes::PreparedTransactionInProgress, "Cannot invalidate prepared transaction", - !_txnState.isInSet( - lg, TransactionState::kPrepared | TransactionState::kCommittingWithPrepare)); + !o().txnState.isInSet(TransactionState::kPrepared | + TransactionState::kCommittingWithPrepare)); // Invalidate the session and clear both the retryable writes and transactional states on // this participant. _invalidate(lg); - _resetRetryableWriteState(lg); + _resetRetryableWriteState(); _resetTransactionState(lg, TransactionState::kNone); } -void TransactionParticipant::abortPreparedTransactionForRollback() { - stdx::lock_guard<stdx::mutex> lg(_mutex); +void TransactionParticipant::Participant::abortPreparedTransactionForRollback( + OperationContext* opCtx) { + stdx::lock_guard<Client> lg(*opCtx->getClient()); // Invalidate the session. _invalidate(lg); @@ -1958,7 +1843,7 @@ void TransactionParticipant::abortPreparedTransactionForRollback() { uassert(51030, str::stream() << "Cannot call abortPreparedTransactionForRollback on unprepared " << "transaction.", - _txnState.isPrepared(lg)); + o().txnState.isPrepared()); // It should be safe to clear transactionOperationBytes and transactionOperations because // we only modify these variables when adding an operation to a transaction. Since this @@ -1969,13 +1854,8 @@ void TransactionParticipant::abortPreparedTransactionForRollback() { _resetTransactionState(lg, TransactionState::kNone); } -repl::OpTime TransactionParticipant::getLastWriteOpTime() const { - stdx::lock_guard<stdx::mutex> lg(_mutex); - return _lastWriteOpTime; -} - -boost::optional<repl::OplogEntry> TransactionParticipant::checkStatementExecuted( - StmtId stmtId) const { +boost::optional<repl::OplogEntry> TransactionParticipant::Participant::checkStatementExecuted( + OperationContext* opCtx, StmtId stmtId) const { const auto stmtTimestamp = _checkStatementExecuted(stmtId); if (!stmtTimestamp) @@ -1983,7 +1863,7 @@ boost::optional<repl::OplogEntry> TransactionParticipant::checkStatementExecuted TransactionHistoryIterator txnIter(*stmtTimestamp); while (txnIter.hasNext()) { - const auto entry = txnIter.next(_opCtx()); + const auto entry = txnIter.next(opCtx); invariant(entry.getStatementId()); if (*entry.getStatementId() == stmtId) return entry; @@ -1992,38 +1872,23 @@ boost::optional<repl::OplogEntry> TransactionParticipant::checkStatementExecuted MONGO_UNREACHABLE; } -bool TransactionParticipant::checkStatementExecutedNoOplogEntryFetch(StmtId stmtId) const { +bool TransactionParticipant::Participant::checkStatementExecutedNoOplogEntryFetch( + StmtId stmtId) const { return bool(_checkStatementExecuted(stmtId)); } -void TransactionParticipant::_checkValid(WithLock) const { - uassert(ErrorCodes::ConflictingOperationInProgress, - str::stream() << "Session " << _sessionId() - << " was concurrently modified and the operation must be retried.", - _isValid); -} - -void TransactionParticipant::_checkIsActiveTransaction(WithLock, TxnNumber txnNumber) const { - uassert(ErrorCodes::ConflictingOperationInProgress, - str::stream() << "Cannot perform operations on transaction " << txnNumber - << " on session " - << _sessionId() - << " because a different transaction " - << _activeTxnNumber - << " is now active.", - txnNumber == _activeTxnNumber); -} - -boost::optional<repl::OpTime> TransactionParticipant::_checkStatementExecuted(StmtId stmtId) const { - invariant(_isValid); +boost::optional<repl::OpTime> TransactionParticipant::Participant::_checkStatementExecuted( + StmtId stmtId) const { + invariant(p().isValid); - const auto it = _activeTxnCommittedStatements.find(stmtId); - if (it == _activeTxnCommittedStatements.end()) { + const auto it = p().activeTxnCommittedStatements.find(stmtId); + if (it == p().activeTxnCommittedStatements.end()) { uassert(ErrorCodes::IncompleteTransactionHistory, - str::stream() << "Incomplete history detected for transaction " << _activeTxnNumber + str::stream() << "Incomplete history detected for transaction " + << o().activeTxnNumber << " on session " << _sessionId(), - !_hasIncompleteHistory); + !p().hasIncompleteHistory); return boost::none; } @@ -2031,7 +1896,7 @@ boost::optional<repl::OpTime> TransactionParticipant::_checkStatementExecuted(St return it->second; } -UpdateRequest TransactionParticipant::_makeUpdateRequest( +UpdateRequest TransactionParticipant::Participant::_makeUpdateRequest( const repl::OpTime& newLastWriteOpTime, Date_t newLastWriteDate, boost::optional<DurableTxnStateEnum> newState) const { @@ -2040,7 +1905,7 @@ UpdateRequest TransactionParticipant::_makeUpdateRequest( const auto updateBSON = [&] { SessionTxnRecord newTxnRecord; newTxnRecord.setSessionId(_sessionId()); - newTxnRecord.setTxnNum(_activeTxnNumber); + newTxnRecord.setTxnNum(o().activeTxnNumber); newTxnRecord.setLastWriteOpTime(newLastWriteOpTime); newTxnRecord.setLastWriteDate(newLastWriteDate); newTxnRecord.setState(newState); @@ -2053,34 +1918,37 @@ UpdateRequest TransactionParticipant::_makeUpdateRequest( return updateRequest; } -void TransactionParticipant::_registerUpdateCacheOnCommit( - std::vector<StmtId> stmtIdsWritten, const repl::OpTime& lastStmtIdWriteOpTime) { - _opCtx()->recoveryUnit()->onCommit( - [ this, stmtIdsWritten = std::move(stmtIdsWritten), lastStmtIdWriteOpTime ]( +void TransactionParticipant::Participant::_registerUpdateCacheOnCommit( + OperationContext* opCtx, + std::vector<StmtId> stmtIdsWritten, + const repl::OpTime& lastStmtIdWriteOpTime) { + opCtx->recoveryUnit()->onCommit( + [ opCtx, stmtIdsWritten = std::move(stmtIdsWritten), lastStmtIdWriteOpTime ]( boost::optional<Timestamp>) { - invariant(_isValid); + TransactionParticipant::Participant participant(opCtx); + invariant(participant.p().isValid); - RetryableWritesStats::get(getGlobalServiceContext()) + RetryableWritesStats::get(opCtx->getServiceContext()) ->incrementTransactionsCollectionWriteCount(); - stdx::lock_guard<stdx::mutex> lg(_mutex); + stdx::lock_guard<Client> lg(*opCtx->getClient()); // The cache of the last written record must always be advanced after a write so that // subsequent writes have the correct point to start from. - _lastWriteOpTime = lastStmtIdWriteOpTime; + participant.o(lg).lastWriteOpTime = lastStmtIdWriteOpTime; for (const auto stmtId : stmtIdsWritten) { if (stmtId == kIncompleteHistoryStmtId) { - _hasIncompleteHistory = true; + participant.p().hasIncompleteHistory = true; continue; } - const auto insertRes = - _activeTxnCommittedStatements.emplace(stmtId, lastStmtIdWriteOpTime); + const auto insertRes = participant.p().activeTxnCommittedStatements.emplace( + stmtId, lastStmtIdWriteOpTime); if (!insertRes.second) { const auto& existingOpTime = insertRes.first->second; - fassertOnRepeatedExecution(_sessionId(), - _activeTxnNumber, + fassertOnRepeatedExecution(participant._sessionId(), + participant.o().activeTxnNumber, stmtId, existingOpTime, lastStmtIdWriteOpTime); @@ -2093,7 +1961,7 @@ void TransactionParticipant::_registerUpdateCacheOnCommit( const auto closeConnectionElem = data["closeConnection"]; if (closeConnectionElem.eoo() || closeConnectionElem.Bool()) { - _opCtx()->getClient()->session()->end(); + opCtx->getClient()->session()->end(); } const auto failBeforeCommitExceptionElem = data["failBeforeCommitExceptionCode"]; @@ -2101,7 +1969,7 @@ void TransactionParticipant::_registerUpdateCacheOnCommit( const auto failureCode = ErrorCodes::Error(int(failBeforeCommitExceptionElem.Number())); uasserted(failureCode, str::stream() << "Failing write for " << _sessionId() << ":" - << _activeTxnNumber + << o().activeTxnNumber << " due to failpoint. The write must not be reflected."); } } diff --git a/src/mongo/db/transaction_participant.h b/src/mongo/db/transaction_participant.h index 650e1296f65..f90a12ed77b 100644 --- a/src/mongo/db/transaction_participant.h +++ b/src/mongo/db/transaction_participant.h @@ -42,6 +42,7 @@ #include "mongo/db/repl/oplog_entry.h" #include "mongo/db/repl/read_concern_args.h" #include "mongo/db/session.h" +#include "mongo/db/session_catalog.h" #include "mongo/db/session_txn_record_gen.h" #include "mongo/db/single_transaction_stats.h" #include "mongo/db/storage/recovery_unit.h" @@ -81,12 +82,96 @@ enum class TerminationCause { }; /** - * A state machine that coordinates a distributed transaction commit with the transaction - * coordinator. + * This class maintains the state of a transaction running on a server session. It can only exist as + * a decoration on the Session object and its state can only be modified by the thread which has the + * session checked-out. + * + * Its methods are split in two groups with distinct read/write and concurrency control rules. See + * the comments below for more information. */ class TransactionParticipant { MONGO_DISALLOW_COPYING(TransactionParticipant); + struct PrivateState; + struct ObservableState; + + /** + * Indicates the state of the current multi-document transaction, if any. If the transaction is + * in any state but kInProgress, no more operations can be collected. Once the transaction is in + * kPrepared, the transaction is not allowed to abort outside of an 'abortTransaction' command. + * At this point, aborting the transaction must log an 'abortTransaction' oplog entry. + */ + class TransactionState { + public: + enum StateFlag { + kNone = 1 << 0, + kInProgress = 1 << 1, + kPrepared = 1 << 2, + kCommittingWithoutPrepare = 1 << 3, + kCommittingWithPrepare = 1 << 4, + kCommitted = 1 << 5, + kAbortedWithoutPrepare = 1 << 6, + kAbortedWithPrepare = 1 << 7 + }; + + using StateSet = int; + bool isInSet(StateSet stateSet) const { + return _state & stateSet; + } + + /** + * Transitions the session from the current state to the new state. If transition validation + * is not relaxed, invariants if the transition is illegal. + */ + enum class TransitionValidation { kValidateTransition, kRelaxTransitionValidation }; + void transitionTo( + StateFlag newState, + TransitionValidation shouldValidate = TransitionValidation::kValidateTransition); + + bool inMultiDocumentTransaction() const { + return _state == kInProgress || _state == kPrepared; + } + + bool isNone() const { + return _state == kNone; + } + + bool isInProgress() const { + return _state == kInProgress; + } + + bool isPrepared() const { + return _state == kPrepared; + } + + bool isCommittingWithoutPrepare() const { + return _state == kCommittingWithoutPrepare; + } + + bool isCommittingWithPrepare() const { + return _state == kCommittingWithPrepare; + } + + bool isCommitted() const { + return _state == kCommitted; + } + + bool isAborted() const { + return _state == kAbortedWithPrepare || _state == kAbortedWithoutPrepare; + } + + std::string toString() const { + return toString(_state); + } + + static std::string toString(StateFlag state); + + private: + static bool _isLegalTransition(StateFlag oldState, StateFlag newState); + + StateFlag _state = kNone; + }; + public: /** * Holds state for a snapshot read or multi-statement transaction in between network @@ -98,9 +183,9 @@ public: /** * Stashes transaction state from 'opCtx' in the newly constructed TxnResources. - * Ephemerally holds the Client lock associated with opCtx. + * Caller must hold the Client lock associated with opCtx, attested by WithLock. */ - TxnResources(OperationContext* opCtx, StashStyle stashStyle); + TxnResources(WithLock, OperationContext* opCtx, StashStyle stashStyle) noexcept; ~TxnResources(); // Rule of 5: because we have a class-defined destructor, we need to explictly specify @@ -146,11 +231,6 @@ public: SideTransactionBlock(OperationContext* opCtx); ~SideTransactionBlock(); - // Rule of 5: because we have a class-defined destructor, we need to explictly specify - // the move operator and move assignment operator. - SideTransactionBlock(SideTransactionBlock&&) = default; - SideTransactionBlock& operator=(SideTransactionBlock&&) = default; - private: boost::optional<TxnResources> _txnResources; OperationContext* _opCtx; @@ -160,413 +240,566 @@ public: static const BSONObj kDeadEndSentinel; - TransactionParticipant(); - ~TransactionParticipant(); - /** - * Obtains the transaction participant from a session and a syntactic sugar variant, which - * obtains it from an operation context on which the session has been checked-out. + * Class used by observers to examine the state of a TransactionParticipant. */ - static TransactionParticipant* get(OperationContext* opCtx); - static TransactionParticipant* get(Session* session); + class Observer { + public: + explicit Observer(const ObservableSession& session); - /** - * When the server returns a NoSuchTransaction error for a command, it performs a noop write if - * there is a writeConcern on the command. The TransientTransactionError label is only appended - * to a NoSuchTransaction response for 'commitTransaction' and 'coordinateCommitTransaction' if - * there is no writeConcern error. This ensures that if 'commitTransaction' or - * 'coordinateCommitTransaction' is run with w:majority, then the TransientTransactionError - * label is only returned if the transaction is not committed on any valid branch of history, - * so the driver or application can safely retry the entire transaction. - */ - static void performNoopWriteForNoSuchTransaction(OperationContext* opCtx); + /** + * Returns the currently active transaction number on this participant. + */ + TxnNumber getActiveTxnNumber() const { + return o().activeTxnNumber; + } - /** - * Blocking method, which loads the transaction state from storage if it has been marked as - * needing refresh. - * - * In order to avoid the possibility of deadlock, this method must not be called while holding a - * lock. - */ - void refreshFromStorageIfNeeded(); + /** + * Returns the op time of the last committed write for this session and transaction. If no + * write has completed yet, returns an empty timestamp. + */ + repl::OpTime getLastWriteOpTime() const { + return o().lastWriteOpTime; + } - /** - * Starts a new transaction (and if the txnNumber is newer aborts any in-progress transaction on - * the session), or continues an already active transaction. - * - * 'autocommit' comes from the 'autocommit' field in the original client request. The only valid - * values are boost::none (meaning no autocommit was specified) and false (meaning that this is - * the beginning of a multi-statement transaction). - * - * 'startTransaction' comes from the 'startTransaction' field in the original client request. - * See below for the acceptable values and the meaning of the combinations of autocommit and - * startTransaction. - * - * autocommit = boost::none, startTransaction = boost::none: Means retryable write - * autocommit = false, startTransaction = boost::none: Means continuation of a multi-statement - * transaction - * autocommit = false, startTransaction = true: Means abort whatever transaction is in progress - * on the session and start a new transaction - * - * Any combination other than the ones listed above will invariant since it is expected that the - * caller has performed the necessary customer input validations. - * - * Exceptions of note, which can be thrown are: - * - TransactionTooOld - if attempt is made to start a transaction older than the currently - * active one or the last one which committed - * - PreparedTransactionInProgress - if the transaction is in the prepared state and a new - * transaction or retryable write is attempted - */ - void beginOrContinue(TxnNumber txnNumber, - boost::optional<bool> autocommit, - boost::optional<bool> startTransaction); + /** + * Returns the prepare op time that was selected for the transaction, which can be Null if + * the transaction is not prepared. + */ + repl::OpTime getPrepareOpTime() const { + return o().prepareOpTime; + } - /** - * Used only by the secondary oplog application logic. Equivalent to 'beginOrContinue(txnNumber, - * false, true)' without performing any checks for whether the new txnNumber will start a - * transaction number in the past. - * - * NOTE: This method assumes that there are no concurrent users of the transaction since it - * unconditionally changes the active transaction on the session. - */ - void beginOrContinueTransactionUnconditionally(TxnNumber txnNumber); + /** + * Returns whether the transaction has exceeded its expiration time. + */ + bool expiredAsOf(Date_t when) const; - /** - * Transfers management of transaction resources from the OperationContext to the Session. - */ - void stashTransactionResources(OperationContext* opCtx); + /** + * Returns whether we are in a multi-document transaction, which means we have an active + * transaction which has autocommit:false and has not been committed or aborted. It is + * possible that the current transaction is stashed onto the stack via a + * `SideTransactionBlock`. + */ + bool inMultiDocumentTransaction() const { + return o().txnState.inMultiDocumentTransaction(); + }; - /** - * Transfers management of transaction resources from the Session to the OperationContext. - */ - void unstashTransactionResources(OperationContext* opCtx, const std::string& cmdName); + bool transactionIsCommitted() const { + return o().txnState.isCommitted(); + } - /** - * Puts a transaction into a prepared state and returns the prepareTimestamp. - * - * On secondary, the "prepareTimestamp" will be given in the oplog. - */ - Timestamp prepareTransaction(OperationContext* opCtx, - boost::optional<repl::OpTime> prepareOptime); + bool transactionIsAborted() const { + return o().txnState.isAborted(); + } - /** - * Commits the transaction, including committing the write unit of work and updating - * transaction state. - * - * Throws an exception if the transaction is prepared. - */ - void commitUnpreparedTransaction(OperationContext* opCtx); + bool transactionIsPrepared() const { + return o().txnState.isPrepared(); + } - /** - * Commits the transaction, including committing the write unit of work and updating - * transaction state. - * - * On a secondary, the "commitOplogEntryOpTime" will be the OpTime of the commitTransaction oplog - * entry. - * - * Throws an exception if the transaction is not prepared or if the 'commitTimestamp' is null. - */ - void commitPreparedTransaction(OperationContext* opCtx, - Timestamp commitTimestamp, - boost::optional<repl::OpTime> commitOplogEntryOpTime); + /** + * Returns true if we are in an active multi-document transaction or if the transaction has + * been aborted. This is used to cover the case where a transaction has been aborted, but + * the + * OperationContext state has not been cleared yet. + */ + bool inActiveOrKilledMultiDocumentTransaction() const { + return o().txnState.inMultiDocumentTransaction() || o().txnState.isAborted(); + } - /** - * Aborts the transaction outside the transaction, releasing transaction resources. - * - * Not called with session checked out. - */ - void abortArbitraryTransaction(); - - /* - * Aborts the transaction inside the transaction, releasing transaction resources. - * We're inside the transaction when we have the Session checked out and 'opCtx' owns the - * transaction resources. - * Aborts the transaction and releases transaction resources when we have the Session checked - * out and 'opCtx' owns the transaction resources. - */ - void abortActiveTransaction(OperationContext* opCtx); + /** + * If this session is holding stashed locks in txnResourceStash, reports the current state + * of the session using the provided builder. + */ + BSONObj reportStashedState(OperationContext* opCtx) const; + void reportStashedState(OperationContext* opCtx, BSONObjBuilder* builder) const; - /* - * If the transaction is prepared, stash its resources. If not, it's the same as - * abortActiveTransaction. - */ - void abortActiveUnpreparedOrStashPreparedTransaction(OperationContext* opCtx); + /** + * If this session is not holding stashed locks in txnResourceStash (transaction is active), + * reports the current state of the session using the provided builder. + */ + void reportUnstashedState(OperationContext* opCtx, BSONObjBuilder* builder) const; - /** - * Aborts the storage transaction of the prepared transaction on this participant by releasing - * its resources. Also invalidates the session and the current transaction state. - * Avoids writing any oplog entries or making any changes to the transaction table since the - * state for prepared transactions will be re-constituted during replication recovery. - */ - void abortPreparedTransactionForRollback(); + protected: + explicit Observer(TransactionParticipant* tp) : _tp(tp) {} - /** - * Adds a stored operation to the list of stored operations for the current multi-document - * (non-autocommit) transaction. It is illegal to add operations when no multi-document - * transaction is in progress. - */ - void addTransactionOperation(OperationContext* opCtx, const repl::ReplOperation& operation); + const TransactionParticipant::ObservableState& o() const { + return _tp->_o; + } - /** - * Returns a reference to the stored operations for a completed multi-document (non-autocommit) - * transaction. "Completed" implies that no more operations will be added to the transaction. - * It is legal to call this method only when the transaction state is in progress or committed. - */ - std::vector<repl::ReplOperation>& retrieveCompletedTransactionOperations( - OperationContext* opCtx); + const LogicalSessionId& _sessionId() const; - /** - * Clears the stored operations for an multi-document (non-autocommit) transaction, marking - * the transaction as closed. It is illegal to attempt to add operations to the transaction - * after this is called. - */ - void clearOperationsInMemory(OperationContext* opCtx); + // Reports transaction stats for both active and inactive transactions using the provided + // builder. + void _reportTransactionStats(OperationContext* opCtx, + BSONObjBuilder* builder, + repl::ReadConcernArgs readConcernArgs) const; - /** - * Yield or reacquire locks for prepared transacitons, used on replication state transition. - */ - void refreshLocksForPreparedTransaction(OperationContext* opCtx, bool yieldLocks); + TransactionParticipant* _tp; + }; // class Observer - /** - * May only be called while a multi-document transaction is not committed and adds the multi-key - * path info to the set of path infos to be updated at commit time. - */ - void addUncommittedMultikeyPathInfo(MultikeyPathInfo info) { - invariant(inMultiDocumentTransaction()); - _multikeyPathInfo.emplace_back(std::move(info)); - } /** - * May only be called while a mutil-document transaction is not committed and returns the path - * infos which have been added so far. + * Class used by a thread that has checked out the TransactionParticipant's session to + * observe and modify the transaction participant. */ - const std::vector<MultikeyPathInfo>& getUncommittedMultikeyPathInfos() const { - invariant(inMultiDocumentTransaction()); - return _multikeyPathInfo; - } + class Participant : public Observer { + public: + explicit Participant(OperationContext* opCtx); + explicit Participant(const SessionToKill& session); - /** - * Called after a write under the specified transaction completes while the node is a primary - * and specifies the statement ids which were written. Must be called while the caller is still - * in the write's WUOW. Updates the on-disk state of the session to match the specified - * transaction/opTime and keeps the cached state in sync. - * - * 'txnState' is 'none' for retryable writes. - * - * Must only be called with the session checked-out. - * - * Throws if the session has been invalidated or the active transaction number doesn't match. - */ - void onWriteOpCompletedOnPrimary(OperationContext* opCtx, - TxnNumber txnNumber, - std::vector<StmtId> stmtIdsWritten, - const repl::OpTime& lastStmtIdWriteOpTime, - Date_t lastStmtIdWriteDate, - boost::optional<DurableTxnStateEnum> txnState); + explicit operator bool() const { + return _tp; + } - /** - * Called after an entry for the specified session and transaction has been written to the oplog - * during chunk migration, while the node is still primary. Must be called while the caller is - * still in the oplog write's WUOW. Updates the on-disk state of the session to match the - * specified transaction/opTime and keeps the cached state in sync. - * - * May be called concurrently with onWriteOpCompletedOnPrimary or onMigrateCompletedOnPrimary - * and doesn't require the session to be checked-out. - * - * Throws if the session has been invalidated or the active transaction number is newer than the - * one specified. - */ - void onMigrateCompletedOnPrimary(OperationContext* opCtx, - TxnNumber txnNumber, - std::vector<StmtId> stmtIdsWritten, - const repl::OpTime& lastStmtIdWriteOpTime, - Date_t oplogLastStmtIdWriteDate); + /** + * Blocking method, which loads the transaction state from storage if it has been marked as + * needing refresh. + * + * In order to avoid the possibility of deadlock, this method must not be called while + * holding a + * lock. + */ + void refreshFromStorageIfNeeded(OperationContext* opCtx); - /** - * Checks whether the given statementId for the specified transaction has already executed and - * if so, returns the oplog entry which was generated by that write. If the statementId hasn't - * executed, returns boost::none. - * - * Must only be called with the session checked-out. - * - * Throws if the session has been invalidated or the active transaction number doesn't match. - */ - boost::optional<repl::OplogEntry> checkStatementExecuted(StmtId stmtId) const; + /** + * Starts a new transaction (and if the txnNumber is newer aborts any in-progress + * transaction on + * the session), or continues an already active transaction. + * + * 'autocommit' comes from the 'autocommit' field in the original client request. The only + * valid + * values are boost::none (meaning no autocommit was specified) and false (meaning that this + * is + * the beginning of a multi-statement transaction). + * + * 'startTransaction' comes from the 'startTransaction' field in the original client + * request. + * See below for the acceptable values and the meaning of the combinations of autocommit and + * startTransaction. + * + * autocommit = boost::none, startTransaction = boost::none: Means retryable write + * autocommit = false, startTransaction = boost::none: Means continuation of a + * multi-statement + * transaction + * autocommit = false, startTransaction = true: Means abort whatever transaction is in + * progress + * on the session and start a new transaction + * + * Any combination other than the ones listed above will invariant since it is expected that + * the + * caller has performed the necessary customer input validations. + * + * Exceptions of note, which can be thrown are: + * - TransactionTooOld - if attempt is made to start a transaction older than the + * currently + * active one or the last one which committed + * - PreparedTransactionInProgress - if the transaction is in the prepared state and a new + * transaction or retryable write is attempted + */ + void beginOrContinue(OperationContext* opCtx, + TxnNumber txnNumber, + boost::optional<bool> autocommit, + boost::optional<bool> startTransaction); - /** - * Checks whether the given statementId for the specified transaction has already executed - * without fetching the oplog entry which was generated by that write. - * - * Must only be called with the session checked-out. - * - * Throws if the session has been invalidated or the active transaction number doesn't match. - */ - bool checkStatementExecutedNoOplogEntryFetch(StmtId stmtId) const; + /** + * Used only by the secondary oplog application logic. Equivalent to + * 'beginOrContinue(txnNumber, + * false, true)' without performing any checks for whether the new txnNumber will start a + * transaction number in the past. + */ + void beginOrContinueTransactionUnconditionally(OperationContext* opCtx, + TxnNumber txnNumber); - /** - * Marks the session as requiring refresh. Used when the session state has been modified - * externally, such as through a direct write to the transactions table. - */ - void invalidate(); + /** + * Transfers management of transaction resources from the currently checked-out + * OperationContext + * to the Session. + */ + void stashTransactionResources(OperationContext* opCtx); - /** - * Kills the transaction if it is running, ensuring that it releases all resources, even if the - * transaction is in prepare(). Avoids writing any oplog entries or making any changes to the - * transaction table. State for prepared transactions will be re-constituted at startup. - * Note that we don't take any active steps to prevent continued use of this - * TransactionParticipant after shutdown() is called, but we rely on callers to not - * continue using the TransactionParticipant once we are in shutdown. - */ - void shutdown(); + /** + * Transfers management of transaction resources from the Session to the currently + * checked-out + * OperationContext. + */ + void unstashTransactionResources(OperationContext* opCtx, const std::string& cmdName); - /** - * Returns the currently active transaction number on this participant. - */ - TxnNumber getActiveTxnNumber() const { - stdx::lock_guard<stdx::mutex> lg(_mutex); - return _activeTxnNumber; - } + /** + * Puts a transaction into a prepared state and returns the prepareTimestamp. + * + * On secondary, the "prepareTimestamp" will be given in the oplog. + */ + Timestamp prepareTransaction(OperationContext* opCtx, + boost::optional<repl::OpTime> prepareOptime); - /** - * Returns the op time of the last committed write for this session and transaction. If no write - * has completed yet, returns an empty timestamp. - * - * Throws if the session has been invalidated or the active transaction number doesn't match. - */ - repl::OpTime getLastWriteOpTime() const; + /** + * Commits the transaction, including committing the write unit of work and updating + * transaction state. + * + * Throws an exception if the transaction is prepared. + */ + void commitUnpreparedTransaction(OperationContext* opCtx); - /** - * Returns the prepare op time that was selected for the transaction, which can be Null if the - * transaction is not prepared. - */ - repl::OpTime getPrepareOpTime() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return _prepareOpTime; - } + /** + * Commits the transaction, including committing the write unit of work and updating + * transaction state. + * + * On a secondary, the "commitOplogEntryOpTime" will be the OpTime of the commitTransaction + * oplog entry. + * + * Throws an exception if the transaction is not prepared or if the 'commitTimestamp' is + * null. + */ + void commitPreparedTransaction(OperationContext* opCtx, + Timestamp commitTimestamp, + boost::optional<repl::OpTime> commitOplogEntryOpTime); - /** - * Returns whether the transaction has exceeded its expiration time. - */ - bool expired() const; + /** + * Aborts the transaction, if it is not in the "prepared" state. + */ + void abortTransactionIfNotPrepared(OperationContext* opCtx); - /** - * Returns whether we are in a multi-document transaction, which means we have an active - * transaction which has autoCommit:false and has not been committed or aborted. It is possible - * that the current transaction is stashed onto the stack via a `SideTransactionBlock`. - */ - bool inMultiDocumentTransaction() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return _txnState.inMultiDocumentTransaction(lk); - }; + /* + * Aborts the transaction, releasing transaction resources. + */ + void abortActiveTransaction(OperationContext* opCtx); - bool transactionIsCommitted() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return _txnState.isCommitted(lk); - } + /* + * If the transaction is prepared, stash its resources. If not, it's the same as + * abortActiveTransaction. + */ + void abortActiveUnpreparedOrStashPreparedTransaction(OperationContext* opCtx); - bool transactionIsAborted() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return _txnState.isAborted(lk); - } + /** + * Aborts the storage transaction of the prepared transaction on this participant by + * releasing its resources. Also invalidates the session and the current transaction state. + * Avoids writing any oplog entries or making any changes to the transaction table since the + * state for prepared transactions will be re-constituted during replication recovery. + */ + void abortPreparedTransactionForRollback(OperationContext* opCtx); - bool transactionIsPrepared() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return _txnState.isPrepared(lk); - } + /** + * Adds a stored operation to the list of stored operations for the current multi-document + * (non-autocommit) transaction. It is illegal to add operations when no multi-document + * transaction is in progress. + */ + void addTransactionOperation(OperationContext* opCtx, const repl::ReplOperation& operation); - /** - * Returns true if we are in an active multi-document transaction or if the transaction has - * been aborted. This is used to cover the case where a transaction has been aborted, but the - * OperationContext state has not been cleared yet. - */ - bool inActiveOrKilledMultiDocumentTransaction() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return (_txnState.inMultiDocumentTransaction(lk) || _txnState.isAborted(lk)); - } + /** + * Returns a reference to the stored operations for a completed multi-document + * (non-autocommit) transaction. "Completed" implies that no more operations will be added + * to the transaction. It is legal to call this method only when the transaction state is + * in progress or committed. + */ + std::vector<repl::ReplOperation>& retrieveCompletedTransactionOperations( + OperationContext* opCtx); - /** - * If this session is holding stashed locks in _txnResourceStash, reports the current state of - * the session using the provided builder. Locks the session object's mutex while running. - */ - BSONObj reportStashedState() const; - void reportStashedState(BSONObjBuilder* builder) const; + /** + * Clears the stored operations for an multi-document (non-autocommit) transaction, marking + * the transaction as closed. It is illegal to attempt to add operations to the transaction + * after this is called. + */ + void clearOperationsInMemory(OperationContext* opCtx); - /** - * If this session is not holding stashed locks in _txnResourceStash (transaction is active), - * reports the current state of the session using the provided builder. Locks the session - * object's mutex while running. - * - * If this is called from a thread other than the owner of the opCtx, that thread must be - * holding the client lock. - */ - void reportUnstashedState(OperationContext* opCtx, BSONObjBuilder* builder) const; - - // - // Methods used for unit-testing only - // - - std::string getTransactionInfoForLogForTest( - const SingleThreadedLockStats* lockStats, - bool committed, - const repl::ReadConcernArgs& readConcernArgs) const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - TerminationCause terminationCause = - committed ? TerminationCause::kCommitted : TerminationCause::kAborted; - return _transactionInfoForLog(lockStats, terminationCause, readConcernArgs); - } + /** + * Yield or reacquire locks for prepared transactions, used on replication state transition. + */ + void refreshLocksForPreparedTransaction(OperationContext* opCtx, bool yieldLocks); - SingleTransactionStats getSingleTransactionStatsForTest() const { - stdx::lock_guard<stdx::mutex> lk(_metricsMutex); - return _transactionMetricsObserver.getSingleTransactionStats(); - } + /** + * May only be called while a multi-document transaction is not committed and adds the + * multi-key + * path info to the set of path infos to be updated at commit time. + */ + void addUncommittedMultikeyPathInfo(MultikeyPathInfo info) { + invariant(inMultiDocumentTransaction()); + p().multikeyPathInfo.emplace_back(std::move(info)); + } - std::vector<repl::ReplOperation> getTransactionOperationsForTest() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return _transactionOperations; - } + /** + * May only be called while a mutil-document transaction is not committed and returns the + * path + * infos which have been added so far. + */ + const std::vector<MultikeyPathInfo>& getUncommittedMultikeyPathInfos() const { + invariant(inMultiDocumentTransaction()); + return p().multikeyPathInfo; + } - repl::OpTime getSpeculativeTransactionReadOpTimeForTest() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return _speculativeTransactionReadOpTime; - } + /** + * Called after a write under the specified transaction completes while the node is a + * primary + * and specifies the statement ids which were written. Must be called while the caller is + * still + * in the write's WUOW. Updates the on-disk state of the session to match the specified + * transaction/opTime and keeps the cached state in sync. + * + * 'txnState' is 'none' for retryable writes. + * + * Throws if the session has been invalidated or the active transaction number doesn't + * match. + */ + void onWriteOpCompletedOnPrimary(OperationContext* opCtx, + TxnNumber txnNumber, + std::vector<StmtId> stmtIdsWritten, + const repl::OpTime& lastStmtIdWriteOpTime, + Date_t lastStmtIdWriteDate, + boost::optional<DurableTxnStateEnum> txnState); - boost::optional<repl::OpTime> getFinishOpTimeForTest() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return _finishOpTime; - } + /** + * Called after an entry for the specified session and transaction has been written to the + * oplog during chunk migration, while the node is still primary. Must be called while the + * caller is still in the oplog write's WUOW. Updates the on-disk state of the session to + * match the specified transaction/opTime and keeps the cached state in sync. + * + * Throws if the session has been invalidated or the active transaction number is newer than + * the one specified. + */ + void onMigrateCompletedOnPrimary(OperationContext* opCtx, + TxnNumber txnNumber, + std::vector<StmtId> stmtIdsWritten, + const repl::OpTime& lastStmtIdWriteOpTime, + Date_t oplogLastStmtIdWriteDate); - boost::optional<repl::OpTime> getOldestOplogEntryOpTimeForTest() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - return _oldestOplogEntryOpTime; - } + /** + * Checks whether the given statementId for the specified transaction has already executed + * and if so, returns the oplog entry which was generated by that write. If the statementId + * hasn't executed, returns boost::none. + * + * Throws if the session has been invalidated or the active transaction number doesn't + * match. + */ + boost::optional<repl::OplogEntry> checkStatementExecuted(OperationContext* opCtx, + StmtId stmtId) const; - const Locker* getTxnResourceStashLockerForTest() const { - stdx::lock_guard<stdx::mutex> lk(_mutex); - invariant(_txnResourceStash); - return _txnResourceStash->locker(); - } + /** + * Checks whether the given statementId for the specified transaction has already executed + * without fetching the oplog entry which was generated by that write. + * + * Throws if the session has been invalidated or the active transaction number doesn't + * match. + */ + bool checkStatementExecutedNoOplogEntryFetch(StmtId stmtId) const; - void transitionToPreparedforTest() { - stdx::lock_guard<stdx::mutex> lk(_mutex); - _txnState.transitionTo(lk, TransactionState::kPrepared); - } + /** + * Marks the session as requiring refresh. Used when the session state has been modified + * externally, such as through a direct write to the transactions table. + */ + void invalidate(OperationContext* opCtx); - void transitionToCommittingWithPrepareforTest() { - stdx::lock_guard<stdx::mutex> lk(_mutex); - _txnState.transitionTo(lk, TransactionState::kCommittingWithPrepare); - } + /** + * Kills the transaction if it is running, ensuring that it releases all resources, even if + * the transaction is in prepare(). Avoids writing any oplog entries or making any changes + * to the transaction table. State for prepared transactions will be re-constituted at + * startup. Note that we don't take any active steps to prevent continued use of this + * TransactionParticipant after shutdown() is called, but we rely on callers to not continue + * using the TransactionParticipant once we are in shutdown. + */ + void shutdown(OperationContext* opCtx); + + // + // Methods for use in C++ unit tests, only. Beware: these methods may not adhere to the + // concurrency control rules. + // + + std::string getTransactionInfoForLogForTest( + OperationContext* opCtx, + const SingleThreadedLockStats* lockStats, + bool committed, + const repl::ReadConcernArgs& readConcernArgs) const { + + TerminationCause terminationCause = + committed ? TerminationCause::kCommitted : TerminationCause::kAborted; + return _transactionInfoForLog(opCtx, lockStats, terminationCause, readConcernArgs); + } + + SingleTransactionStats getSingleTransactionStatsForTest() const { + return o().transactionMetricsObserver.getSingleTransactionStats(); + } + + std::vector<repl::ReplOperation> getTransactionOperationsForTest() const { + return p().transactionOperations; + } + + repl::OpTime getSpeculativeTransactionReadOpTimeForTest() const { + return p().speculativeTransactionReadOpTime; + } + + boost::optional<repl::OpTime> getOldestOplogEntryOpTimeForTest() const { + return p().oldestOplogEntryOpTime; + } + + boost::optional<repl::OpTime> getFinishOpTimeForTest() const { + return p().finishOpTime; + } + + const Locker* getTxnResourceStashLockerForTest() const { + invariant(o().txnResourceStash); + return o().txnResourceStash->locker(); + } + + void transitionToPreparedforTest(OperationContext* opCtx) { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).txnState.transitionTo(TransactionState::kPrepared); + } + + void transitionToCommittingWithPrepareforTest(OperationContext* opCtx) { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).txnState.transitionTo(TransactionState::kCommittingWithPrepare); + } + void transitionToAbortedWithoutPrepareforTest(OperationContext* opCtx) { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).txnState.transitionTo(TransactionState::kAbortedWithoutPrepare); + } + + void transitionToAbortedWithPrepareforTest(OperationContext* opCtx) { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + o(lk).txnState.transitionTo(TransactionState::kAbortedWithPrepare); + } - void transitionToAbortedWithoutPrepareforTest() { - stdx::lock_guard<stdx::mutex> lk(_mutex); - _txnState.transitionTo(lk, TransactionState::kAbortedWithoutPrepare); + private: + boost::optional<repl::OpTime> _checkStatementExecuted(StmtId stmtId) const; + + UpdateRequest _makeUpdateRequest(const repl::OpTime& newLastWriteOpTime, + Date_t newLastWriteDate, + boost::optional<DurableTxnStateEnum> newState) const; + + void _registerUpdateCacheOnCommit(OperationContext* opCtx, + std::vector<StmtId> stmtIdsWritten, + const repl::OpTime& lastStmtIdWriteTs); + + // Called for speculative transactions to fix the optime of the snapshot to read from. + void _setSpeculativeTransactionOpTime(OperationContext* opCtx, + SpeculativeTransactionOpTime opTimeChoice); + + + // Like _setSpeculativeTransactionOpTime, but caller chooses timestamp of snapshot + // explicitly. + // It is up to the caller to ensure that Timestamp is greater than or equal to the + // all-committed + // optime before calling this method (e.g. by calling + // ReplCoordinator::waitForOpTimeForRead). + void _setSpeculativeTransactionReadTimestamp(OperationContext* opCtx, Timestamp timestamp); + + // Finishes committing the multi-document transaction after the storage-transaction has been + // committed, the oplog entry has been inserted into the oplog, and the transactions table + // has + // been updated. + void _finishCommitTransaction(OperationContext* opCtx); + + // Commits the storage-transaction on the OperationContext. + // + // This should be called *without* the Client being locked. + void _commitStorageTransaction(OperationContext* opCtx); + + // Stash transaction resources. + void _stashActiveTransaction(OperationContext* opCtx); + + // Abort the transaction if it's in one of the expected states and clean up the transaction + // states associated with the opCtx. + void _abortActiveTransaction(OperationContext* opCtx, + TransactionState::StateSet expectedStates); + + // Releases stashed transaction resources to abort the transaction on the session. + void _abortTransactionOnSession(OperationContext* opCtx); + + // Clean up the transaction resources unstashed on operation context. + void _cleanUpTxnResourceOnOpCtx(OperationContext* opCtx, TerminationCause terminationCause); + + // Checks if the command can be run on this transaction based on the state of the + // transaction. + void _checkIsCommandValidWithTxnState(const TxnNumber& requestTxnNumber, + const std::string& cmdName) const; + + // Logs the transaction information if it has run slower than the global parameter slowMS. + // The + // transaction must be committed or aborted when this function is called. + void _logSlowTransaction(OperationContext* opCtx, + const SingleThreadedLockStats* lockStats, + TerminationCause terminationCause, + repl::ReadConcernArgs readConcernArgs); + + // This method returns a string with information about a slow transaction. The format of the + // logging string produced should match the format used for slow operation logging. A + // transaction must be completed (committed or aborted) and a valid LockStats reference must + // be + // passed in order for this method to be called. + std::string _transactionInfoForLog(OperationContext* opCtx, + const SingleThreadedLockStats* lockStats, + TerminationCause terminationCause, + repl::ReadConcernArgs readConcernArgs) const; + + // Bumps up the transaction number of this transaction and perform the necessary cleanup. + void _setNewTxnNumber(OperationContext* opCtx, const TxnNumber& txnNumber); + + // Attempt to begin or retry a retryable write at the given transaction number. + void _beginOrContinueRetryableWrite(OperationContext* opCtx, TxnNumber txnNumber); + + // Attempt to begin a new multi document transaction at the given transaction number. + void _beginMultiDocumentTransaction(OperationContext* opCtx, TxnNumber txnNumber); + + // Attempt to continue an in-progress multi document transaction at the given transaction + // number. + void _continueMultiDocumentTransaction(OperationContext* opCtx, TxnNumber txnNumber); + + // Helper that invalidates the session state and activeTxnNumber. Also resets the single + // transaction stats because the session is no longer valid. + void _invalidate(WithLock); + + // Helper that resets the retryable writes state. + void _resetRetryableWriteState(); + + // Helper that resets the transactional state. This is used when aborting a transaction, + // invalidating a transaction, or starting a new transaction. + void _resetTransactionState(WithLock wl, TransactionState::StateFlag state); + + // Helper that updates ServerTransactionsMetrics once a transaction commits. + void _updateTxnMetricsOnCommit(OperationContext* opCtx, bool isCommittingWithPrepare); + + // Releases the resources held in *o().txnResources to the operation context. + // o().txnResources must be engaged prior to calling this. + void _releaseTransactionResourcesToOpCtx(OperationContext* opCtx); + + TransactionParticipant::PrivateState& p() { + return _tp->_p; + } + const TransactionParticipant::PrivateState& p() const { + return _tp->_p; + } + TransactionParticipant::ObservableState& o(WithLock) { + return _tp->_o; + } + using Observer::o; + }; // class Participant + + static Participant get(OperationContext* opCtx) { + return Participant(opCtx); } - void transitionToAbortedWithPrepareforTest() { - stdx::lock_guard<stdx::mutex> lk(_mutex); - _txnState.transitionTo(lk, TransactionState::kAbortedWithPrepare); + static Participant get(const SessionToKill& session) { + return Participant(session); } + static Observer get(const ObservableSession& osession) { + return Observer(osession); + } + + /** + * When the server returns a NoSuchTransaction error for a command, it performs a noop write if + * there is a writeConcern on the command. The TransientTransactionError label is only appended + * to a NoSuchTransaction response for 'commitTransaction' and 'coordinateCommitTransaction' if + * there is no writeConcern error. This ensures that if 'commitTransaction' or + * 'coordinateCommitTransaction' is run with w:majority, then the TransientTransactionError + * label is only returned if the transaction is not committed on any valid branch of history, + * so the driver or application can safely retry the entire transaction. + */ + static void performNoopWriteForNoSuchTransaction(OperationContext* opCtx); + + TransactionParticipant() = default; + ~TransactionParticipant() = default; + private: /** * Reserves a slot in the oplog with an open storage-transaction while it is alive. Reserves the @@ -578,11 +811,6 @@ private: OplogSlotReserver(OperationContext* opCtx); ~OplogSlotReserver(); - // Rule of 5: because we have a class-defined destructor, we need to explictly specify - // the move operator and move assignment operator. - OplogSlotReserver(OplogSlotReserver&&) = default; - OplogSlotReserver& operator=(OplogSlotReserver&&) = default; - /** * Returns the oplog slot reserved at construction. */ @@ -598,299 +826,103 @@ private: OplogSlot _oplogSlot; }; - /** - * Indicates the state of the current multi-document transaction, if any. If the transaction is - * in any state but kInProgress, no more operations can be collected. Once the transaction is in - * kPrepared, the transaction is not allowed to abort outside of an 'abortTransaction' command. - * At this point, aborting the transaction must log an 'abortTransaction' oplog entry. - */ - class TransactionState { - public: - enum StateFlag { - kNone = 1 << 0, - kInProgress = 1 << 1, - kPrepared = 1 << 2, - kCommittingWithoutPrepare = 1 << 3, - kCommittingWithPrepare = 1 << 4, - kCommitted = 1 << 5, - kAbortedWithoutPrepare = 1 << 6, - kAbortedWithPrepare = 1 << 7 - }; - - using StateSet = int; - bool isInSet(WithLock, StateSet stateSet) const { - return _state & stateSet; - } - - /** - * Transitions the session from the current state to the new state. If transition validation - * is not relaxed, invariants if the transition is illegal. - */ - enum class TransitionValidation { kValidateTransition, kRelaxTransitionValidation }; - void transitionTo( - WithLock, - StateFlag newState, - TransitionValidation shouldValidate = TransitionValidation::kValidateTransition); - - bool inMultiDocumentTransaction(WithLock) const { - return _state == kInProgress || _state == kPrepared; - } - - bool isNone(WithLock) const { - return _state == kNone; - } + friend std::ostream& operator<<(std::ostream& s, TransactionState txnState) { + return (s << txnState.toString()); + } - bool isInProgress(WithLock) const { - return _state == kInProgress; - } + friend StringBuilder& operator<<(StringBuilder& s, TransactionState txnState) { + return (s << txnState.toString()); + } - bool isPrepared(WithLock) const { - return _state == kPrepared; - } + /** + * State in this struct may be read by methods of Observer or Participant, and may be written by + * methods of Participant when they acquire the lock on the opCtx's Client. Access this inside + * Observer and Participant using the private o() method for reading and (Participant only) the + * o(WithLock) method for writing. + */ + struct ObservableState { + // Holds transaction resources between network operations. + boost::optional<TxnResources> txnResourceStash; - bool isCommittingWithoutPrepare(WithLock) const { - return _state == kCommittingWithoutPrepare; - } + // Maintains the transaction state and the transition table for legal state transitions. + TransactionState txnState; - bool isCommittingWithPrepare(WithLock) const { - return _state == kCommittingWithPrepare; - } + // Tracks the last seen txn number for the session and is always >= to the transaction + // number in the last written txn record. When it is > than that in the last written txn + // record, this means a new transaction has begun on the session, but it hasn't yet + // performed any writes. + TxnNumber activeTxnNumber{kUninitializedTxnNumber}; - bool isCommitted(WithLock) const { - return _state == kCommitted; - } + // Caches what is known to be the last optime written for the active transaction. + repl::OpTime lastWriteOpTime; - bool isAborted(WithLock) const { - return _state == kAbortedWithoutPrepare || _state == kAbortedWithPrepare; - } + // Set when a snapshot read / transaction begins. Alleviates cache pressure by limiting how + // long a snapshot will remain open and available. Checked in combination with _txnState to + // determine whether the transaction should be aborted. This is unset until a transaction + // begins on the session, and then reset only when new transactions begin. + boost::optional<Date_t> transactionExpireDate; - std::string toString() const { - return toString(_state); - } + // Track the prepareOpTime, the OpTime of the 'prepare' oplog entry for a transaction. + repl::OpTime prepareOpTime; - static std::string toString(StateFlag state); + // Tracks and updates transaction metrics upon the appropriate transaction event. + TransactionMetricsObserver transactionMetricsObserver; + } _o; - private: - static bool _isLegalTransition(StateFlag oldState, StateFlag newState); + /** + * State in this struct may be read and written by methods of the Participant, only. It may + * access the struct via the private p() accessor. No further locking is required in methods + * of the Participant. + */ + struct PrivateState { + // Only set if the server is shutting down and it has been ensured that no new requests will + // be accepted. Ensures that any transaction resources will not be stashed from the + // operation context onto the transaction participant when the session is checked-in so that + // locks can automatically get freed. + bool inShutdown = false; - StateFlag _state = kNone; - }; + // Holds oplog data for operations which have been applied in the current multi-document + // transaction. + std::vector<repl::ReplOperation> transactionOperations; - friend std::ostream& operator<<(std::ostream& s, TransactionState txnState) { - return (s << txnState.toString()); - } + // Total size in bytes of all operations within the _transactionOperations vector. + size_t transactionOperationBytes = 0; - friend StringBuilder& operator<<(StringBuilder& s, TransactionState txnState) { - return (s << txnState.toString()); - } + // The autocommit setting of this transaction. Should always be false for multi-statement + // transaction. Currently only needed for diagnostics reporting. + boost::optional<bool> autoCommit; - // Shortcut to obtain the id of the session under which this participant runs - const LogicalSessionId& _sessionId() const; + // The OpTime a speculative transaction is reading from and also the earliest opTime it + // should wait for write concern for on commit. + repl::OpTime speculativeTransactionReadOpTime; - // Shortcut to obtain the currently checked-out operation context under this participant runs - OperationContext* _opCtx() const; + // Contains uncommitted multi-key path info entries which were modified under this + // transaction so they can be applied to subsequent opreations before the transaction + // commits + std::vector<MultikeyPathInfo> multikeyPathInfo; - /** - * Performing any checks based on the in-memory state of the TransactionParticipant requires - * that the object is fully in sync with its on-disk representation in the transactions table. - * This method checks that. The object can be out of sync with the on-disk representation either - * when it was just created, or after invalidate() was called (which typically happens after a - * direct write to the transactions table). - */ - void _checkValid(WithLock) const; - - // Checks that the specified transaction number is the same as the activeTxnNumber. Effectively - // a check that the caller operates on the transaction it thinks it is operating on. - void _checkIsActiveTransaction(WithLock, TxnNumber txnNumber) const; - - boost::optional<repl::OpTime> _checkStatementExecuted(StmtId stmtId) const; - - UpdateRequest _makeUpdateRequest(const repl::OpTime& newLastWriteOpTime, - Date_t newLastWriteDate, - boost::optional<DurableTxnStateEnum> newState) const; - - void _registerUpdateCacheOnCommit(std::vector<StmtId> stmtIdsWritten, - const repl::OpTime& lastStmtIdWriteTs); - - // Called for speculative transactions to fix the optime of the snapshot to read from. - void _setSpeculativeTransactionOpTime(WithLock, - OperationContext* opCtx, - SpeculativeTransactionOpTime opTimeChoice); - - - // Like _setSpeculativeTransactionOpTime, but caller chooses timestamp of snapshot explicitly. - // It is up to the caller to ensure that Timestamp is greater than or equal to the all-committed - // optime before calling this method (e.g. by calling ReplCoordinator::waitForOpTimeForRead). - void _setSpeculativeTransactionReadTimestamp(WithLock, - OperationContext* opCtx, - Timestamp timestamp); - - // Finishes committing the multi-document transaction after the storage-transaction has been - // committed, the oplog entry has been inserted into the oplog, and the transactions table has - // been updated. - void _finishCommitTransaction(WithLock lk, OperationContext* opCtx); - - // Commits the storage-transaction on the OperationContext. - // - // This should be called *without* the mutex being locked. - void _commitStorageTransaction(OperationContext* opCtx); - - // Stash transaction resources. - void _stashActiveTransaction(WithLock, OperationContext* opCtx); - - // Abort the transaction if it's in one of the expected states and clean up the transaction - // states associated with the opCtx. - void _abortActiveTransaction(stdx::unique_lock<stdx::mutex> lock, - OperationContext* opCtx, - TransactionState::StateSet expectedStates); - - // Releases stashed transaction resources to abort the transaction on the session. - void _abortTransactionOnSession(WithLock); - - // Clean up the transaction resources unstashed on operation context. - void _cleanUpTxnResourceOnOpCtx(WithLock wl, - OperationContext* opCtx, - TerminationCause terminationCause); - - // Checks if the current transaction number of this transaction still matches with the - // parent session as well as the transaction number of the current operation context. - void _checkIsActiveTransaction(WithLock, - const TxnNumber& requestTxnNumber, - bool checkAbort) const; - - // Checks if the command can be run on this transaction based on the state of the transaction. - void _checkIsCommandValidWithTxnState(WithLock, - const TxnNumber& requestTxnNumber, - const std::string& cmdName); - - // Logs the transaction information if it has run slower than the global parameter slowMS. The - // transaction must be committed or aborted when this function is called. - void _logSlowTransaction(WithLock wl, - const SingleThreadedLockStats* lockStats, - TerminationCause terminationCause, - repl::ReadConcernArgs readConcernArgs); - - // This method returns a string with information about a slow transaction. The format of the - // logging string produced should match the format used for slow operation logging. A - // transaction must be completed (committed or aborted) and a valid LockStats reference must be - // passed in order for this method to be called. - std::string _transactionInfoForLog(const SingleThreadedLockStats* lockStats, - TerminationCause terminationCause, - repl::ReadConcernArgs readConcernArgs) const; - - // Reports transaction stats for both active and inactive transactions using the provided - // builder. The lock may be either a lock on _mutex or a lock on _metricsMutex. - void _reportTransactionStats(WithLock wl, - BSONObjBuilder* builder, - repl::ReadConcernArgs readConcernArgs) const; - - // Bumps up the transaction number of this transaction and perform the necessary cleanup. - void _setNewTxnNumber(WithLock wl, const TxnNumber& txnNumber); - - // Attempt to begin or retry a retryable write at the given transaction number. - void _beginOrContinueRetryableWrite(WithLock wl, TxnNumber txnNumber); - - // Attempt to begin a new multi document transaction at the given transaction number. - void _beginMultiDocumentTransaction(WithLock wl, TxnNumber txnNumber); - - // Attempt to continue an in-progress multi document transaction at the given transaction - // number. - void _continueMultiDocumentTransaction(WithLock wl, TxnNumber txnNumber); - - // Helper that invalidates the session state and activeTxnNumber. Also resets the single - // transaction stats because the session is no longer valid. - void _invalidate(WithLock); - - // Helper that resets the retryable writes state. - void _resetRetryableWriteState(WithLock); - - // Helper that resets the transactional state. This is used when aborting a transaction, - // invalidating a transaction, or starting a new transaction. - void _resetTransactionState(WithLock wl, TransactionState::StateFlag state); - - // Protects the member variables below. - mutable stdx::mutex _mutex; - - // Holds transaction resources between network operations. - boost::optional<TxnResources> _txnResourceStash; - - // Maintains the transaction state and the transition table for legal state transitions. - TransactionState _txnState; - - // Holds oplog data for operations which have been applied in the current multi-document - // transaction. - std::vector<repl::ReplOperation> _transactionOperations; - - // Total size in bytes of all operations within the _transactionOperations vector. - size_t _transactionOperationBytes = 0; - - // Tracks the last seen txn number for the session and is always >= to the transaction number in - // the last written txn record. When it is > than that in the last written txn record, this - // means a new transaction has begun on the session, but it hasn't yet performed any writes. - TxnNumber _activeTxnNumber{kUninitializedTxnNumber}; - - // Caches what is known to be the last optime written for the active transaction. - repl::OpTime _lastWriteOpTime; - - // Set when a snapshot read / transaction begins. Alleviates cache pressure by limiting how long - // a snapshot will remain open and available. Checked in combination with _txnState to determine - // whether the transaction should be aborted. - // This is unset until a transaction begins on the session, and then reset only when new - // transactions begin. - boost::optional<Date_t> _transactionExpireDate; - - // The autoCommit setting of this transaction. Should always be false for multi-statement - // transaction. Currently only needed for diagnostics reporting. - boost::optional<bool> _autoCommit; - - // Track the prepareOpTime, the OpTime of the 'prepare' oplog entry for a transaction. - repl::OpTime _prepareOpTime; - - // The OpTime a speculative transaction is reading from and also the earliest opTime it - // should wait for write concern for on commit. - repl::OpTime _speculativeTransactionReadOpTime; - - // Contains uncommitted multi-key path info entries which were modified under this transaction - // so they can be applied to subsequent opreations before the transaction commits - std::vector<MultikeyPathInfo> _multikeyPathInfo; - - // Tracks the OpTime of the first oplog entry written by this TransactionParticipant. - boost::optional<repl::OpTime> _oldestOplogEntryOpTime; - - // Tracks the OpTime of the abort/commit oplog entry associated with this transaction. - boost::optional<repl::OpTime> _finishOpTime; - - // Protects _transactionMetricsObserver. The concurrency rules are that const methods on - // _transactionMetricsObserver may be called under either _mutex or _metricsMutex, but for - // non-const methods, both mutexes must be held, with _mutex being taken before _metricsMutex. - // No other locks, particularly including the Client lock, may be taken while holding - // _metricsMutex. - mutable stdx::mutex _metricsMutex; - - // Tracks and updates transaction metrics upon the appropriate transaction event. - TransactionMetricsObserver _transactionMetricsObserver; + // Tracks the OpTime of the first oplog entry written by this TransactionParticipant. + boost::optional<repl::OpTime> oldestOplogEntryOpTime; - // Only set if the server is shutting down and it has been ensured that no new requests will be - // accepted. Ensures that any transaction resources will not be stashed from the operation - // context onto the transaction participant when the session is checked-in so that locks can - // automatically get freed. - bool _inShutdown{false}; + // Tracks the OpTime of the abort/commit oplog entry associated with this transaction. + boost::optional<repl::OpTime> finishOpTime; - // - // Retryable writes state - // + // + // Retryable writes state + // - // Specifies whether the session information needs to be refreshed from storage - bool _isValid{false}; + // Specifies whether the session information needs to be refreshed from storage + bool isValid{false}; - // Set to true if incomplete history is detected. For example, when the oplog to a write was - // truncated because it was too old. - bool _hasIncompleteHistory{false}; + // Set to true if incomplete history is detected. For example, when the oplog to a write was + // truncated because it was too old. + bool hasIncompleteHistory{false}; - // For the active txn, tracks which statement ids have been committed and at which oplog - // opTime. Used for fast retryability check and retrieving the previous write's data without - // having to scan through the oplog. - CommittedStatementTimestampMap _activeTxnCommittedStatements; + // For the active txn, tracks which statement ids have been committed and at which oplog + // opTime. Used for fast retryability check and retrieving the previous write's data without + // having to scan through the oplog. + CommittedStatementTimestampMap activeTxnCommittedStatements; + } _p; }; } // namespace mongo diff --git a/src/mongo/db/transaction_participant_retryable_writes_test.cpp b/src/mongo/db/transaction_participant_retryable_writes_test.cpp index 92ea9a26393..5271df46282 100644 --- a/src/mongo/db/transaction_participant_retryable_writes_test.cpp +++ b/src/mongo/db/transaction_participant_retryable_writes_test.cpp @@ -206,8 +206,8 @@ protected: repl::OpTime prevOpTime, boost::optional<DurableTxnStateEnum> txnState) { const auto session = OperationContextSession::get(opCtx()); - const auto txnParticipant = TransactionParticipant::get(session); - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); const auto uuid = UUID::gen(); @@ -215,7 +215,7 @@ protected: WriteUnitOfWork wuow(opCtx()); const auto opTime = logOp(opCtx(), kNss, uuid, session->getSessionId(), txnNum, stmtId, prevOpTime); - txnParticipant->onWriteOpCompletedOnPrimary( + txnParticipant.onWriteOpCompletedOnPrimary( opCtx(), txnNum, {stmtId}, opTime, Date_t::now(), txnState); wuow.commit(); @@ -245,12 +245,12 @@ protected: ASSERT_EQ(txnState != boost::none, txnRecordObj.hasField(SessionTxnRecord::kStateFieldName)); - const auto txnParticipant = TransactionParticipant::get(session); - ASSERT_EQ(opTime, txnParticipant->getLastWriteOpTime()); + auto txnParticipant = TransactionParticipant::get(opCtx()); + ASSERT_EQ(opTime, txnParticipant.getLastWriteOpTime()); - txnParticipant->invalidate(); - txnParticipant->refreshFromStorageIfNeeded(); - ASSERT_EQ(opTime, txnParticipant->getLastWriteOpTime()); + txnParticipant.invalidate(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); + ASSERT_EQ(opTime, txnParticipant.getLastWriteOpTime()); } private: @@ -259,12 +259,12 @@ private: TEST_F(TransactionParticipantRetryableWritesTest, SessionEntryNotWrittenOnBegin) { const auto& sessionId = *opCtx()->getLogicalSessionId(); - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); const TxnNumber txnNum = 20; - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); - ASSERT(txnParticipant->getLastWriteOpTime().isNull()); + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); + ASSERT(txnParticipant.getLastWriteOpTime().isNull()); DBDirectClient client(opCtx()); auto cursor = client.query(NamespaceString::kSessionTransactionsTableNamespace, @@ -274,12 +274,12 @@ TEST_F(TransactionParticipantRetryableWritesTest, SessionEntryNotWrittenOnBegin) } TEST_F(TransactionParticipantRetryableWritesTest, SessionEntryWrittenAtFirstWrite) { - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); const auto& sessionId = *opCtx()->getLogicalSessionId(); const TxnNumber txnNum = 21; - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); const auto opTime = writeTxnRecord(txnNum, 0, {}, boost::none); @@ -296,13 +296,13 @@ TEST_F(TransactionParticipantRetryableWritesTest, SessionEntryWrittenAtFirstWrit ASSERT_EQ(txnNum, txnRecord.getTxnNum()); ASSERT_EQ(opTime, txnRecord.getLastWriteOpTime()); ASSERT(!txnRecord.getState()); - ASSERT_EQ(opTime, txnParticipant->getLastWriteOpTime()); + ASSERT_EQ(opTime, txnParticipant.getLastWriteOpTime()); } TEST_F(TransactionParticipantRetryableWritesTest, StartingNewerTransactionUpdatesThePersistedSession) { - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); const auto& sessionId = *opCtx()->getLogicalSessionId(); @@ -322,16 +322,16 @@ TEST_F(TransactionParticipantRetryableWritesTest, ASSERT_EQ(200, txnRecord.getTxnNum()); ASSERT_EQ(secondOpTime, txnRecord.getLastWriteOpTime()); ASSERT(!txnRecord.getState()); - ASSERT_EQ(secondOpTime, txnParticipant->getLastWriteOpTime()); + ASSERT_EQ(secondOpTime, txnParticipant.getLastWriteOpTime()); - txnParticipant->invalidate(); - txnParticipant->refreshFromStorageIfNeeded(); - ASSERT_EQ(secondOpTime, txnParticipant->getLastWriteOpTime()); + txnParticipant.invalidate(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); + ASSERT_EQ(secondOpTime, txnParticipant.getLastWriteOpTime()); } TEST_F(TransactionParticipantRetryableWritesTest, TransactionTableUpdatesReplaceEntireDocument) { - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); const auto firstOpTime = writeTxnRecord(100, 0, {}, boost::none); assertTxnRecord(100, 0, firstOpTime, boost::none); @@ -344,21 +344,22 @@ TEST_F(TransactionParticipantRetryableWritesTest, TransactionTableUpdatesReplace } TEST_F(TransactionParticipantRetryableWritesTest, StartingOldTxnShouldAssert) { - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); const TxnNumber txnNum = 20; - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); - ASSERT_THROWS_CODE(txnParticipant->beginOrContinue(txnNum - 1, boost::none, boost::none), - AssertionException, - ErrorCodes::TransactionTooOld); - ASSERT(txnParticipant->getLastWriteOpTime().isNull()); + ASSERT_THROWS_CODE( + txnParticipant.beginOrContinue(opCtx(), txnNum - 1, boost::none, boost::none), + AssertionException, + ErrorCodes::TransactionTooOld); + ASSERT(txnParticipant.getLastWriteOpTime().isNull()); } TEST_F(TransactionParticipantRetryableWritesTest, SessionTransactionsCollectionNotDefaultCreated) { - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); const auto& sessionId = *opCtx()->getLogicalSessionId(); @@ -369,65 +370,65 @@ TEST_F(TransactionParticipantRetryableWritesTest, SessionTransactionsCollectionN ASSERT(client.runCommand(nss.db().toString(), BSON("drop" << nss.coll()), dropResult)); const TxnNumber txnNum = 21; - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); AutoGetCollection autoColl(opCtx(), kNss, MODE_IX); WriteUnitOfWork wuow(opCtx()); const auto uuid = UUID::gen(); const auto opTime = logOp(opCtx(), kNss, uuid, sessionId, txnNum, 0); - ASSERT_THROWS(txnParticipant->onWriteOpCompletedOnPrimary( + ASSERT_THROWS(txnParticipant.onWriteOpCompletedOnPrimary( opCtx(), txnNum, {0}, opTime, Date_t::now(), boost::none), AssertionException); } TEST_F(TransactionParticipantRetryableWritesTest, CheckStatementExecuted) { - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); const TxnNumber txnNum = 100; - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); - ASSERT(!txnParticipant->checkStatementExecuted(1000)); - ASSERT(!txnParticipant->checkStatementExecutedNoOplogEntryFetch(1000)); + ASSERT(!txnParticipant.checkStatementExecuted(opCtx(), 1000)); + ASSERT(!txnParticipant.checkStatementExecutedNoOplogEntryFetch(1000)); const auto firstOpTime = writeTxnRecord(txnNum, 1000, {}, boost::none); - ASSERT(txnParticipant->checkStatementExecuted(1000)); - ASSERT(txnParticipant->checkStatementExecutedNoOplogEntryFetch(1000)); + ASSERT(txnParticipant.checkStatementExecuted(opCtx(), 1000)); + ASSERT(txnParticipant.checkStatementExecutedNoOplogEntryFetch(1000)); - ASSERT(!txnParticipant->checkStatementExecuted(2000)); - ASSERT(!txnParticipant->checkStatementExecutedNoOplogEntryFetch(2000)); + ASSERT(!txnParticipant.checkStatementExecuted(opCtx(), 2000)); + ASSERT(!txnParticipant.checkStatementExecutedNoOplogEntryFetch(2000)); writeTxnRecord(txnNum, 2000, firstOpTime, boost::none); - ASSERT(txnParticipant->checkStatementExecuted(2000)); - ASSERT(txnParticipant->checkStatementExecutedNoOplogEntryFetch(2000)); + ASSERT(txnParticipant.checkStatementExecuted(opCtx(), 2000)); + ASSERT(txnParticipant.checkStatementExecutedNoOplogEntryFetch(2000)); // Invalidate the session and ensure the statements still check out - txnParticipant->invalidate(); - txnParticipant->refreshFromStorageIfNeeded(); + txnParticipant.invalidate(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); - ASSERT(txnParticipant->checkStatementExecuted(1000)); - ASSERT(txnParticipant->checkStatementExecuted(2000)); + ASSERT(txnParticipant.checkStatementExecuted(opCtx(), 1000)); + ASSERT(txnParticipant.checkStatementExecuted(opCtx(), 2000)); - ASSERT(txnParticipant->checkStatementExecutedNoOplogEntryFetch(1000)); - ASSERT(txnParticipant->checkStatementExecutedNoOplogEntryFetch(2000)); + ASSERT(txnParticipant.checkStatementExecutedNoOplogEntryFetch(1000)); + ASSERT(txnParticipant.checkStatementExecutedNoOplogEntryFetch(2000)); } DEATH_TEST_F(TransactionParticipantRetryableWritesTest, CheckStatementExecutedForInvalidatedTransactionInvariants, - "Invariant failure _isValid") { - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->invalidate(); - txnParticipant->checkStatementExecuted(0); + "Invariant failure p().isValid") { + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.invalidate(opCtx()); + txnParticipant.checkStatementExecuted(opCtx(), 0); } DEATH_TEST_F(TransactionParticipantRetryableWritesTest, WriteOpCompletedOnPrimaryForOldTransactionInvariants, - "Invariant failure txnNumber == _activeTxnNumber") { - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + "Invariant failure txnNumber == o().activeTxnNumber") { + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); const auto& sessionId = *opCtx()->getLogicalSessionId(); const TxnNumber txnNum = 100; - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); const auto uuid = UUID::gen(); @@ -435,7 +436,7 @@ DEATH_TEST_F(TransactionParticipantRetryableWritesTest, AutoGetCollection autoColl(opCtx(), kNss, MODE_IX); WriteUnitOfWork wuow(opCtx()); const auto opTime = logOp(opCtx(), kNss, uuid, sessionId, txnNum, 0); - txnParticipant->onWriteOpCompletedOnPrimary( + txnParticipant.onWriteOpCompletedOnPrimary( opCtx(), txnNum, {0}, opTime, Date_t::now(), boost::none); wuow.commit(); } @@ -444,27 +445,27 @@ DEATH_TEST_F(TransactionParticipantRetryableWritesTest, AutoGetCollection autoColl(opCtx(), kNss, MODE_IX); WriteUnitOfWork wuow(opCtx()); const auto opTime = logOp(opCtx(), kNss, uuid, sessionId, txnNum - 1, 0); - txnParticipant->onWriteOpCompletedOnPrimary( + txnParticipant.onWriteOpCompletedOnPrimary( opCtx(), txnNum - 1, {0}, opTime, Date_t::now(), boost::none); } } DEATH_TEST_F(TransactionParticipantRetryableWritesTest, WriteOpCompletedOnPrimaryForInvalidatedTransactionInvariants, - "Invariant failure txnNumber == _activeTxnNumber") { - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + "Invariant failure txnNumber == o().activeTxnNumber") { + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); const TxnNumber txnNum = 100; - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); AutoGetCollection autoColl(opCtx(), kNss, MODE_IX); WriteUnitOfWork wuow(opCtx()); const auto uuid = UUID::gen(); const auto opTime = logOp(opCtx(), kNss, uuid, *opCtx()->getLogicalSessionId(), txnNum, 0); - txnParticipant->invalidate(); - txnParticipant->onWriteOpCompletedOnPrimary( + txnParticipant.invalidate(opCtx()); + txnParticipant.onWriteOpCompletedOnPrimary( opCtx(), txnNum, {0}, opTime, Date_t::now(), boost::none); } @@ -520,20 +521,20 @@ TEST_F(TransactionParticipantRetryableWritesTest, IncompleteHistoryDueToOpLogTru }()); } - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); - ASSERT_THROWS_CODE(txnParticipant->checkStatementExecuted(0), + ASSERT_THROWS_CODE(txnParticipant.checkStatementExecuted(opCtx(), 0), AssertionException, ErrorCodes::IncompleteTransactionHistory); - ASSERT(txnParticipant->checkStatementExecuted(1)); - ASSERT(txnParticipant->checkStatementExecuted(2)); + ASSERT(txnParticipant.checkStatementExecuted(opCtx(), 1)); + ASSERT(txnParticipant.checkStatementExecuted(opCtx(), 2)); - ASSERT_THROWS_CODE(txnParticipant->checkStatementExecutedNoOplogEntryFetch(0), + ASSERT_THROWS_CODE(txnParticipant.checkStatementExecutedNoOplogEntryFetch(0), AssertionException, ErrorCodes::IncompleteTransactionHistory); - ASSERT(txnParticipant->checkStatementExecutedNoOplogEntryFetch(1)); - ASSERT(txnParticipant->checkStatementExecutedNoOplogEntryFetch(2)); + ASSERT(txnParticipant.checkStatementExecutedNoOplogEntryFetch(1)); + ASSERT(txnParticipant.checkStatementExecutedNoOplogEntryFetch(2)); } TEST_F(TransactionParticipantRetryableWritesTest, ErrorOnlyWhenStmtIdBeingCheckedIsNotInCache) { @@ -541,9 +542,9 @@ TEST_F(TransactionParticipantRetryableWritesTest, ErrorOnlyWhenStmtIdBeingChecke const auto sessionId = *opCtx()->getLogicalSessionId(); const TxnNumber txnNum = 2; - const auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->refreshFromStorageIfNeeded(); - txnParticipant->beginOrContinue(txnNum, boost::none, boost::none); + auto txnParticipant = TransactionParticipant::get(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); + txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none); OperationSessionInfo osi; osi.setSessionId(sessionId); @@ -568,7 +569,7 @@ TEST_F(TransactionParticipantRetryableWritesTest, ErrorOnlyWhenStmtIdBeingChecke {}, false /* prepare */, OplogSlot()); - txnParticipant->onWriteOpCompletedOnPrimary( + txnParticipant.onWriteOpCompletedOnPrimary( opCtx(), txnNum, {1}, opTime, wallClockTime, boost::none); wuow.commit(); @@ -598,30 +599,30 @@ TEST_F(TransactionParticipantRetryableWritesTest, ErrorOnlyWhenStmtIdBeingChecke false /* prepare */, OplogSlot()); - txnParticipant->onWriteOpCompletedOnPrimary( + txnParticipant.onWriteOpCompletedOnPrimary( opCtx(), txnNum, {kIncompleteHistoryStmtId}, opTime, wallClockTime, boost::none); wuow.commit(); } { - auto oplog = txnParticipant->checkStatementExecuted(1); + auto oplog = txnParticipant.checkStatementExecuted(opCtx(), 1); ASSERT_TRUE(oplog); ASSERT_EQ(firstOpTime, oplog->getOpTime()); } - ASSERT_THROWS(txnParticipant->checkStatementExecuted(2), AssertionException); + ASSERT_THROWS(txnParticipant.checkStatementExecuted(opCtx(), 2), AssertionException); // Should have the same behavior after loading state from storage. - txnParticipant->invalidate(); - txnParticipant->refreshFromStorageIfNeeded(); + txnParticipant.invalidate(opCtx()); + txnParticipant.refreshFromStorageIfNeeded(opCtx()); { - auto oplog = txnParticipant->checkStatementExecuted(1); + auto oplog = txnParticipant.checkStatementExecuted(opCtx(), 1); ASSERT_TRUE(oplog); ASSERT_EQ(firstOpTime, oplog->getOpTime()); } - ASSERT_THROWS(txnParticipant->checkStatementExecuted(2), AssertionException); + ASSERT_THROWS(txnParticipant.checkStatementExecuted(opCtx(), 2), AssertionException); } } // namespace diff --git a/src/mongo/db/transaction_participant_test.cpp b/src/mongo/db/transaction_participant_test.cpp index d13a172d746..75e6ddafbad 100644 --- a/src/mongo/db/transaction_participant_test.cpp +++ b/src/mongo/db/transaction_participant_test.cpp @@ -234,24 +234,10 @@ protected: } void runFunctionFromDifferentOpCtx(std::function<void(OperationContext*)> func) { - // Stash the original client. - auto originalClient = Client::releaseCurrent(); - // Create a new client (e.g. for migration) and opCtx. - auto service = opCtx()->getServiceContext(); - auto newClientOwned = service->makeClient("newClient"); - auto newClient = newClientOwned.get(); - Client::setCurrent(std::move(newClientOwned)); - auto newOpCtx = newClient->makeOperationContext(); - - ON_BLOCK_EXIT([&] { - // Restore the original client. - newOpCtx.reset(); - Client::releaseCurrent(); - Client::setCurrent(std::move(originalClient)); - }); - - // Run the function on bahalf of another operation context. + auto newClientOwned = getServiceContext()->makeClient("newClient"); + AlternativeClientRegion acr(newClientOwned); + auto newOpCtx = cc().makeOperationContext(); func(newOpCtx.get()); } @@ -260,7 +246,7 @@ protected: opCtx()->lockState()->setShouldConflictWithSecondaryBatchApplication(false); auto opCtxSession = std::make_unique<MongoDOperationContextSession>(opCtx()); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), false, startNewTxn); + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, startNewTxn); return opCtxSession; } @@ -276,10 +262,10 @@ TEST_F(TxnParticipantTest, TransactionThrowsLockTimeoutIfLockIsUnavailable) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); { Lock::DBLock dbXLock(opCtx(), dbName, MODE_X); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); auto clientWithDatabaseXLock = Client::releaseCurrent(); @@ -305,8 +291,8 @@ TEST_F(TxnParticipantTest, TransactionThrowsLockTimeoutIfLockIsUnavailable) { MongoDOperationContextSession newOpCtxSession(newOpCtx.get()); auto newTxnParticipant = TransactionParticipant::get(newOpCtx.get()); - newTxnParticipant->beginOrContinue(newTxnNum, false, true); - newTxnParticipant->unstashTransactionResources(newOpCtx.get(), "insert"); + newTxnParticipant.beginOrContinue(newOpCtx.get(), newTxnNum, false, true); + newTxnParticipant.unstashTransactionResources(newOpCtx.get(), "insert"); Date_t t1 = Date_t::now(); ASSERT_THROWS_CODE(Lock::DBLock(newOpCtx.get(), dbName, MODE_X), @@ -342,14 +328,14 @@ TEST_F(TxnParticipantTest, StashAndUnstashResources) { // Perform initial unstash which sets up a WriteUnitOfWork. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "find"); + txnParticipant.unstashTransactionResources(opCtx(), "find"); ASSERT_EQUALS(originalLocker, opCtx()->lockState()); ASSERT_EQUALS(originalRecoveryUnit, opCtx()->recoveryUnit()); ASSERT(opCtx()->getWriteUnitOfWork()); ASSERT(opCtx()->lockState()->isLocked()); // Stash resources. The original Locker and RecoveryUnit now belong to the stash. - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); ASSERT_NOT_EQUALS(originalLocker, opCtx()->lockState()); ASSERT_NOT_EQUALS(originalRecoveryUnit, opCtx()->recoveryUnit()); ASSERT(!opCtx()->getWriteUnitOfWork()); @@ -359,25 +345,26 @@ TEST_F(TxnParticipantTest, StashAndUnstashResources) { // Unstash the stashed resources. This restores the original Locker and RecoveryUnit to the // OperationContext. - txnParticipant->unstashTransactionResources(opCtx(), "find"); + txnParticipant.unstashTransactionResources(opCtx(), "find"); ASSERT_EQUALS(originalLocker, opCtx()->lockState()); ASSERT_EQUALS(originalRecoveryUnit, opCtx()->recoveryUnit()); ASSERT(opCtx()->getWriteUnitOfWork()); // Commit the transaction. This allows us to release locks. - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); } TEST_F(TxnParticipantTest, CannotSpecifyStartTransactionOnInProgressTxn) { // Must specify startTransaction=true and autocommit=false to start a transaction. auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - ASSERT_TRUE(txnParticipant->inMultiDocumentTransaction()); + ASSERT_TRUE(txnParticipant.inMultiDocumentTransaction()); // Cannot try to start a transaction that already started. - ASSERT_THROWS_CODE(txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), false, true), - AssertionException, - ErrorCodes::ConflictingOperationInProgress); + ASSERT_THROWS_CODE( + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, true), + AssertionException, + ErrorCodes::ConflictingOperationInProgress); } TEST_F(TxnParticipantTest, AutocommitRequiredOnEveryTxnOp) { @@ -385,19 +372,19 @@ TEST_F(TxnParticipantTest, AutocommitRequiredOnEveryTxnOp) { auto txnParticipant = TransactionParticipant::get(opCtx()); // We must have stashed transaction resources to do a second operation on the transaction. - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); auto txnNum = *opCtx()->getTxnNumber(); // Omitting 'autocommit' after the first statement of a transaction should throw an error. - ASSERT_THROWS_CODE(txnParticipant->beginOrContinue(txnNum, boost::none, boost::none), + ASSERT_THROWS_CODE(txnParticipant.beginOrContinue(opCtx(), txnNum, boost::none, boost::none), AssertionException, ErrorCodes::InvalidOptions); // Including autocommit=false should succeed. - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), false, boost::none); + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, boost::none); } DEATH_TEST_F(TxnParticipantTest, AutocommitCannotBeTrue, "invariant") { @@ -405,7 +392,7 @@ DEATH_TEST_F(TxnParticipantTest, AutocommitCannotBeTrue, "invariant") { auto txnParticipant = TransactionParticipant::get(opCtx()); // Passing 'autocommit=true' is not allowed and should crash. - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), true, boost::none); + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), true, boost::none); } DEATH_TEST_F(TxnParticipantTest, StartTransactionCannotBeFalse, "invariant") { @@ -413,7 +400,7 @@ DEATH_TEST_F(TxnParticipantTest, StartTransactionCannotBeFalse, "invariant") { auto txnParticipant = TransactionParticipant::get(opCtx()); // Passing 'startTransaction=false' is not allowed and should crash. - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), false, false); + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, false); } TEST_F(TxnParticipantTest, SameTransactionPreservesStoredStatements) { @@ -421,40 +408,40 @@ TEST_F(TxnParticipantTest, SameTransactionPreservesStoredStatements) { auto txnParticipant = TransactionParticipant::get(opCtx()); // We must have stashed transaction resources to re-open the transaction. - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0)); - txnParticipant->addTransactionOperation(opCtx(), operation); + txnParticipant.addTransactionOperation(opCtx(), operation); ASSERT_BSONOBJ_EQ(operation.toBSON(), - txnParticipant->getTransactionOperationsForTest()[0].toBSON()); + txnParticipant.getTransactionOperationsForTest()[0].toBSON()); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); // Check the transaction operations before re-opening the transaction. ASSERT_BSONOBJ_EQ(operation.toBSON(), - txnParticipant->getTransactionOperationsForTest()[0].toBSON()); + txnParticipant.getTransactionOperationsForTest()[0].toBSON()); // Re-opening the same transaction should have no effect. - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), false, boost::none); + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, boost::none); ASSERT_BSONOBJ_EQ(operation.toBSON(), - txnParticipant->getTransactionOperationsForTest()[0].toBSON()); + txnParticipant.getTransactionOperationsForTest()[0].toBSON()); } TEST_F(TxnParticipantTest, AbortClearsStoredStatements) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0)); - txnParticipant->addTransactionOperation(opCtx(), operation); + txnParticipant.addTransactionOperation(opCtx(), operation); ASSERT_BSONOBJ_EQ(operation.toBSON(), - txnParticipant->getTransactionOperationsForTest()[0].toBSON()); + txnParticipant.getTransactionOperationsForTest()[0].toBSON()); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); - txnParticipant->abortArbitraryTransaction(); - ASSERT_TRUE(txnParticipant->getTransactionOperationsForTest().empty()); - ASSERT_TRUE(txnParticipant->transactionIsAborted()); + txnParticipant.stashTransactionResources(opCtx()); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); + ASSERT_TRUE(txnParticipant.getTransactionOperationsForTest().empty()); + ASSERT_TRUE(txnParticipant.transactionIsAborted()); } // This test makes sure the commit machinery works even when no operations are done on the @@ -462,14 +449,14 @@ TEST_F(TxnParticipantTest, AbortClearsStoredStatements) { TEST_F(TxnParticipantTest, EmptyTransactionCommit) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->commitUnpreparedTransaction(opCtx()); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); - ASSERT_TRUE(txnParticipant->transactionIsCommitted()); + ASSERT_TRUE(txnParticipant.transactionIsCommitted()); } // This test makes sure the commit machinery works even when no operations are done on the @@ -477,45 +464,45 @@ TEST_F(TxnParticipantTest, EmptyTransactionCommit) { TEST_F(TxnParticipantTest, EmptyPreparedTransactionCommit) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTS = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); - txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.commitPreparedTransaction(opCtx(), commitTS, {}); + txnParticipant.stashTransactionResources(opCtx()); - ASSERT_TRUE(txnParticipant->transactionIsCommitted()); + ASSERT_TRUE(txnParticipant.transactionIsCommitted()); } TEST_F(TxnParticipantTest, PrepareSucceedsWithNestedLocks) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); { Lock::GlobalLock lk1(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); Lock::GlobalLock lk2(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTS = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); - txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.commitPreparedTransaction(opCtx(), commitTS, {}); + txnParticipant.stashTransactionResources(opCtx()); - ASSERT_TRUE(txnParticipant->transactionIsCommitted()); + ASSERT_TRUE(txnParticipant.transactionIsCommitted()); } TEST_F(TxnParticipantTest, CommitTransactionSetsCommitTimestampOnPreparedTransaction) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTS = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); auto originalFn = _opObserver->onTransactionCommitFn; @@ -531,13 +518,13 @@ TEST_F(TxnParticipantTest, CommitTransactionSetsCommitTimestampOnPreparedTransac ASSERT(statements.empty()); }; - txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTS, {}); // The recovery unit is reset on commit. ASSERT(opCtx()->recoveryUnit()->getCommitTimestamp().isNull()); - txnParticipant->stashTransactionResources(opCtx()); - ASSERT_TRUE(txnParticipant->transactionIsCommitted()); + txnParticipant.stashTransactionResources(opCtx()); + ASSERT_TRUE(txnParticipant.transactionIsCommitted()); ASSERT(opCtx()->recoveryUnit()->getCommitTimestamp().isNull()); } @@ -546,11 +533,11 @@ TEST_F(TxnParticipantTest, CommitTransactionWithCommitTimestampFailsOnUnprepared auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); - ASSERT_THROWS_CODE(txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}), + ASSERT_THROWS_CODE(txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}), AssertionException, ErrorCodes::InvalidOptions); } @@ -570,16 +557,16 @@ TEST_F(TxnParticipantTest, CommitTransactionDoesNotSetCommitTimestampOnUnprepare }; auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); ASSERT(opCtx()->recoveryUnit()->getCommitTimestamp().isNull()); - txnParticipant->stashTransactionResources(opCtx()); - ASSERT_TRUE(txnParticipant->transactionIsCommitted()); + txnParticipant.stashTransactionResources(opCtx()); + ASSERT_TRUE(txnParticipant.transactionIsCommitted()); ASSERT(opCtx()->recoveryUnit()->getCommitTimestamp().isNull()); } @@ -587,12 +574,12 @@ TEST_F(TxnParticipantTest, CommitTransactionWithoutCommitTimestampFailsOnPrepare auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); - txnParticipant->prepareTransaction(opCtx(), {}); - ASSERT_THROWS_CODE(txnParticipant->commitUnpreparedTransaction(opCtx()), + txnParticipant.prepareTransaction(opCtx(), {}); + ASSERT_THROWS_CODE(txnParticipant.commitUnpreparedTransaction(opCtx()), AssertionException, ErrorCodes::InvalidOptions); } @@ -601,12 +588,12 @@ TEST_F(TxnParticipantTest, CommitTransactionWithNullCommitTimestampFailsOnPrepar auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); - txnParticipant->prepareTransaction(opCtx(), {}); - ASSERT_THROWS_CODE(txnParticipant->commitPreparedTransaction(opCtx(), Timestamp(), {}), + txnParticipant.prepareTransaction(opCtx(), {}); + ASSERT_THROWS_CODE(txnParticipant.commitPreparedTransaction(opCtx(), Timestamp(), {}), AssertionException, ErrorCodes::InvalidOptions); } @@ -616,12 +603,12 @@ TEST_F(TxnParticipantTest, auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); - auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); - ASSERT_THROWS_CODE(txnParticipant->commitPreparedTransaction( + auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); + ASSERT_THROWS_CODE(txnParticipant.commitPreparedTransaction( opCtx(), Timestamp(prepareTimestamp.getSecs() - 1, 1), {}), AssertionException, ErrorCodes::InvalidOptions); @@ -632,12 +619,12 @@ TEST_F(TxnParticipantTest, auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); - auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); - ASSERT_THROWS_CODE(txnParticipant->commitPreparedTransaction(opCtx(), prepareTimestamp, {}), + auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); + ASSERT_THROWS_CODE(txnParticipant.commitPreparedTransaction(opCtx(), prepareTimestamp, {}), AssertionException, ErrorCodes::InvalidOptions); } @@ -647,13 +634,13 @@ TEST_F(TxnParticipantTest, TEST_F(TxnParticipantTest, EmptyTransactionAbort) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); - txnParticipant->abortArbitraryTransaction(); - ASSERT_TRUE(txnParticipant->transactionIsAborted()); + txnParticipant.stashTransactionResources(opCtx()); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); + ASSERT_TRUE(txnParticipant.transactionIsAborted()); } // This test makes sure the abort machinery works even when no operations are done on the @@ -661,174 +648,20 @@ TEST_F(TxnParticipantTest, EmptyTransactionAbort) { TEST_F(TxnParticipantTest, EmptyPreparedTransactionAbort) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->prepareTransaction(opCtx(), {}); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT_TRUE(txnParticipant->transactionIsAborted()); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfUnstashAndAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - - // An unstash after an abort should uassert. - ASSERT_THROWS_CODE(txnParticipant->unstashTransactionResources(opCtx(), "find"), - AssertionException, - ErrorCodes::NoSuchTransaction); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfStashAndAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "find"); - - // The transaction may be aborted without checking out the txnParticipant-> - txnParticipant->abortArbitraryTransaction(); - - // A stash after an abort should be a noop. - txnParticipant->stashTransactionResources(opCtx()); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfAddTransactionOperationAndAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - - // An addTransactionOperation() after an abort should uassert. - auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0)); - ASSERT_THROWS_CODE(txnParticipant->addTransactionOperation(opCtx(), operation), - AssertionException, - ErrorCodes::NoSuchTransaction); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfRetrieveCompletedTransactionOperationsAndAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - - // A retrieveCompletedTransactionOperations() after an abort should uassert. - ASSERT_THROWS_CODE(txnParticipant->retrieveCompletedTransactionOperations(opCtx()), - AssertionException, - ErrorCodes::NoSuchTransaction); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfClearOperationsInMemory) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - - // An clearOperationsInMemory() after an abort should uassert. - ASSERT_THROWS_CODE(txnParticipant->clearOperationsInMemory(opCtx()), - AssertionException, - ErrorCodes::NoSuchTransaction); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfCommitUnpreparedTransactionAndAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - - // A commitUnpreparedTransaction() after an abort should uassert. - ASSERT_THROWS_CODE(txnParticipant->commitUnpreparedTransaction(opCtx()), - AssertionException, - ErrorCodes::NoSuchTransaction); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfCommitPreparedTransactionAndAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - auto prepareTS = txnParticipant->prepareTransaction(opCtx(), {}); - auto commitTS = Timestamp(prepareTS.getSecs(), prepareTS.getInc() + 1); - - txnParticipant->abortArbitraryTransaction(); - - // A commitPreparedTransaction() after an abort should succeed since the abort should fail. - txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}); - - ASSERT(_opObserver->transactionCommitted); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); - ASSERT(txnParticipant->transactionIsCommitted()); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfActiveUnpreparedAbortAndArbitraryAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - - txnParticipant->unstashTransactionResources(opCtx(), "insert"); - ASSERT(txnParticipant->inMultiDocumentTransaction()); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - - // The operation throws for some reason and aborts implicitly. - // Abort active transaction after it's been aborted by KillSession is a no-op. - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); - ASSERT(opCtx()->getWriteUnitOfWork() == nullptr); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfActivePreparedAbortAndArbitraryAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - - txnParticipant->unstashTransactionResources(opCtx(), "insert"); - ASSERT(txnParticipant->inMultiDocumentTransaction()); - txnParticipant->prepareTransaction(opCtx(), {}); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - - // The operation throws for some reason and aborts implicitly. - // Abort active transaction after it's been aborted by KillSession is a no-op. - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); - ASSERT(opCtx()->getWriteUnitOfWork() == nullptr); -} - -TEST_F(TxnParticipantTest, ConcurrencyOfPrepareTransactionAndAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - ASSERT(txnParticipant->transactionIsAborted()); - - // A prepareTransaction() after an abort should uassert. - ASSERT_THROWS_CODE(txnParticipant->prepareTransaction(opCtx(), {}), - AssertionException, - ErrorCodes::NoSuchTransaction); - ASSERT_FALSE(_opObserver->transactionPrepared); - ASSERT(txnParticipant->transactionIsAborted()); + txnParticipant.prepareTransaction(opCtx(), {}); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT_TRUE(txnParticipant.transactionIsAborted()); } TEST_F(TxnParticipantTest, KillSessionsDuringPrepareDoesNotAbortTransaction) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); auto ruPrepareTimestamp = Timestamp(); auto originalFn = _opObserver->onTransactionPrepareFn; @@ -839,93 +672,41 @@ TEST_F(TxnParticipantTest, KillSessionsDuringPrepareDoesNotAbortTransaction) { ASSERT_FALSE(ruPrepareTimestamp.isNull()); // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); }; // Check that prepareTimestamp gets set. - auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); ASSERT_EQ(ruPrepareTimestamp, prepareTimestamp); // Check that the oldest prepareTimestamp is the one we just set. auto prepareOpTime = ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime(); ASSERT_EQ(prepareOpTime->getTimestamp(), prepareTimestamp); ASSERT(_opObserver->transactionPrepared); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); -} - -DEATH_TEST_F(TxnParticipantTest, AbortDuringPrepareIsFatal, "Invariant") { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - - auto originalFn = _opObserver->onTransactionPrepareFn; - _opObserver->onTransactionPrepareFn = [&]() { - originalFn(); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); - }; - - txnParticipant->prepareTransaction(opCtx(), {}); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); } TEST_F(TxnParticipantTest, ThrowDuringOnTransactionPrepareAbortsTransaction) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); _opObserver->onTransactionPrepareThrowsException = true; - ASSERT_THROWS_CODE(txnParticipant->prepareTransaction(opCtx(), {}), + ASSERT_THROWS_CODE(txnParticipant.prepareTransaction(opCtx(), {}), AssertionException, ErrorCodes::OperationFailed); ASSERT_FALSE(_opObserver->transactionPrepared); - ASSERT(txnParticipant->transactionIsAborted()); -} - -TEST_F(TxnParticipantTest, KillSessionsDuringPreparedCommitDoesNotAbortTransaction) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); - const auto commitTS = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); - - auto originalFn = _opObserver->onTransactionCommitFn; - _opObserver->onTransactionCommitFn = [&](boost::optional<OplogSlot> commitOplogEntryOpTime, - boost::optional<Timestamp> commitTimestamp, - std::vector<repl::ReplOperation>& statements) { - originalFn(commitOplogEntryOpTime, commitTimestamp, statements); - ASSERT(commitOplogEntryOpTime); - ASSERT(commitTimestamp); - - ASSERT_GT(*commitTimestamp, prepareTimestamp); - - ASSERT(statements.empty()); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); - }; - - txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}); - - // The recovery unit is reset on commit. - ASSERT(opCtx()->recoveryUnit()->getCommitTimestamp().isNull()); - - ASSERT(_opObserver->transactionCommitted); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); - ASSERT(txnParticipant->transactionIsCommitted()); + ASSERT(txnParticipant.transactionIsAborted()); } TEST_F(TxnParticipantTest, StepDownAfterPrepareDoesNotBlock) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.prepareTransaction(opCtx(), {}); // Test that we can acquire the RSTL in mode X, and then immediately release it so the test can // complete successfully. @@ -937,17 +718,17 @@ TEST_F(TxnParticipantTest, StepDownAfterPrepareDoesNotBlock) { }; runFunctionFromDifferentOpCtx(func); - txnParticipant->abortActiveTransaction(opCtx()); + txnParticipant.abortActiveTransaction(opCtx()); ASSERT(_opObserver->transactionAborted); - ASSERT(txnParticipant->transactionIsAborted()); + ASSERT(txnParticipant.transactionIsAborted()); } TEST_F(TxnParticipantTest, StepDownAfterPrepareDoesNotBlockThenCommit) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTS = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); // Test that we can acquire the RSTL in mode X, and then immediately release it so the test can @@ -960,118 +741,82 @@ TEST_F(TxnParticipantTest, StepDownAfterPrepareDoesNotBlockThenCommit) { }; runFunctionFromDifferentOpCtx(func); - txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTS, {}); ASSERT(_opObserver->transactionCommitted); - ASSERT(txnParticipant->transactionIsCommitted()); + ASSERT(txnParticipant.transactionIsCommitted()); } TEST_F(TxnParticipantTest, StepDownDuringAbortSucceeds) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); ASSERT_OK(repl::ReplicationCoordinator::get(opCtx())->setFollowerMode( repl::MemberState::RS_SECONDARY)); - txnParticipant->abortActiveTransaction(opCtx()); + txnParticipant.abortActiveTransaction(opCtx()); ASSERT(_opObserver->transactionAborted); - ASSERT(txnParticipant->transactionIsAborted()); + ASSERT(txnParticipant.transactionIsAborted()); } TEST_F(TxnParticipantTest, StepDownDuringPreparedAbortFails) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.prepareTransaction(opCtx(), {}); ASSERT_OK(repl::ReplicationCoordinator::get(opCtx())->setFollowerMode( repl::MemberState::RS_SECONDARY)); ASSERT_THROWS_CODE( - txnParticipant->abortActiveTransaction(opCtx()), AssertionException, ErrorCodes::NotMaster); + txnParticipant.abortActiveTransaction(opCtx()), AssertionException, ErrorCodes::NotMaster); } TEST_F(TxnParticipantTest, StepDownDuringPreparedCommitFails) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTS = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); ASSERT_OK(repl::ReplicationCoordinator::get(opCtx())->setFollowerMode( repl::MemberState::RS_SECONDARY)); - ASSERT_THROWS_CODE(txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}), + ASSERT_THROWS_CODE(txnParticipant.commitPreparedTransaction(opCtx(), commitTS, {}), AssertionException, ErrorCodes::NotMaster); } -TEST_F(TxnParticipantTest, ArbitraryAbortDuringPreparedCommitDoesNotAbortTransaction) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); - const auto commitTS = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); - - auto originalFn = _opObserver->onTransactionCommitFn; - _opObserver->onTransactionCommitFn = [&](boost::optional<OplogSlot> commitOplogEntryOpTime, - boost::optional<Timestamp> commitTimestamp, - std::vector<repl::ReplOperation>& statements) { - originalFn(commitOplogEntryOpTime, commitTimestamp, statements); - ASSERT(commitOplogEntryOpTime); - ASSERT(commitTimestamp); - - ASSERT_GT(*commitTimestamp, prepareTimestamp); - - ASSERT(statements.empty()); - - // The transaction may be aborted without checking out the txnParticipant. - auto func = [&](OperationContext* opCtx) { txnParticipant->abortArbitraryTransaction(); }; - runFunctionFromDifferentOpCtx(func); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); - }; - - txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}); - - // The recovery unit is reset on commit. - ASSERT(opCtx()->recoveryUnit()->getCommitTimestamp().isNull()); - - ASSERT(_opObserver->transactionCommitted); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); - ASSERT(txnParticipant->transactionIsCommitted()); -} - DEATH_TEST_F(TxnParticipantTest, ThrowDuringPreparedOnTransactionCommitIsFatal, "Caught exception during commit") { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); _opObserver->onTransactionCommitThrowsException = true; - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTS = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); - txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTS, {}); } TEST_F(TxnParticipantTest, ThrowDuringUnpreparedCommitLetsTheAbortAtEntryPointToCleanUp) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); _opObserver->onTransactionCommitThrowsException = true; - ASSERT_THROWS_CODE(txnParticipant->commitUnpreparedTransaction(opCtx()), + ASSERT_THROWS_CODE(txnParticipant.commitUnpreparedTransaction(opCtx()), AssertionException, ErrorCodes::OperationFailed); ASSERT_FALSE(_opObserver->transactionCommitted); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); - ASSERT_FALSE(txnParticipant->transactionIsCommitted()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); + ASSERT_FALSE(txnParticipant.transactionIsCommitted()); // Simulate the abort at entry point. - txnParticipant->abortActiveUnpreparedOrStashPreparedTransaction(opCtx()); - ASSERT_TRUE(txnParticipant->transactionIsAborted()); + txnParticipant.abortActiveUnpreparedOrStashPreparedTransaction(opCtx()); + ASSERT_TRUE(txnParticipant.transactionIsAborted()); } TEST_F(TxnParticipantTest, ContinuingATransactionWithNoResourcesAborts) { @@ -1083,7 +828,7 @@ TEST_F(TxnParticipantTest, ContinuingATransactionWithNoResourcesAborts) { auto txnParticipant = TransactionParticipant::get(opCtx()); ASSERT_THROWS_CODE( - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), false, boost::none), + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, boost::none), AssertionException, ErrorCodes::NoSuchTransaction); } @@ -1092,7 +837,7 @@ TEST_F(TxnParticipantTest, KillSessionsDoesNotAbortPreparedTransactions) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto ruPrepareTimestamp = Timestamp(); auto originalFn = _opObserver->onTransactionPrepareFn; @@ -1103,23 +848,23 @@ TEST_F(TxnParticipantTest, KillSessionsDoesNotAbortPreparedTransactions) { }; // Check that prepareTimestamp gets set. - auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); ASSERT_EQ(ruPrepareTimestamp, prepareTimestamp); // Check that the oldest prepareTimestamp is the one we just set. auto prepareOpTime = ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime(); ASSERT_EQ(prepareOpTime->getTimestamp(), prepareTimestamp); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); - txnParticipant->abortArbitraryTransaction(); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); ASSERT(_opObserver->transactionPrepared); } -TEST_F(TxnParticipantTest, TransactionTimeoutDoesNotAbortPreparedTransactions) { +TEST_F(TxnParticipantTest, CannotAbortArbitraryPreparedTransactions) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto ruPrepareTimestamp = Timestamp(); auto originalFn = _opObserver->onTransactionPrepareFn; @@ -1130,16 +875,15 @@ TEST_F(TxnParticipantTest, TransactionTimeoutDoesNotAbortPreparedTransactions) { }; // Check that prepareTimestamp gets set. - auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); ASSERT_EQ(ruPrepareTimestamp, prepareTimestamp); // Check that the oldest prepareTimestamp is the one we just set. auto prepareOpTime = ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime(); ASSERT_EQ(prepareOpTime->getTimestamp(), prepareTimestamp); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); - ASSERT(!txnParticipant->expired()); - txnParticipant->abortArbitraryTransaction(); - ASSERT(!txnParticipant->transactionIsAborted()); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); + ASSERT(!txnParticipant.transactionIsAborted()); ASSERT(_opObserver->transactionPrepared); } @@ -1147,7 +891,7 @@ TEST_F(TxnParticipantTest, CannotStartNewTransactionWhilePreparedTransactionInPr auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto ruPrepareTimestamp = Timestamp(); auto originalFn = _opObserver->onTransactionPrepareFn; @@ -1159,37 +903,36 @@ TEST_F(TxnParticipantTest, CannotStartNewTransactionWhilePreparedTransactionInPr }; // Check that prepareTimestamp gets set. - auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); ASSERT_EQ(ruPrepareTimestamp, prepareTimestamp); // Check that the oldest prepareTimestamp is the one we just set. auto prepareOpTime = ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime(); ASSERT_EQ(prepareOpTime->getTimestamp(), prepareTimestamp); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); OperationContextSession::checkIn(opCtx()); { + auto guard = makeGuard([&]() { OperationContextSession::checkOut(opCtx()); }); // Try to start a new transaction while there is already a prepared transaction on the // session. This should fail with a PreparedTransactionInProgress error. - auto func = [ + runFunctionFromDifferentOpCtx([ lsid = *opCtx()->getLogicalSessionId(), txnNumberToStart = *opCtx()->getTxnNumber() + 1 ](OperationContext * newOpCtx) { newOpCtx->setLogicalSessionId(lsid); newOpCtx->setTxnNumber(txnNumberToStart); + MongoDOperationContextSession ocs(newOpCtx); auto txnParticipant = TransactionParticipant::get(newOpCtx); - - ASSERT_THROWS_CODE(txnParticipant->beginOrContinue(txnNumberToStart, false, true), - AssertionException, - ErrorCodes::PreparedTransactionInProgress); - }; - - runFunctionFromDifferentOpCtx(func); + ASSERT_THROWS_CODE( + txnParticipant.beginOrContinue(newOpCtx, txnNumberToStart, false, true), + AssertionException, + ErrorCodes::PreparedTransactionInProgress); + }); } - OperationContextSession::checkOut(opCtx()); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); ASSERT(_opObserver->transactionPrepared); } @@ -1197,17 +940,17 @@ TEST_F(TxnParticipantTest, CannotInsertInPreparedTransaction) { auto outerScopedSession = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0)); - txnParticipant->addTransactionOperation(opCtx(), operation); + txnParticipant.addTransactionOperation(opCtx(), operation); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.prepareTransaction(opCtx(), {}); - ASSERT_THROWS_CODE(txnParticipant->unstashTransactionResources(opCtx(), "insert"), + ASSERT_THROWS_CODE(txnParticipant.unstashTransactionResources(opCtx(), "insert"), AssertionException, ErrorCodes::PreparedTransactionInProgress); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); ASSERT(_opObserver->transactionPrepared); } @@ -1215,50 +958,44 @@ TEST_F(TxnParticipantTest, ImplictAbortDoesNotAbortPreparedTransaction) { auto outerScopedSession = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0)); - txnParticipant->addTransactionOperation(opCtx(), operation); + txnParticipant.addTransactionOperation(opCtx(), operation); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.prepareTransaction(opCtx(), {}); // The next command throws an exception and wants to abort the transaction. // This is a no-op. - txnParticipant->abortActiveUnpreparedOrStashPreparedTransaction(opCtx()); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + txnParticipant.abortActiveUnpreparedOrStashPreparedTransaction(opCtx()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); ASSERT_TRUE(_opObserver->transactionPrepared); } -DEATH_TEST_F(TxnParticipantTest, AbortIsIllegalDuringCommittingPreparedTransaction, "invariant") { +DEATH_TEST_F(TxnParticipantTest, + AbortIsIllegalDuringCommittingPreparedTransaction, + "isCommittingWithPrepare") { auto outerScopedSession = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0)); - txnParticipant->addTransactionOperation(opCtx(), operation); - auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.addTransactionOperation(opCtx(), operation); + auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); auto commitTS = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); // Check that the oldest prepareTimestamp is the one we just set. auto prepareOpTime = ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime(); ASSERT_EQ(prepareOpTime->getTimestamp(), prepareTimestamp); - auto sessionId = *opCtx()->getLogicalSessionId(); - auto txnNum = *opCtx()->getTxnNumber(); _opObserver->onTransactionCommitFn = [&](boost::optional<OplogSlot> commitOplogEntryOpTime, boost::optional<Timestamp> commitTimestamp, std::vector<repl::ReplOperation>& statements) { - // This should never happen. - auto func = [&](OperationContext* opCtx) { - opCtx->setLogicalSessionId(sessionId); - opCtx->setTxnNumber(txnNum); - // Hit an invariant. This should never happen. - txnParticipant->abortActiveTransaction(opCtx); - }; - runFunctionFromDifferentOpCtx(func); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + // Hit an invariant. This should never happen. + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); }; - txnParticipant->commitPreparedTransaction(opCtx(), commitTS, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTS, {}); // Check that we removed the prepareTimestamp from the set. ASSERT_FALSE(ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime()); } @@ -1267,7 +1004,7 @@ TEST_F(TxnParticipantTest, CannotContinueNonExistentTransaction) { MongoDOperationContextSession opCtxSession(opCtx()); auto txnParticipant = TransactionParticipant::get(opCtx()); ASSERT_THROWS_CODE( - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), false, boost::none), + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), false, boost::none), AssertionException, ErrorCodes::NoSuchTransaction); } @@ -1277,7 +1014,7 @@ TEST_F(TxnParticipantTest, TransactionTooLargeWhileBuilding) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // Two 6MB operations should succeed; three 6MB operations should fail. constexpr size_t kBigDataSize = 6 * 1024 * 1024; @@ -1286,9 +1023,9 @@ TEST_F(TxnParticipantTest, TransactionTooLargeWhileBuilding) { kNss, kUUID, BSON("_id" << 0 << "data" << BSONBinData(bigData.get(), kBigDataSize, BinDataGeneral))); - txnParticipant->addTransactionOperation(opCtx(), operation); - txnParticipant->addTransactionOperation(opCtx(), operation); - ASSERT_THROWS_CODE(txnParticipant->addTransactionOperation(opCtx(), operation), + txnParticipant.addTransactionOperation(opCtx(), operation); + txnParticipant.addTransactionOperation(opCtx(), operation); + ASSERT_THROWS_CODE(txnParticipant.addTransactionOperation(opCtx(), operation), AssertionException, ErrorCodes::TransactionTooLarge); } @@ -1311,7 +1048,7 @@ TEST_F(TxnParticipantTest, StashInNestedSessionIsANoop) { // Perform initial unstash, which sets up a WriteUnitOfWork. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "find"); + txnParticipant.unstashTransactionResources(opCtx(), "find"); ASSERT_EQUALS(originalLocker, opCtx()->lockState()); ASSERT_EQUALS(originalRecoveryUnit, opCtx()->recoveryUnit()); ASSERT(opCtx()->getWriteUnitOfWork()); @@ -1319,7 +1056,7 @@ TEST_F(TxnParticipantTest, StashInNestedSessionIsANoop) { { // Make it look like we're in a DBDirectClient running a nested operation. DirectClientSetter inDirectClient(opCtx()); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); // The stash was a noop, so the locker, RecoveryUnit, and WriteUnitOfWork on the // OperationContext are unaffected. @@ -1344,12 +1081,12 @@ protected: auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - ASSERT(txnParticipant->inMultiDocumentTransaction()); + ASSERT(txnParticipant.inMultiDocumentTransaction()); - ASSERT_THROWS_CODE( - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), autocommit, startTransaction), - AssertionException, - 50911); + ASSERT_THROWS_CODE(txnParticipant.beginOrContinue( + opCtx(), *opCtx()->getTxnNumber(), autocommit, startTransaction), + AssertionException, + 50911); } void canSpecifyStartTransactionOnAbortedTxn() { @@ -1358,13 +1095,14 @@ protected: auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - ASSERT(txnParticipant->inMultiDocumentTransaction()); + ASSERT(txnParticipant.inMultiDocumentTransaction()); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT(txnParticipant.transactionIsAborted()); - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), autocommit, startTransaction); - ASSERT(txnParticipant->inMultiDocumentTransaction()); + txnParticipant.beginOrContinue( + opCtx(), *opCtx()->getTxnNumber(), autocommit, startTransaction); + ASSERT(txnParticipant.inMultiDocumentTransaction()); } void cannotSpecifyStartTransactionOnCommittedTxn() { @@ -1373,15 +1111,15 @@ protected: auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - ASSERT(txnParticipant->inMultiDocumentTransaction()); + ASSERT(txnParticipant.inMultiDocumentTransaction()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.commitUnpreparedTransaction(opCtx()); - ASSERT_THROWS_CODE( - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), autocommit, startTransaction), - AssertionException, - 50911); + ASSERT_THROWS_CODE(txnParticipant.beginOrContinue( + opCtx(), *opCtx()->getTxnNumber(), autocommit, startTransaction), + AssertionException, + 50911); } void cannotSpecifyStartTransactionOnPreparedTxn() { @@ -1390,32 +1128,32 @@ protected: auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - ASSERT(txnParticipant->inMultiDocumentTransaction()); + ASSERT(txnParticipant.inMultiDocumentTransaction()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0)); - txnParticipant->addTransactionOperation(opCtx(), operation); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.addTransactionOperation(opCtx(), operation); + txnParticipant.prepareTransaction(opCtx(), {}); - ASSERT_THROWS_CODE( - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), autocommit, startTransaction), - AssertionException, - 50911); + ASSERT_THROWS_CODE(txnParticipant.beginOrContinue( + opCtx(), *opCtx()->getTxnNumber(), autocommit, startTransaction), + AssertionException, + 50911); } void cannotSpecifyStartTransactionOnStartedRetryableWrite() { MongoDOperationContextSession opCtxSession(opCtx()); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), boost::none, boost::none); - ASSERT_FALSE(txnParticipant->inMultiDocumentTransaction()); + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), boost::none, boost::none); + ASSERT_FALSE(txnParticipant.inMultiDocumentTransaction()); auto autocommit = false; auto startTransaction = true; - ASSERT_THROWS_CODE( - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), autocommit, startTransaction), - AssertionException, - 50911); + ASSERT_THROWS_CODE(txnParticipant.beginOrContinue( + opCtx(), *opCtx()->getTxnNumber(), autocommit, startTransaction), + AssertionException, + 50911); } void cannotSpecifyStartTransactionOnAbortedPreparedTransaction() { @@ -1424,20 +1162,20 @@ protected: auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - ASSERT(txnParticipant->inMultiDocumentTransaction()); + ASSERT(txnParticipant.inMultiDocumentTransaction()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); - ASSERT(txnParticipant->transactionIsPrepared()); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.prepareTransaction(opCtx(), {}); + ASSERT(txnParticipant.transactionIsPrepared()); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT(txnParticipant.transactionIsAborted()); startTransaction = true; - ASSERT_THROWS_CODE( - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), autocommit, startTransaction), - AssertionException, - 50911); + ASSERT_THROWS_CODE(txnParticipant.beginOrContinue( + opCtx(), *opCtx()->getTxnNumber(), autocommit, startTransaction), + AssertionException, + 50911); } }; @@ -1521,97 +1259,27 @@ TEST_F(ConfigTxnParticipantTest, CannotSpecifyStartTransactionOnAbortedPreparedT cannotSpecifyStartTransactionOnAbortedPreparedTransaction(); } -TEST_F(TxnParticipantTest, KillSessionsDuringUnpreparedAbortSucceeds) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - - auto originalFn = _opObserver->onTransactionAbortFn; - _opObserver->onTransactionAbortFn = [&] { - originalFn(); - - // The transaction may be aborted without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - ASSERT(txnParticipant->transactionIsAborted()); - }; - - txnParticipant->abortActiveTransaction(opCtx()); - - ASSERT(_opObserver->transactionAborted); - ASSERT(txnParticipant->transactionIsAborted()); -} - -TEST_F(TxnParticipantTest, ActiveAbortIsLegalDuringUnpreparedAbort) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - - auto sessionId = *opCtx()->getLogicalSessionId(); - auto txnNumber = *opCtx()->getTxnNumber(); - auto originalFn = _opObserver->onTransactionAbortFn; - _opObserver->onTransactionAbortFn = [&] { - originalFn(); - - auto func = [&](OperationContext* opCtx) { - opCtx->setLogicalSessionId(sessionId); - opCtx->setTxnNumber(txnNumber); - - // Prevent recursion. - _opObserver->onTransactionAbortFn = originalFn; - txnParticipant->abortActiveTransaction(opCtx); - ASSERT(txnParticipant->transactionIsAborted()); - }; - runFunctionFromDifferentOpCtx(func); - }; - - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(_opObserver->transactionAborted); - ASSERT(txnParticipant->transactionIsAborted()); -} - TEST_F(TxnParticipantTest, ThrowDuringUnpreparedOnTransactionAbort) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); _opObserver->onTransactionAbortThrowsException = true; - ASSERT_THROWS_CODE(txnParticipant->abortActiveTransaction(opCtx()), + ASSERT_THROWS_CODE(txnParticipant.abortActiveTransaction(opCtx()), AssertionException, ErrorCodes::OperationFailed); } -TEST_F(TxnParticipantTest, KillSessionsDuringPreparedAbortFails) { - auto sessionCheckout = checkOutSession(); - auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); - - auto originalFn = _opObserver->onTransactionAbortFn; - _opObserver->onTransactionAbortFn = [&] { - originalFn(); - - // KillSessions may attempt to abort without checking out the txnParticipant. - txnParticipant->abortArbitraryTransaction(); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); - ASSERT(txnParticipant->transactionIsPrepared()); - }; - - txnParticipant->abortActiveTransaction(opCtx()); - - ASSERT(_opObserver->transactionAborted); - ASSERT(txnParticipant->transactionIsAborted()); -} - TEST_F(TxnParticipantTest, ThrowDuringPreparedOnTransactionAbortIsFatal) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.prepareTransaction(opCtx(), {}); _opObserver->onTransactionAbortThrowsException = true; - ASSERT_THROWS_CODE(txnParticipant->abortActiveTransaction(opCtx()), + ASSERT_THROWS_CODE(txnParticipant.abortActiveTransaction(opCtx()), AssertionException, ErrorCodes::OperationFailed); } @@ -1628,16 +1296,16 @@ TEST_F(TxnParticipantTest, ReacquireLocksForPreparedTransactionsOnStepUp) { repl::UnreplicatedWritesBlock uwb(opCtx()); ASSERT(!opCtx()->writesAreReplicated()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); // Simulate the locking of an insert. { Lock::DBLock dbLock(opCtx(), "test", MODE_IX); Lock::CollectionLock collLock(opCtx()->lockState(), "test.foo", MODE_IX); } - txnParticipant->prepareTransaction(opCtx(), repl::OpTime({1, 1}, 1)); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.prepareTransaction(opCtx(), repl::OpTime({1, 1}, 1)); + txnParticipant.stashTransactionResources(opCtx()); // Secondary yields locks for prepared transactions. - ASSERT_FALSE(txnParticipant->getTxnResourceStashLockerForTest()->isLocked()); + ASSERT_FALSE(txnParticipant.getTxnResourceStashLockerForTest()->isLocked()); } // Step-up will restore the locks of prepared transactions. @@ -1646,9 +1314,9 @@ TEST_F(TxnParticipantTest, ReacquireLocksForPreparedTransactionsOnStepUp) { { auto sessionCheckout = checkOutSession({}); auto txnParticipant = TransactionParticipant::get(opCtx()); - ASSERT(txnParticipant->getTxnResourceStashLockerForTest()->isLocked()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - txnParticipant->abortActiveTransaction(opCtx()); + ASSERT(txnParticipant.getTxnResourceStashLockerForTest()->isLocked()); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.abortActiveTransaction(opCtx()); } } @@ -1697,8 +1365,8 @@ TEST_F(TransactionsMetricsTest, IncrementPreparedTransaction) { auto txnParticipant = TransactionParticipant::get(opCtx()); unsigned long long beforePrepareCount = ServerTransactionsMetrics::get(opCtx())->getTotalPrepared(); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.prepareTransaction(opCtx(), {}); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalPrepared(), beforePrepareCount + 1U); } @@ -1706,12 +1374,12 @@ TEST_F(TransactionsMetricsTest, IncrementPreparedTransaction) { TEST_F(TransactionsMetricsTest, IncrementTotalCommittedOnCommit) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); unsigned long long beforeCommitCount = ServerTransactionsMetrics::get(opCtx())->getTotalCommitted(); - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); // Assert that the committed counter is incremented by 1. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalCommitted(), beforeCommitCount + 1U); @@ -1720,17 +1388,17 @@ TEST_F(TransactionsMetricsTest, IncrementTotalCommittedOnCommit) { TEST_F(TransactionsMetricsTest, IncrementTotalPreparedThenCommitted) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); unsigned long long beforePreparedThenCommittedCount = ServerTransactionsMetrics::get(opCtx())->getTotalPreparedThenCommitted(); - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}); - ASSERT_TRUE(txnParticipant->transactionIsCommitted()); + ASSERT_TRUE(txnParticipant.transactionIsCommitted()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalPreparedThenCommitted(), beforePreparedThenCommittedCount + 1U); } @@ -1739,12 +1407,12 @@ TEST_F(TransactionsMetricsTest, IncrementTotalPreparedThenCommitted) { TEST_F(TransactionsMetricsTest, IncrementTotalAbortedUponAbort) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); unsigned long long beforeAbortCount = ServerTransactionsMetrics::get(opCtx())->getTotalAborted(); - txnParticipant->abortArbitraryTransaction(); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); // Assert that the aborted counter is incremented by 1. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalAborted(), beforeAbortCount + 1U); @@ -1756,11 +1424,11 @@ TEST_F(TransactionsMetricsTest, IncrementTotalPreparedThenAborted) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.prepareTransaction(opCtx(), {}); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT(txnParticipant.transactionIsAborted()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalPreparedThenAborted(), beforePreparedThenAbortedCount + 1U); } @@ -1771,15 +1439,15 @@ TEST_F(TransactionsMetricsTest, IncrementCurrentPreparedWithCommit) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentPrepared(), beforeCurrentPrepared + 1U); - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}); - ASSERT(txnParticipant->transactionIsCommitted()); + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}); + ASSERT(txnParticipant.transactionIsCommitted()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentPrepared(), beforeCurrentPrepared); } @@ -1789,13 +1457,13 @@ TEST_F(TransactionsMetricsTest, IncrementCurrentPreparedWithAbort) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.prepareTransaction(opCtx(), {}); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentPrepared(), beforeCurrentPrepared + 1U); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT(txnParticipant.transactionIsAborted()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentPrepared(), beforeCurrentPrepared); } @@ -1806,18 +1474,18 @@ TEST_F(TransactionsMetricsTest, TrackTotalOpenTransactionsWithAbort) { // Tests that starting a transaction increments the open transactions counter by 1. auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentOpen(), beforeTransactionStart + 1U); // Tests that stashing the transaction resources does not affect the open transactions counter. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentOpen(), beforeTransactionStart + 1U); // Tests that aborting a transaction decrements the open transactions counter by 1. - txnParticipant->abortArbitraryTransaction(); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentOpen(), beforeTransactionStart); } @@ -1828,20 +1496,20 @@ TEST_F(TransactionsMetricsTest, TrackTotalOpenTransactionsWithCommit) { // Tests that starting a transaction increments the open transactions counter by 1. auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentOpen(), beforeTransactionStart + 1U); // Tests that stashing the transaction resources does not affect the open transactions counter. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentOpen(), beforeTransactionStart + 1U); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // Tests that committing a transaction decrements the open transactions counter by 1. - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentOpen(), beforeTransactionStart); } @@ -1859,7 +1527,7 @@ TEST_F(TransactionsMetricsTest, TrackTotalActiveAndInactiveTransactionsWithCommi // Tests that the first unstash increments the active counter and decrements the inactive // counter. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter + 1U); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactiveCounter); @@ -1867,20 +1535,20 @@ TEST_F(TransactionsMetricsTest, TrackTotalActiveAndInactiveTransactionsWithCommi // Tests that stashing the transaction resources decrements active counter and increments // inactive counter. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactiveCounter + 1U); // Tests that the second unstash increments the active counter and decrements the inactive // counter. - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter + 1U); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactiveCounter); // Tests that committing a transaction decrements the active counter only. - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactiveCounter); } @@ -1899,7 +1567,7 @@ TEST_F(TransactionsMetricsTest, TrackTotalActiveAndInactiveTransactionsWithStash // Tests that the first unstash increments the active counter and decrements the inactive // counter. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter + 1U); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactiveCounter); @@ -1907,13 +1575,13 @@ TEST_F(TransactionsMetricsTest, TrackTotalActiveAndInactiveTransactionsWithStash // Tests that stashing the transaction resources decrements active counter and increments // inactive counter. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactiveCounter + 1U); // Tests that aborting a stashed transaction decrements the inactive counter only. - txnParticipant->abortArbitraryTransaction(); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactiveCounter); } @@ -1932,13 +1600,13 @@ TEST_F(TransactionsMetricsTest, TrackTotalActiveAndInactiveTransactionsWithUnsta // Tests that the first unstash increments the active counter and decrements the inactive // counter. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter + 1U); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactiveCounter); // Tests that aborting a stashed transaction decrements the active counter only. - txnParticipant->abortArbitraryTransaction(); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactiveCounter); } @@ -1956,8 +1624,8 @@ TEST_F(TransactionsMetricsTest, TrackCurrentActiveAndInactivePreparedTransaction // Tests that unstashing a transaction puts it into an active state. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); @@ -1968,21 +1636,21 @@ TEST_F(TransactionsMetricsTest, TrackCurrentActiveAndInactivePreparedTransaction ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalPrepared(), beforePrepareCount + 1U); // Tests that the first stash decrements the active counter and increments the inactive counter. - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActivePreparedCounter); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactivePreparedCounter + 1U); // Tests that unstashing increments the active counter and decrements the inactive counter. - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActivePreparedCounter + 1U); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactivePreparedCounter); // Tests that committing decrements the active counter only. - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActivePreparedCounter); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), @@ -2002,8 +1670,8 @@ TEST_F(TransactionsMetricsTest, auto txnParticipant = TransactionParticipant::get(opCtx()); // Tests that unstashing a transaction increments the active counter only. - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.prepareTransaction(opCtx(), {}); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActivePreparedCounter + 1U); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), @@ -2011,20 +1679,20 @@ TEST_F(TransactionsMetricsTest, // Tests that stashing a prepared transaction decrements the active counter and increments the // inactive counter. - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActivePreparedCounter); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactivePreparedCounter + 1U); // Tests that aborting a stashed prepared transaction decrements the inactive counter only. - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActivePreparedCounter + 1U); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), beforeInactivePreparedCounter); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT(txnParticipant.transactionIsAborted()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActivePreparedCounter); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentInactive(), @@ -2055,10 +1723,10 @@ TEST_F(TransactionsMetricsTest, TransactionErrorsBeforeUnstash) { auto txnParticipant = TransactionParticipant::get(opCtx()); const bool autocommit = false; const boost::optional<bool> startTransaction = boost::none; - ASSERT_THROWS_CODE( - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), autocommit, startTransaction), - AssertionException, - ErrorCodes::NoSuchTransaction); + ASSERT_THROWS_CODE(txnParticipant.beginOrContinue( + opCtx(), *opCtx()->getTxnNumber(), autocommit, startTransaction), + AssertionException, + ErrorCodes::NoSuchTransaction); // The transaction is now aborted. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getCurrentActive(), beforeActiveCounter); @@ -2072,16 +1740,16 @@ TEST_F(TransactionsMetricsTest, SingleTransactionStatsDurationShouldBeSetUponCom auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); // Advance the clock. tickSource->advance(Microseconds(100)); - txnParticipant->commitUnpreparedTransaction(opCtx()); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getDuration( - tickSource, tickSource->getTicks()), + txnParticipant.commitUnpreparedTransaction(opCtx()); + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getDuration(tickSource, + tickSource->getTicks()), Microseconds(100)); } @@ -2090,7 +1758,7 @@ TEST_F(TransactionsMetricsTest, SingleTransactionStatsPreparedDurationShouldBeSe auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); @@ -2098,14 +1766,14 @@ TEST_F(TransactionsMetricsTest, SingleTransactionStatsPreparedDurationShouldBeSe tickSource->advance(Microseconds(10)); // Prepare the transaction and extend the duration in the prepared state. - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); tickSource->advance(Microseconds(100)); - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getPreparedDuration( + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}); + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getPreparedDuration( tickSource, tickSource->getTicks()), Microseconds(100)); } @@ -2115,14 +1783,14 @@ TEST_F(TransactionsMetricsTest, SingleTransactionStatsDurationShouldBeSetUponAbo auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // Advance the clock. tickSource->advance(Microseconds(100)); - txnParticipant->abortArbitraryTransaction(); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getDuration( - tickSource, tickSource->getTicks()), + txnParticipant.abortTransactionIfNotPrepared(opCtx()); + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getDuration(tickSource, + tickSource->getTicks()), Microseconds(100)); } @@ -2131,17 +1799,17 @@ TEST_F(TransactionsMetricsTest, SingleTransactionStatsPreparedDurationShouldBeSe auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); // Advance the clock. tickSource->advance(Microseconds(10)); // Prepare the transaction and extend the duration in the prepared state. - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.prepareTransaction(opCtx(), {}); tickSource->advance(Microseconds(100)); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getPreparedDuration( + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getPreparedDuration( tickSource, tickSource->getTicks()), Microseconds(100)); } @@ -2151,29 +1819,29 @@ TEST_F(TransactionsMetricsTest, SingleTransactionStatsDurationShouldKeepIncreasi auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); tickSource->advance(Microseconds(100)); // The transaction's duration should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getDuration( - tickSource, tickSource->getTicks()), + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getDuration(tickSource, + tickSource->getTicks()), Microseconds(100)); tickSource->advance(Microseconds(100)); // Commit the transaction and check duration. - txnParticipant->commitUnpreparedTransaction(opCtx()); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getDuration( - tickSource, tickSource->getTicks()), + txnParticipant.commitUnpreparedTransaction(opCtx()); + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getDuration(tickSource, + tickSource->getTicks()), Microseconds(200)); // The transaction committed, so the duration shouldn't have increased even if more time passed. tickSource->advance(Microseconds(100)); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getDuration( - tickSource, tickSource->getTicks()), + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getDuration(tickSource, + tickSource->getTicks()), Microseconds(200)); } @@ -2183,34 +1851,34 @@ TEST_F(TransactionsMetricsTest, auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); // Prepare the transaction and extend the duration in the prepared state. - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); tickSource->advance(Microseconds(100)); // The prepared transaction's duration should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getPreparedDuration( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getPreparedDuration( tickSource, tickSource->getTicks()), Microseconds(100)); tickSource->advance(Microseconds(100)); // Commit the prepared transaction and check the prepared duration. - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getPreparedDuration( + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}); + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getPreparedDuration( tickSource, tickSource->getTicks()), Microseconds(200)); // The prepared transaction committed, so the prepared duration shouldn't have increased even if // more time passed. tickSource->advance(Microseconds(100)); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getPreparedDuration( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getPreparedDuration( tickSource, tickSource->getTicks()), Microseconds(200)); } @@ -2220,29 +1888,29 @@ TEST_F(TransactionsMetricsTest, SingleTransactionStatsDurationShouldKeepIncreasi auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); tickSource->advance(Microseconds(100)); // The transaction's duration should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getDuration( - tickSource, tickSource->getTicks()), + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getDuration(tickSource, + tickSource->getTicks()), Microseconds(100)); tickSource->advance(Microseconds(100)); // Abort the transaction and check duration. - txnParticipant->abortArbitraryTransaction(); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getDuration( - tickSource, tickSource->getTicks()), + txnParticipant.abortTransactionIfNotPrepared(opCtx()); + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getDuration(tickSource, + tickSource->getTicks()), Microseconds(200)); // The transaction aborted, so the duration shouldn't have increased even if more time passed. tickSource->advance(Microseconds(100)); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getDuration( - tickSource, tickSource->getTicks()), + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getDuration(tickSource, + tickSource->getTicks()), Microseconds(200)); } @@ -2252,31 +1920,31 @@ TEST_F(TransactionsMetricsTest, auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); // Prepare the transaction and extend the duration in the prepared state. - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.prepareTransaction(opCtx(), {}); tickSource->advance(Microseconds(100)); // The prepared transaction's duration should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getPreparedDuration( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getPreparedDuration( tickSource, tickSource->getTicks()), Microseconds(100)); tickSource->advance(Microseconds(100)); // Abort the prepared transaction and check the prepared duration. - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getPreparedDuration( + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getPreparedDuration( tickSource, tickSource->getTicks()), Microseconds(200)); // The prepared transaction aborted, so the prepared duration shouldn't have increased even if // more time passed. tickSource->advance(Microseconds(100)); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getPreparedDuration( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getPreparedDuration( tickSource, tickSource->getTicks()), Microseconds(200)); } @@ -2288,42 +1956,42 @@ TEST_F(TransactionsMetricsTest, TimeActiveMicrosShouldBeSetUponUnstashAndStash) auto txnParticipant = TransactionParticipant::get(opCtx()); // Time active should be zero. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{0}); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); tickSource->advance(Microseconds(100)); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); // Advance clock during inactive period. tickSource->advance(Microseconds(100)); // Time active should have increased only during active period. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); tickSource->advance(Microseconds(100)); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); // Advance clock during inactive period. tickSource->advance(Microseconds(100)); // Time active should have increased again. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{200}); // Start a new transaction. const auto higherTxnNum = *opCtx()->getTxnNumber() + 1; - txnParticipant->beginOrContinue(higherTxnNum, false, true); + txnParticipant.beginOrContinue(opCtx(), higherTxnNum, false, true); // Time active should be zero for a new transaction. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{0}); } @@ -2335,23 +2003,23 @@ TEST_F(TransactionsMetricsTest, TimeActiveMicrosShouldBeSetUponUnstashAndAbort) auto txnParticipant = TransactionParticipant::get(opCtx()); // Time active should be zero. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{0}); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); tickSource->advance(Microseconds(100)); - txnParticipant->abortArbitraryTransaction(); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); // Time active should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); tickSource->advance(Microseconds(100)); // The transaction is not active after abort, so time active should not have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); } @@ -2363,17 +2031,17 @@ TEST_F(TransactionsMetricsTest, TimeActiveMicrosShouldNotBeSetUponAbortOnly) { auto txnParticipant = TransactionParticipant::get(opCtx()); // Time active should be zero. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{0}); // Advance clock during inactive period. tickSource->advance(Microseconds(100)); - txnParticipant->abortArbitraryTransaction(); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); // Time active should still be zero. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{0}); } @@ -2385,32 +2053,32 @@ TEST_F(TransactionsMetricsTest, TimeActiveMicrosShouldIncreaseUntilStash) { auto txnParticipant = TransactionParticipant::get(opCtx()); // Time active should be zero. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{0}); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); tickSource->advance(Microseconds(100)); // Time active should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds(100)); tickSource->advance(Microseconds(100)); // Time active should have increased again. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds(200)); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); tickSource->advance(Microseconds(100)); // The transaction is no longer active, so time active should have stopped increasing. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds(200)); } @@ -2422,31 +2090,31 @@ TEST_F(TransactionsMetricsTest, TimeActiveMicrosShouldIncreaseUntilCommit) { auto txnParticipant = TransactionParticipant::get(opCtx()); // Time active should be zero. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{0}); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); tickSource->advance(Microseconds(100)); // Time active should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); tickSource->advance(Microseconds(100)); // Time active should have increased again. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds{200}); - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); tickSource->advance(Microseconds(100)); // The transaction is no longer active, so time active should have stopped increasing. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros( tickSource, tickSource->getTicks()), Microseconds(200)); } @@ -2456,37 +2124,36 @@ TEST_F(TransactionsMetricsTest, AdditiveMetricsObjectsShouldBeAddedTogetherUponS auto txnParticipant = TransactionParticipant::get(opCtx()); // Initialize field values for both AdditiveMetrics objects. - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysExamined = + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysExamined = 1; CurOp::get(opCtx())->debug().additiveMetrics.keysExamined = 5; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.docsExamined = + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.docsExamined = 2; CurOp::get(opCtx())->debug().additiveMetrics.docsExamined = 0; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nMatched = 3; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nModified = 1; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nMatched = 3; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nModified = 1; CurOp::get(opCtx())->debug().additiveMetrics.nModified = 1; CurOp::get(opCtx())->debug().additiveMetrics.ninserted = 4; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysInserted = + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysInserted = 1; CurOp::get(opCtx())->debug().additiveMetrics.keysInserted = 1; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysDeleted = - 0; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysDeleted = 0; CurOp::get(opCtx())->debug().additiveMetrics.keysDeleted = 0; - txnParticipant->getSingleTransactionStatsForTest() + txnParticipant.getSingleTransactionStatsForTest() .getOpDebug() ->additiveMetrics.prepareReadConflicts = 5; CurOp::get(opCtx())->debug().additiveMetrics.prepareReadConflicts = 4; auto additiveMetricsToCompare = - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics; additiveMetricsToCompare.add(CurOp::get(opCtx())->debug().additiveMetrics); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); - ASSERT(txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.equals( + ASSERT(txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.equals( additiveMetricsToCompare)); } @@ -2495,40 +2162,39 @@ TEST_F(TransactionsMetricsTest, AdditiveMetricsObjectsShouldBeAddedTogetherUponC auto txnParticipant = TransactionParticipant::get(opCtx()); // Initialize field values for both AdditiveMetrics objects. - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysExamined = + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysExamined = 3; CurOp::get(opCtx())->debug().additiveMetrics.keysExamined = 2; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.docsExamined = + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.docsExamined = 0; CurOp::get(opCtx())->debug().additiveMetrics.docsExamined = 2; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nMatched = 4; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nModified = 5; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nMatched = 4; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nModified = 5; CurOp::get(opCtx())->debug().additiveMetrics.nModified = 1; CurOp::get(opCtx())->debug().additiveMetrics.ninserted = 1; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.ndeleted = 4; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.ndeleted = 4; CurOp::get(opCtx())->debug().additiveMetrics.ndeleted = 0; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysInserted = + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysInserted = 1; CurOp::get(opCtx())->debug().additiveMetrics.keysInserted = 1; - txnParticipant->getSingleTransactionStatsForTest() + txnParticipant.getSingleTransactionStatsForTest() .getOpDebug() ->additiveMetrics.prepareReadConflicts = 0; CurOp::get(opCtx())->debug().additiveMetrics.prepareReadConflicts = 0; - txnParticipant->getSingleTransactionStatsForTest() - .getOpDebug() - ->additiveMetrics.writeConflicts = 6; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.writeConflicts = + 6; CurOp::get(opCtx())->debug().additiveMetrics.writeConflicts = 3; auto additiveMetricsToCompare = - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics; additiveMetricsToCompare.add(CurOp::get(opCtx())->debug().additiveMetrics); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); - ASSERT(txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.equals( + ASSERT(txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.equals( additiveMetricsToCompare)); } @@ -2537,37 +2203,35 @@ TEST_F(TransactionsMetricsTest, AdditiveMetricsObjectsShouldBeAddedTogetherUponA auto txnParticipant = TransactionParticipant::get(opCtx()); // Initialize field values for both AdditiveMetrics objects. - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysExamined = + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysExamined = 2; CurOp::get(opCtx())->debug().additiveMetrics.keysExamined = 4; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.docsExamined = + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.docsExamined = 1; CurOp::get(opCtx())->debug().additiveMetrics.docsExamined = 3; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nMatched = 2; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nModified = 0; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nMatched = 2; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.nModified = 0; CurOp::get(opCtx())->debug().additiveMetrics.nModified = 3; CurOp::get(opCtx())->debug().additiveMetrics.ndeleted = 5; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysInserted = + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysInserted = 1; CurOp::get(opCtx())->debug().additiveMetrics.keysInserted = 1; - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysDeleted = - 6; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.keysDeleted = 6; CurOp::get(opCtx())->debug().additiveMetrics.keysDeleted = 0; - txnParticipant->getSingleTransactionStatsForTest() - .getOpDebug() - ->additiveMetrics.writeConflicts = 3; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.writeConflicts = + 3; CurOp::get(opCtx())->debug().additiveMetrics.writeConflicts = 3; auto additiveMetricsToCompare = - txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics; + txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics; additiveMetricsToCompare.add(CurOp::get(opCtx())->debug().additiveMetrics); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->abortActiveTransaction(opCtx()); + txnParticipant.abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.equals( + ASSERT(txnParticipant.getSingleTransactionStatsForTest().getOpDebug()->additiveMetrics.equals( additiveMetricsToCompare)); } @@ -2579,33 +2243,33 @@ TEST_F(TransactionsMetricsTest, TimeInactiveMicrosShouldBeSetUponUnstashAndStash // Time inactive should have increased. tickSource->advance(Microseconds(100)); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); // Time inactive should have increased again. tickSource->advance(Microseconds(100)); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{200}); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); tickSource->advance(Microseconds(100)); // The transaction is currently active, so time inactive should not have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{200}); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); tickSource->advance(Microseconds(100)); // The transaction is inactive again, so time inactive should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{300}); } @@ -2617,28 +2281,28 @@ TEST_F(TransactionsMetricsTest, TimeInactiveMicrosShouldBeSetUponUnstashAndAbort auto txnParticipant = TransactionParticipant::get(opCtx()); // Time inactive should be greater than or equal to zero. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{0}); tickSource->advance(Microseconds(100)); // Time inactive should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); - txnParticipant->abortArbitraryTransaction(); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); tickSource->advance(Microseconds(100)); // The transaction has aborted, so time inactive should not have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); } @@ -2650,26 +2314,26 @@ TEST_F(TransactionsMetricsTest, TimeInactiveMicrosShouldIncreaseUntilCommit) { auto txnParticipant = TransactionParticipant::get(opCtx()); // Time inactive should be greater than or equal to zero. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{0}); tickSource->advance(Microseconds(100)); // Time inactive should have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); tickSource->advance(Microseconds(100)); // The transaction has committed, so time inactive should not have increased. - ASSERT_EQ(txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( + ASSERT_EQ(txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros( tickSource, tickSource->getTicks()), Microseconds{100}); } @@ -2714,12 +2378,12 @@ TEST_F(TransactionsMetricsTest, ReportStashedResources) { // Perform initial unstash which sets up a WriteUnitOfWork. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "find"); + txnParticipant.unstashTransactionResources(opCtx(), "find"); ASSERT(opCtx()->getWriteUnitOfWork()); ASSERT(opCtx()->lockState()->isLocked()); // Prepare the transaction and extend the duration in the prepared state. - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); @@ -2727,11 +2391,11 @@ TEST_F(TransactionsMetricsTest, ReportStashedResources) { tickSource->advance(Microseconds(preparedDuration)); // Stash resources. The original Locker and RecoveryUnit now belong to the stash. - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); ASSERT(!opCtx()->getWriteUnitOfWork()); // Verify that the Session's report of its own stashed state aligns with our expectations. - auto stashedState = txnParticipant->reportStashedState(); + auto stashedState = txnParticipant.reportStashedState(opCtx()); auto transactionDocument = stashedState.getObjectField("transaction"); auto parametersDocument = transactionDocument.getObjectField("parameters"); @@ -2772,14 +2436,14 @@ TEST_F(TransactionsMetricsTest, ReportStashedResources) { // Unstash the stashed resources. This restores the original Locker and RecoveryUnit to the // OperationContext. - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); ASSERT(opCtx()->getWriteUnitOfWork()); // With the resources unstashed, verify that the Session reports an empty stashed state. - ASSERT(txnParticipant->reportStashedState().isEmpty()); + ASSERT(txnParticipant.reportStashedState(opCtx()).isEmpty()); // Commit the transaction. This allows us to release locks. - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}); } TEST_F(TransactionsMetricsTest, ReportUnstashedResources) { @@ -2804,18 +2468,18 @@ TEST_F(TransactionsMetricsTest, ReportUnstashedResources) { // Perform initial unstash which sets up a WriteUnitOfWork. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "find"); + txnParticipant.unstashTransactionResources(opCtx(), "find"); ASSERT(opCtx()->getWriteUnitOfWork()); ASSERT(opCtx()->lockState()->isLocked()); // Prepare transaction and extend duration in the prepared state. - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.prepareTransaction(opCtx(), {}); const long prepareDuration = 10; tickSource->advance(Microseconds(prepareDuration)); // Verify that the Session's report of its own unstashed state aligns with our expectations. BSONObjBuilder unstashedStateBuilder; - txnParticipant->reportUnstashedState(opCtx(), &unstashedStateBuilder); + txnParticipant.reportUnstashedState(opCtx(), &unstashedStateBuilder); auto unstashedState = unstashedStateBuilder.obj(); auto transactionDocument = unstashedState.getObjectField("transaction"); auto parametersDocument = transactionDocument.getObjectField("parameters"); @@ -2842,12 +2506,12 @@ TEST_F(TransactionsMetricsTest, ReportUnstashedResources) { ASSERT_GTE(transactionDocument.getField("timeInactiveMicros").numberLong(), 0); // Stash resources. The original Locker and RecoveryUnit now belong to the stash. - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); ASSERT(!opCtx()->getWriteUnitOfWork()); // With the resources stashed, verify that the Session reports an empty unstashed state. BSONObjBuilder builder; - txnParticipant->reportUnstashedState(opCtx(), &builder); + txnParticipant.reportUnstashedState(opCtx(), &builder); ASSERT(builder.obj().isEmpty()); } @@ -2857,8 +2521,8 @@ TEST_F(TransactionsMetricsTest, ReportUnstashedResourcesForARetryableWrite) { MongoDOperationContextSession opCtxSession(opCtx()); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->beginOrContinue(*opCtx()->getTxnNumber(), boost::none, boost::none); - txnParticipant->unstashTransactionResources(opCtx(), "find"); + txnParticipant.beginOrContinue(opCtx(), *opCtx()->getTxnNumber(), boost::none, boost::none); + txnParticipant.unstashTransactionResources(opCtx(), "find"); // Build a BSONObj containing the details which we expect to see reported when we invoke // reportUnstashedState. For a retryable write, we should only include the txnNumber. @@ -2871,7 +2535,7 @@ TEST_F(TransactionsMetricsTest, ReportUnstashedResourcesForARetryableWrite) { // Verify that the Session's report of its own unstashed state aligns with our expectations. BSONObjBuilder unstashedStateBuilder; - txnParticipant->reportUnstashedState(opCtx(), &unstashedStateBuilder); + txnParticipant.reportUnstashedState(opCtx(), &unstashedStateBuilder); ASSERT_BSONOBJ_EQ(unstashedStateBuilder.obj(), reportBuilder.obj()); } @@ -2904,13 +2568,13 @@ TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponStash) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // The transaction machinery cannot store an empty locker. { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); // LastClientInfo should have been set. - auto lastClientInfo = txnParticipant->getSingleTransactionStatsForTest().getLastClientInfo(); + auto lastClientInfo = txnParticipant.getSingleTransactionStatsForTest().getLastClientInfo(); ASSERT_EQ(lastClientInfo.clientHostAndPort, ""); ASSERT_EQ(lastClientInfo.connectionId, 0); ASSERT_EQ(lastClientInfo.appName, "appName"); @@ -2922,11 +2586,11 @@ TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponStash) { clientMetadataIsMasterState.setClientMetadata(opCtx()->getClient(), std::move(newClientMetadata.getValue())); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); + txnParticipant.stashTransactionResources(opCtx()); // LastClientInfo's clientMetadata should have been updated to the new ClientMetadata object. - lastClientInfo = txnParticipant->getSingleTransactionStatsForTest().getLastClientInfo(); + lastClientInfo = txnParticipant.getSingleTransactionStatsForTest().getLastClientInfo(); ASSERT_EQ(lastClientInfo.appName, "newAppName"); ASSERT_BSONOBJ_EQ(lastClientInfo.clientMetadata, newObj.getField("client").Obj()); } @@ -2941,13 +2605,13 @@ TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponCommit) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); // The transaction machinery cannot store an empty locker. Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); // LastClientInfo should have been set. - auto lastClientInfo = txnParticipant->getSingleTransactionStatsForTest().getLastClientInfo(); + auto lastClientInfo = txnParticipant.getSingleTransactionStatsForTest().getLastClientInfo(); ASSERT_EQ(lastClientInfo.clientHostAndPort, ""); ASSERT_EQ(lastClientInfo.connectionId, 0); ASSERT_EQ(lastClientInfo.appName, "appName"); @@ -2965,11 +2629,11 @@ TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponAbort) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); - txnParticipant->abortActiveTransaction(opCtx()); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); + txnParticipant.abortActiveTransaction(opCtx()); // LastClientInfo should have been set. - auto lastClientInfo = txnParticipant->getSingleTransactionStatsForTest().getLastClientInfo(); + auto lastClientInfo = txnParticipant.getSingleTransactionStatsForTest().getLastClientInfo(); ASSERT_EQ(lastClientInfo.clientHostAndPort, ""); ASSERT_EQ(lastClientInfo.connectionId, 0); ASSERT_EQ(lastClientInfo.appName, "appName"); @@ -3023,33 +2687,33 @@ void buildSingleTransactionStatsString(StringBuilder* sb, const int metricValue) * Builds the time active and time inactive info string. */ void buildTimeActiveInactiveString(StringBuilder* sb, - TransactionParticipant* txnParticipant, + TransactionParticipant::Participant txnParticipant, TickSource* tickSource, TickSource::Tick curTick) { // Add time active micros to string. (*sb) << " timeActiveMicros:" << durationCount<Microseconds>( - txnParticipant->getSingleTransactionStatsForTest().getTimeActiveMicros(tickSource, - curTick)); + txnParticipant.getSingleTransactionStatsForTest().getTimeActiveMicros(tickSource, + curTick)); // Add time inactive micros to string. (*sb) << " timeInactiveMicros:" << durationCount<Microseconds>( - txnParticipant->getSingleTransactionStatsForTest().getTimeInactiveMicros( - tickSource, curTick)); + txnParticipant.getSingleTransactionStatsForTest().getTimeInactiveMicros(tickSource, + curTick)); } /* * Builds the total prepared duration info string. */ void buildPreparedDurationString(StringBuilder* sb, - TransactionParticipant* txnParticipant, + TransactionParticipant::Participant txnParticipant, TickSource* tickSource, TickSource::Tick curTick) { (*sb) << " totalPreparedDurationMicros:" << durationCount<Microseconds>( - txnParticipant->getSingleTransactionStatsForTest().getPreparedDuration(tickSource, - curTick)); + txnParticipant.getSingleTransactionStatsForTest().getPreparedDuration(tickSource, + curTick)); } /* @@ -3057,7 +2721,7 @@ void buildPreparedDurationString(StringBuilder* sb, */ std::string buildTransactionInfoString( OperationContext* opCtx, - TransactionParticipant* txnParticipant, + TransactionParticipant::Participant txnParticipant, std::string terminationCause, const LogicalSessionId sessionId, const TxnNumber txnNum, @@ -3088,7 +2752,7 @@ std::string buildTransactionInfoString( StringBuilder readTimestampInfo; readTimestampInfo << " readTimestamp:" - << txnParticipant->getSpeculativeTransactionReadOpTimeForTest().getTimestamp().toString() + << txnParticipant.getSpeculativeTransactionReadOpTimeForTest().getTimestamp().toString() << ","; StringBuilder singleTransactionStatsInfo; @@ -3129,24 +2793,24 @@ std::string buildTransactionInfoString( expectedTransactionInfo << totalPreparedDuration.str(); expectedTransactionInfo << " prepareOpTime:" << (prepareOpTime ? prepareOpTime->toString() - : txnParticipant->getPrepareOpTime().toString()); + : txnParticipant.getPrepareOpTime().toString()); } - if (txnParticipant->getOldestOplogEntryOpTimeForTest()) { + if (txnParticipant.getOldestOplogEntryOpTimeForTest()) { ASSERT(!oldestOplogEntryOpTime); expectedTransactionInfo << " oldestOplogEntryOpTime:" - << txnParticipant->getOldestOplogEntryOpTimeForTest()->toString(); + << txnParticipant.getOldestOplogEntryOpTimeForTest()->toString(); } if (oldestOplogEntryOpTime) { - ASSERT(!txnParticipant->getOldestOplogEntryOpTimeForTest()); + ASSERT(!txnParticipant.getOldestOplogEntryOpTimeForTest()); expectedTransactionInfo << " oldestOplogEntryOpTime:" << oldestOplogEntryOpTime->toString(); } - if (txnParticipant->getFinishOpTimeForTest()) { + if (txnParticipant.getFinishOpTimeForTest()) { expectedTransactionInfo << " finishOpTime:" - << txnParticipant->getFinishOpTimeForTest()->toString(); + << txnParticipant.getFinishOpTimeForTest()->toString(); } expectedTransactionInfo << ", " << duration_cast<Milliseconds>( - txnParticipant->getSingleTransactionStatsForTest().getDuration( + txnParticipant.getSingleTransactionStatsForTest().getDuration( tickSource, tickSource->getTicks())); return expectedTransactionInfo.str(); } @@ -3169,13 +2833,13 @@ TEST_F(TransactionsMetricsTest, TestTransactionInfoForLogAfterCommit) { auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.commitUnpreparedTransaction(opCtx()); const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); ASSERT(lockerInfo); - std::string testTransactionInfo = - txnParticipant->getTransactionInfoForLogForTest(&lockerInfo->stats, true, readConcernArgs); + std::string testTransactionInfo = txnParticipant.getTransactionInfoForLogForTest( + opCtx(), &lockerInfo->stats, true, readConcernArgs); std::string expectedTransactionInfo = buildTransactionInfoString(opCtx(), @@ -3209,19 +2873,19 @@ TEST_F(TransactionsMetricsTest, TestPreparedTransactionInfoForLogAfterCommit) { // Prepare the transaction and extend the duration in the prepared state. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); tickSource->advance(Microseconds(10)); - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}); const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); ASSERT(lockerInfo); - std::string testTransactionInfo = - txnParticipant->getTransactionInfoForLogForTest(&lockerInfo->stats, true, readConcernArgs); + std::string testTransactionInfo = txnParticipant.getTransactionInfoForLogForTest( + opCtx(), &lockerInfo->stats, true, readConcernArgs); std::string expectedTransactionInfo = buildTransactionInfoString(opCtx(), @@ -3252,14 +2916,14 @@ TEST_F(TransactionsMetricsTest, TestTransactionInfoForLogAfterAbort) { auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - txnParticipant->abortActiveTransaction(opCtx()); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.abortActiveTransaction(opCtx()); const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); ASSERT(lockerInfo); - std::string testTransactionInfo = - txnParticipant->getTransactionInfoForLogForTest(&lockerInfo->stats, false, readConcernArgs); + std::string testTransactionInfo = txnParticipant.getTransactionInfoForLogForTest( + opCtx(), &lockerInfo->stats, false, readConcernArgs); std::string expectedTransactionInfo = buildTransactionInfoString(opCtx(), @@ -3293,17 +2957,17 @@ TEST_F(TransactionsMetricsTest, TestPreparedTransactionInfoForLogAfterAbort) { // Prepare the transaction and extend the duration in the prepared state. auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.prepareTransaction(opCtx(), {}); tickSource->advance(Microseconds(10)); - txnParticipant->abortActiveTransaction(opCtx()); + txnParticipant.abortActiveTransaction(opCtx()); const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); ASSERT(lockerInfo); - std::string testTransactionInfo = - txnParticipant->getTransactionInfoForLogForTest(&lockerInfo->stats, false, readConcernArgs); + std::string testTransactionInfo = txnParticipant.getTransactionInfoForLogForTest( + opCtx(), &lockerInfo->stats, false, readConcernArgs); std::string expectedTransactionInfo = buildTransactionInfoString(opCtx(), @@ -3334,10 +2998,10 @@ DEATH_TEST_F(TransactionsMetricsTest, TestTransactionInfoForLogWithNoLockerInfoS const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); ASSERT(lockerInfo); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.commitUnpreparedTransaction(opCtx()); - txnParticipant->getTransactionInfoForLogForTest(nullptr, true, readConcernArgs); + txnParticipant.getTransactionInfoForLogForTest(opCtx(), nullptr, true, readConcernArgs); } TEST_F(TransactionsMetricsTest, LogTransactionInfoAfterSlowCommit) { @@ -3359,19 +3023,20 @@ TEST_F(TransactionsMetricsTest, LogTransactionInfoAfterSlowCommit) { const int metricValue = 1; setupAdditiveMetrics(metricValue, opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); serverGlobalParams.slowMS = 10; tickSource->advance(Microseconds(11 * 1000)); startCapturingLogMessages(); - txnParticipant->commitUnpreparedTransaction(opCtx()); + txnParticipant.commitUnpreparedTransaction(opCtx()); stopCapturingLogMessages(); const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); ASSERT(lockerInfo); std::string expectedTransactionInfo = "transaction " + - txnParticipant->getTransactionInfoForLogForTest(&lockerInfo->stats, true, readConcernArgs); + txnParticipant.getTransactionInfoForLogForTest( + opCtx(), &lockerInfo->stats, true, readConcernArgs); ASSERT_EQUALS(1, countLogLinesContaining(expectedTransactionInfo)); } @@ -3397,19 +3062,20 @@ TEST_F(TransactionsMetricsTest, LogPreparedTransactionInfoAfterSlowCommit) { serverGlobalParams.slowMS = 10; tickSource->advance(Microseconds(11 * 1000)); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); startCapturingLogMessages(); - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}); stopCapturingLogMessages(); const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); ASSERT(lockerInfo); std::string expectedTransactionInfo = "transaction " + - txnParticipant->getTransactionInfoForLogForTest(&lockerInfo->stats, true, readConcernArgs); + txnParticipant.getTransactionInfoForLogForTest( + opCtx(), &lockerInfo->stats, true, readConcernArgs); ASSERT_EQUALS(1, countLogLinesContaining(expectedTransactionInfo)); } @@ -3432,13 +3098,13 @@ TEST_F(TransactionsMetricsTest, LogTransactionInfoAfterSlowAbort) { const int metricValue = 1; setupAdditiveMetrics(metricValue, opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); serverGlobalParams.slowMS = 10; tickSource->advance(Microseconds(11 * 1000)); startCapturingLogMessages(); - txnParticipant->abortActiveTransaction(opCtx()); + txnParticipant.abortActiveTransaction(opCtx()); stopCapturingLogMessages(); const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); @@ -3475,15 +3141,15 @@ TEST_F(TransactionsMetricsTest, LogPreparedTransactionInfoAfterSlowAbort) { const int metricValue = 1; setupAdditiveMetrics(metricValue, opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.prepareTransaction(opCtx(), {}); serverGlobalParams.slowMS = 10; tickSource->advance(Microseconds(11 * 1000)); - auto prepareOpTime = txnParticipant->getPrepareOpTime(); + auto prepareOpTime = txnParticipant.getPrepareOpTime(); startCapturingLogMessages(); - txnParticipant->abortActiveTransaction(opCtx()); + txnParticipant.abortActiveTransaction(opCtx()); stopCapturingLogMessages(); const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); @@ -3522,18 +3188,18 @@ TEST_F(TransactionsMetricsTest, LogTransactionInfoAfterExceptionInPrepare) { const int metricValue = 1; setupAdditiveMetrics(metricValue, opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); serverGlobalParams.slowMS = 10; tickSource->advance(Microseconds(11 * 1000)); _opObserver->onTransactionPrepareThrowsException = true; startCapturingLogMessages(); - ASSERT_THROWS_CODE(txnParticipant->prepareTransaction(opCtx(), {}), + ASSERT_THROWS_CODE(txnParticipant.prepareTransaction(opCtx(), {}), AssertionException, ErrorCodes::OperationFailed); ASSERT_FALSE(_opObserver->transactionPrepared); - ASSERT(txnParticipant->transactionIsAborted()); + ASSERT(txnParticipant.transactionIsAborted()); stopCapturingLogMessages(); const auto lockerInfo = opCtx()->lockState()->getLockerInfo(boost::none); @@ -3569,12 +3235,12 @@ TEST_F(TransactionsMetricsTest, LogTransactionInfoAfterSlowStashedAbort) { const int metricValue = 1; setupAdditiveMetrics(metricValue, opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); { Lock::GlobalLock lk(opCtx(), MODE_IX, Date_t::now(), Lock::InterruptBehavior::kThrow); } - txnParticipant->stashTransactionResources(opCtx()); - const auto txnResourceStashLocker = txnParticipant->getTxnResourceStashLockerForTest(); + txnParticipant.stashTransactionResources(opCtx()); + const auto txnResourceStashLocker = txnParticipant.getTxnResourceStashLockerForTest(); ASSERT(txnResourceStashLocker); const auto lockerInfo = txnResourceStashLocker->getLockerInfo(boost::none); @@ -3582,7 +3248,7 @@ TEST_F(TransactionsMetricsTest, LogTransactionInfoAfterSlowStashedAbort) { tickSource->advance(Microseconds(11 * 1000)); startCapturingLogMessages(); - txnParticipant->abortArbitraryTransaction(); + txnParticipant.abortTransactionIfNotPrepared(opCtx()); stopCapturingLogMessages(); std::string expectedTransactionInfo = "transaction parameters"; @@ -3596,17 +3262,17 @@ TEST_F(TxnParticipantTest, WhenOldestTSRemovedNextOldestBecomesNewOldest) { // Check that there are no Timestamps in the set. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 0U); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - auto firstPrepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + auto firstPrepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); // Check that we added a Timestamp to the set. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 1U); // Check that the oldest prepareTimestamp is equal to firstPrepareTimestamp because there is // only one prepared transaction on this Service. auto prepareOpTime = ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime(); ASSERT_EQ(prepareOpTime->getTimestamp(), firstPrepareTimestamp); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); auto originalClient = Client::releaseCurrent(); /** @@ -3628,19 +3294,19 @@ TEST_F(TxnParticipantTest, WhenOldestTSRemovedNextOldestBecomesNewOldest) { MongoDOperationContextSession newOpCtxSession(newOpCtx.get()); auto newTxnParticipant = TransactionParticipant::get(newOpCtx.get()); - newTxnParticipant->beginOrContinue(newTxnNum, false, true); - newTxnParticipant->unstashTransactionResources(newOpCtx.get(), "prepareTransaction"); + newTxnParticipant.beginOrContinue(newOpCtx.get(), newTxnNum, false, true); + newTxnParticipant.unstashTransactionResources(newOpCtx.get(), "prepareTransaction"); // secondPrepareTimestamp should be greater than firstPreparedTimestamp because this // transaction was prepared after. - secondPrepareTimestamp = newTxnParticipant->prepareTransaction(newOpCtx.get(), {}); + secondPrepareTimestamp = newTxnParticipant.prepareTransaction(newOpCtx.get(), {}); ASSERT_GT(secondPrepareTimestamp, firstPrepareTimestamp); // Check that we added a Timestamp to the set. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 2U); // The oldest prepareTimestamp should still be firstPrepareTimestamp. prepareOpTime = ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime(); ASSERT_EQ(prepareOpTime->getTimestamp(), firstPrepareTimestamp); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); } Client::releaseCurrent(); @@ -3648,9 +3314,9 @@ TEST_F(TxnParticipantTest, WhenOldestTSRemovedNextOldestBecomesNewOldest) { // Switch clients and abort the first transaction. This should cause the oldestActiveTS to be // equal to the secondPrepareTimestamp. - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT(txnParticipant.transactionIsAborted()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 1U); prepareOpTime = ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime(); ASSERT_EQ(prepareOpTime->getTimestamp(), secondPrepareTimestamp); @@ -3663,13 +3329,13 @@ TEST_F(TxnParticipantTest, ReturnNullTimestampIfNoOldestActiveTimestamp) { // Check that there are no Timestamps in the set. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 0U); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.prepareTransaction(opCtx(), {}); // Check that we added a Timestamp to the set. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 1U); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); - txnParticipant->stashTransactionResources(opCtx()); + txnParticipant.stashTransactionResources(opCtx()); auto originalClient = Client::releaseCurrent(); /** @@ -3690,20 +3356,20 @@ TEST_F(TxnParticipantTest, ReturnNullTimestampIfNoOldestActiveTimestamp) { MongoDOperationContextSession newOpCtxSession(newOpCtx.get()); auto newTxnParticipant = TransactionParticipant::get(newOpCtx.get()); - newTxnParticipant->beginOrContinue(newTxnNum, false, true); - newTxnParticipant->unstashTransactionResources(newOpCtx.get(), "prepareTransaction"); + newTxnParticipant.beginOrContinue(newOpCtx.get(), newTxnNum, false, true); + newTxnParticipant.unstashTransactionResources(newOpCtx.get(), "prepareTransaction"); // secondPrepareTimestamp should be greater than firstPreparedTimestamp because this // transaction was prepared after. - newTxnParticipant->prepareTransaction(newOpCtx.get(), {}); + newTxnParticipant.prepareTransaction(newOpCtx.get(), {}); // Check that we added a Timestamp to the set. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 2U); // The oldest prepareTimestamp should still be firstPrepareTimestamp. - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); // Abort this transaction and check that we have decremented the total active timestamps // count. - newTxnParticipant->abortActiveTransaction(newOpCtx.get()); + newTxnParticipant.abortActiveTransaction(newOpCtx.get()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 1U); } @@ -3712,9 +3378,9 @@ TEST_F(TxnParticipantTest, ReturnNullTimestampIfNoOldestActiveTimestamp) { // Switch clients and abort the first transaction. This means we no longer have an oldest active // timestamp. - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT(txnParticipant.transactionIsAborted()); ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 0U); ASSERT_FALSE(ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime()); } @@ -3726,8 +3392,8 @@ TEST_F(TxnParticipantTest, ProperlyMaintainOldestNonMajorityCommittedOpTimeSet) // Check that there are no Timestamps in the set. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 0U); - txnParticipant->unstashTransactionResources(opCtx(), "prepareTransaction"); - auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + txnParticipant.unstashTransactionResources(opCtx(), "prepareTransaction"); + auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); // Check that we added a Timestamp to the set. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 1U); @@ -3746,14 +3412,14 @@ TEST_F(TxnParticipantTest, ProperlyMaintainOldestNonMajorityCommittedOpTimeSet) ServerTransactionsMetrics::get(opCtx())->getFinishOpTimeOfOldestNonMajCommitted_forTest(); ASSERT_EQ(nonMajorityCommittedOpTimeFinishOpTime->getTimestamp(), Timestamp::max()); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); // Since this test uses a mock opObserver, we have to manually set the finishTimestamp on the // txnParticipant. auto finishOpTime = repl::OpTime({10, 10}, 0); repl::ReplClientInfo::forClient(opCtx()->getClient()).setLastOp(finishOpTime); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT(txnParticipant->transactionIsAborted()); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT(txnParticipant.transactionIsAborted()); // Make sure that we moved the OpTime from the oldestActiveOplogEntryOpTimes to // oldestNonMajorityCommittedOpTimes along with the abort/commit oplog entry OpTime @@ -3860,13 +3526,13 @@ TEST_F(TxnParticipantTest, RollbackResetsInMemoryStateOfPreparedTransaction) { ASSERT_FALSE(ServerTransactionsMetrics::get(opCtx())->getOldestNonMajorityCommittedOpTime()); // Perform an insert as a part of a transaction so that we have a transaction operation. - txnParticipant->unstashTransactionResources(opCtx(), "insert"); + txnParticipant.unstashTransactionResources(opCtx(), "insert"); auto operation = repl::OplogEntry::makeInsertOperation(kNss, kUUID, BSON("TestValue" << 0)); - txnParticipant->addTransactionOperation(opCtx(), operation); + txnParticipant.addTransactionOperation(opCtx(), operation); ASSERT_BSONOBJ_EQ(operation.toBSON(), - txnParticipant->getTransactionOperationsForTest()[0].toBSON()); + txnParticipant.getTransactionOperationsForTest()[0].toBSON()); - auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), {}); + auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), {}); // Check that we added a Timestamp to oldestActiveOplogEntryOpTimes. ASSERT_EQ(ServerTransactionsMetrics::get(opCtx())->getTotalActiveOpTimes(), 1U); @@ -3878,23 +3544,23 @@ TEST_F(TxnParticipantTest, RollbackResetsInMemoryStateOfPreparedTransaction) { ServerTransactionsMetrics::get(opCtx())->getOldestNonMajorityCommittedOpTime(); ASSERT_EQ(oldestActiveOpTime->getTimestamp(), prepareTimestamp); ASSERT_EQ(oldestNonMajorityCommittedOpTime->getTimestamp(), prepareTimestamp); - ASSERT_FALSE(txnParticipant->transactionIsAborted()); + ASSERT_FALSE(txnParticipant.transactionIsAborted()); // Make sure the state of txnParticipant is populated correctly after a prepared transaction. - ASSERT(txnParticipant->transactionIsPrepared()); - ASSERT_EQ(txnParticipant->getTransactionOperationsForTest().size(), 1U); - ASSERT_EQ(txnParticipant->getPrepareOpTime().getTimestamp(), prepareTimestamp); - ASSERT_NE(txnParticipant->getActiveTxnNumber(), kUninitializedTxnNumber); + ASSERT(txnParticipant.transactionIsPrepared()); + ASSERT_EQ(txnParticipant.getTransactionOperationsForTest().size(), 1U); + ASSERT_EQ(txnParticipant.getPrepareOpTime().getTimestamp(), prepareTimestamp); + ASSERT_NE(txnParticipant.getActiveTxnNumber(), kUninitializedTxnNumber); - txnParticipant->abortPreparedTransactionForRollback(); + txnParticipant.abortPreparedTransactionForRollback(opCtx()); ServerTransactionsMetrics::get(opCtx())->clearOpTimes(); // After calling abortPreparedTransactionForRollback, the state of txnParticipant should be // invalidated. - ASSERT_FALSE(txnParticipant->transactionIsPrepared()); - ASSERT_EQ(txnParticipant->getTransactionOperationsForTest().size(), 0U); - ASSERT_EQ(txnParticipant->getPrepareOpTime().getTimestamp(), Timestamp()); - ASSERT_EQ(txnParticipant->getActiveTxnNumber(), kUninitializedTxnNumber); + ASSERT_FALSE(txnParticipant.transactionIsPrepared()); + ASSERT_EQ(txnParticipant.getTransactionOperationsForTest().size(), 0U); + ASSERT_EQ(txnParticipant.getPrepareOpTime().getTimestamp(), Timestamp()); + ASSERT_EQ(txnParticipant.getActiveTxnNumber(), kUninitializedTxnNumber); // After calling clearOpTimes, we should no longer have an oldestActiveOpTime. ASSERT_FALSE(ServerTransactionsMetrics::get(opCtx())->getOldestActiveOpTime()); @@ -3907,18 +3573,18 @@ TEST_F(TxnParticipantTest, PrepareTransactionAsSecondarySetsThePrepareOpTime) { auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), prepareOpTime); - ASSERT(txnParticipant->transactionIsPrepared()); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), prepareOpTime); + ASSERT(txnParticipant.transactionIsPrepared()); ASSERT_EQ(prepareTimestamp, prepareOpTime.getTimestamp()); - ASSERT_EQ(txnParticipant->getPrepareOpTime(), prepareOpTime); + ASSERT_EQ(txnParticipant.getPrepareOpTime(), prepareOpTime); // If _prepareOptime was not set and was null, then commitPreparedTransaction would falsely // succeed everytime. We set the commitTimestamp to be less than the prepareTimestamp to make // sure this is not the case. const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() - 1); - ASSERT_THROWS_CODE(txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}), + ASSERT_THROWS_CODE(txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}), AssertionException, ErrorCodes::InvalidOptions); } @@ -3932,18 +3598,18 @@ TEST_F(TxnParticipantTest, CommitPreparedTransactionAsSecondarySetsTheFinishOpTi repl::UnreplicatedWritesBlock uwb(opCtx()); ASSERT(!opCtx()->writesAreReplicated()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), prepareOpTime); - ASSERT(txnParticipant->transactionIsPrepared()); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), prepareOpTime); + ASSERT(txnParticipant.transactionIsPrepared()); ASSERT_EQ(prepareTimestamp, prepareOpTime.getTimestamp()); - ASSERT_EQ(txnParticipant->getPrepareOpTime(), prepareOpTime); + ASSERT_EQ(txnParticipant.getPrepareOpTime(), prepareOpTime); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); const auto commitOplogEntryOpTime = repl::OpTime({10, 10}, 0); - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, commitOplogEntryOpTime); - ASSERT_EQ(txnParticipant->getFinishOpTimeForTest().get(), commitOplogEntryOpTime); - ASSERT_TRUE(txnParticipant->transactionIsCommitted()); + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, commitOplogEntryOpTime); + ASSERT_EQ(txnParticipant.getFinishOpTimeForTest().get(), commitOplogEntryOpTime); + ASSERT_TRUE(txnParticipant.transactionIsCommitted()); } DEATH_TEST_F(TxnParticipantTest, @@ -3957,15 +3623,15 @@ DEATH_TEST_F(TxnParticipantTest, repl::UnreplicatedWritesBlock uwb(opCtx()); ASSERT(!opCtx()->writesAreReplicated()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), prepareOpTime); - ASSERT(txnParticipant->transactionIsPrepared()); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), prepareOpTime); + ASSERT(txnParticipant.transactionIsPrepared()); ASSERT_EQ(prepareTimestamp, prepareOpTime.getTimestamp()); - ASSERT_EQ(txnParticipant->getPrepareOpTime(), prepareOpTime); + ASSERT_EQ(txnParticipant.getPrepareOpTime(), prepareOpTime); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, {}); + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, {}); } DEATH_TEST_F(TxnParticipantTest, @@ -3975,16 +3641,16 @@ DEATH_TEST_F(TxnParticipantTest, auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); - txnParticipant->unstashTransactionResources(opCtx(), "commitTransaction"); - const auto prepareTimestamp = txnParticipant->prepareTransaction(opCtx(), prepareOpTime); - ASSERT(txnParticipant->transactionIsPrepared()); + txnParticipant.unstashTransactionResources(opCtx(), "commitTransaction"); + const auto prepareTimestamp = txnParticipant.prepareTransaction(opCtx(), prepareOpTime); + ASSERT(txnParticipant.transactionIsPrepared()); ASSERT_EQ(prepareTimestamp, prepareOpTime.getTimestamp()); - ASSERT_EQ(txnParticipant->getPrepareOpTime(), prepareOpTime); + ASSERT_EQ(txnParticipant.getPrepareOpTime(), prepareOpTime); const auto commitTimestamp = Timestamp(prepareTimestamp.getSecs(), prepareTimestamp.getInc() + 1); const auto commitOplogEntryOpTime = repl::OpTime({10, 10}, 0); - txnParticipant->commitPreparedTransaction(opCtx(), commitTimestamp, commitOplogEntryOpTime); + txnParticipant.commitPreparedTransaction(opCtx(), commitTimestamp, commitOplogEntryOpTime); } TEST_F(TxnParticipantTest, AbortTransactionOnSessionCheckoutWithoutRefresh) { @@ -4004,12 +3670,12 @@ TEST_F(TxnParticipantTest, AbortTransactionOnSessionCheckoutWithoutRefresh) { MongoDOperationContextSessionWithoutRefresh sessionCheckout(opCtx()); auto txnParticipant = TransactionParticipant::get(opCtx()); - ASSERT(txnParticipant->inMultiDocumentTransaction()); - ASSERT_EQ(txnParticipant->getActiveTxnNumber(), txnNumber); + ASSERT(txnParticipant.inMultiDocumentTransaction()); + ASSERT_EQ(txnParticipant.getActiveTxnNumber(), txnNumber); - txnParticipant->unstashTransactionResources(opCtx(), "abortTransaction"); - txnParticipant->abortActiveTransaction(opCtx()); - ASSERT_TRUE(txnParticipant->transactionIsAborted()); + txnParticipant.unstashTransactionResources(opCtx(), "abortTransaction"); + txnParticipant.abortActiveTransaction(opCtx()); + ASSERT_TRUE(txnParticipant.transactionIsAborted()); } } // namespace diff --git a/src/mongo/dbtests/storage_timestamp_tests.cpp b/src/mongo/dbtests/storage_timestamp_tests.cpp index 3572d305c92..543f95616dc 100644 --- a/src/mongo/dbtests/storage_timestamp_tests.cpp +++ b/src/mongo/dbtests/storage_timestamp_tests.cpp @@ -2638,14 +2638,14 @@ public: auto txnParticipant = TransactionParticipant::get(_opCtx); ASSERT(txnParticipant); - txnParticipant->beginOrContinue( - *_opCtx->getTxnNumber(), false /* autocommit */, true /* startTransaction */); - txnParticipant->unstashTransactionResources(_opCtx, "insert"); + txnParticipant.beginOrContinue( + _opCtx, *_opCtx->getTxnNumber(), false /* autocommit */, true /* startTransaction */); + txnParticipant.unstashTransactionResources(_opCtx, "insert"); { AutoGetCollection autoColl(_opCtx, nss, LockMode::MODE_IX, LockMode::MODE_IX); insertDocument(autoColl.getCollection(), InsertStatement(doc)); } - txnParticipant->stashTransactionResources(_opCtx); + txnParticipant.stashTransactionResources(_opCtx); { AutoGetCollection autoColl(_opCtx, nss, LockMode::MODE_IS, LockMode::MODE_IS); @@ -2687,11 +2687,11 @@ public: ASSERT(txnParticipant); logTimestamps(); - txnParticipant->unstashTransactionResources(_opCtx, "insert"); + txnParticipant.unstashTransactionResources(_opCtx, "insert"); - txnParticipant->commitUnpreparedTransaction(_opCtx); + txnParticipant.commitUnpreparedTransaction(_opCtx); - txnParticipant->stashTransactionResources(_opCtx); + txnParticipant.stashTransactionResources(_opCtx); { AutoGetCollection autoColl(_opCtx, nss, LockMode::MODE_X, LockMode::MODE_IX); auto coll = autoColl.getCollection(); @@ -2746,11 +2746,11 @@ public: assertOplogDocumentExistsAtTimestamp(commitFilter, commitEntryTs, false); assertOplogDocumentExistsAtTimestamp(commitFilter, commitTimestamp, false); } - txnParticipant->unstashTransactionResources(_opCtx, "insert"); + txnParticipant.unstashTransactionResources(_opCtx, "insert"); - txnParticipant->prepareTransaction(_opCtx, {}); + txnParticipant.prepareTransaction(_opCtx, {}); - txnParticipant->stashTransactionResources(_opCtx); + txnParticipant.stashTransactionResources(_opCtx); { const auto prepareFilter = BSON("ts" << prepareTs); assertOplogDocumentExistsAtTimestamp(prepareFilter, presentTs, false); @@ -2768,11 +2768,11 @@ public: assertOplogDocumentExistsAtTimestamp(commitFilter, commitTimestamp, false); assertOplogDocumentExistsAtTimestamp(commitFilter, nullTs, false); } - txnParticipant->unstashTransactionResources(_opCtx, "commitTransaction"); + txnParticipant.unstashTransactionResources(_opCtx, "commitTransaction"); - txnParticipant->commitPreparedTransaction(_opCtx, commitTimestamp, {}); + txnParticipant.commitPreparedTransaction(_opCtx, commitTimestamp, {}); - txnParticipant->stashTransactionResources(_opCtx); + txnParticipant.stashTransactionResources(_opCtx); { AutoGetCollection autoColl(_opCtx, nss, LockMode::MODE_X, LockMode::MODE_IX); auto coll = autoColl.getCollection(); |