Status doTxn(OperationContext* opCtx, const std::string& dbName, const BSONObj& doTxnCmd, BSONObjBuilder* result) { auto txnNumber = opCtx->getTxnNumber(); uassert(ErrorCodes::InvalidOptions, "doTxn can only be run with a transaction ID.", txnNumber); auto txnParticipant = TransactionParticipant::get(opCtx); uassert(ErrorCodes::InvalidOptions, "doTxn must be run within a transaction", txnParticipant); invariant(txnParticipant->inMultiDocumentTransaction()); invariant(opCtx->getWriteUnitOfWork()); uassert( ErrorCodes::InvalidOptions, "doTxn supports only CRUD opts.", _areOpsCrudOnly(doTxnCmd)); auto hasPrecondition = _hasPrecondition(doTxnCmd); // Acquire global lock in IX mode so that the replication state check will remain valid. Lock::GlobalLock globalLock(opCtx, MODE_IX); auto replCoord = repl::ReplicationCoordinator::get(opCtx); bool userInitiatedWritesAndNotPrimary = opCtx->writesAreReplicated() && !replCoord->canAcceptWritesForDatabase(opCtx, dbName); if (userInitiatedWritesAndNotPrimary) return Status(ErrorCodes::NotMaster, str::stream() << "Not primary while applying ops to database " << dbName); int numApplied = 0; try { BSONObjBuilder intermediateResult; // The transaction takes place in a global unit of work, so the precondition check // and the writes will share the same snapshot. if (hasPrecondition) { uassertStatusOK(_checkPrecondition(opCtx, doTxnCmd, result)); } numApplied = 0; uassertStatusOK(_doTxn(opCtx, dbName, doTxnCmd, &intermediateResult, &numApplied)); txnParticipant->commitUnpreparedTransaction(opCtx); result->appendElements(intermediateResult.obj()); } catch (const DBException& ex) { txnParticipant->abortActiveUnpreparedOrStashPreparedTransaction(opCtx); BSONArrayBuilder ab; ++numApplied; for (int j = 0; j < numApplied; j++) ab.append(false); result->append("applied", numApplied); result->append("code", ex.code()); result->append("codeName", ErrorCodes::errorString(ex.code())); result->append("errmsg", ex.what()); result->append("results", ab.arr()); return Status(ErrorCodes::UnknownError, ex.what()); } return Status::OK(); }
void killSessionsAbortUnpreparedTransactions(OperationContext* opCtx, const SessionKiller::Matcher& matcher, ErrorCodes::Error reason) { killSessionsAction( opCtx, matcher, [](const ObservableSession& session) { auto participant = TransactionParticipant::get(session); return participant.inMultiDocumentTransaction() && !participant.transactionIsPrepared(); }, [](OperationContext* opCtx, const SessionToKill& session) { TransactionParticipant::get(session).abortTransactionIfNotPrepared(opCtx); }, reason); }
void MongoDSessionCatalog::onStepUp(OperationContext* opCtx) { // Invalidate sessions that could have a retryable write on it, so that we can refresh from disk // in case the in-memory state was out of sync. const auto catalog = SessionCatalog::get(opCtx); // The use of shared_ptr here is in order to work around the limitation of stdx::function that // the functor must be copyable. auto sessionKillTokens = std::make_shared<std::vector<SessionCatalog::KillToken>>(); // Scan all sessions and reacquire locks for prepared transactions. // There may be sessions that are checked out during this scan, but none of them // can be prepared transactions, since only oplog application can make transactions // prepared on secondaries and oplog application has been stopped at this moment. std::vector<LogicalSessionId> sessionIdToReacquireLocks; SessionKiller::Matcher matcher( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); catalog->scanSessions(matcher, [&](const ObservableSession& session) { const auto txnParticipant = TransactionParticipant::get(session.get()); if (!txnParticipant->inMultiDocumentTransaction()) { sessionKillTokens->emplace_back(session.kill()); } if (txnParticipant->transactionIsPrepared()) { sessionIdToReacquireLocks.emplace_back(session.getSessionId()); } }); killSessionTokensFunction(opCtx, sessionKillTokens); { // Create a new opCtx because we need an empty locker to refresh the locks. auto newClient = opCtx->getServiceContext()->makeClient("restore-prepared-txn"); AlternativeClientRegion acr(newClient); for (const auto& sessionId : sessionIdToReacquireLocks) { auto newOpCtx = cc().makeOperationContext(); newOpCtx->setLogicalSessionId(sessionId); MongoDOperationContextSession ocs(newOpCtx.get()); auto txnParticipant = TransactionParticipant::get(OperationContextSession::get(newOpCtx.get())); txnParticipant->refreshLocksForPreparedTransaction(newOpCtx.get(), false); } } const size_t initialExtentSize = 0; const bool capped = false; const bool maxSize = 0; BSONObj result; DBDirectClient client(opCtx); if (client.createCollection(NamespaceString::kSessionTransactionsTableNamespace.ns(), initialExtentSize, capped, maxSize, &result)) { return; } const auto status = getStatusFromCommandResult(result); if (status == ErrorCodes::NamespaceExists) { return; } uassertStatusOKWithContext(status, str::stream() << "Failed to create the " << NamespaceString::kSessionTransactionsTableNamespace.ns() << " collection"); }