void MongoDSessionCatalog::invalidateSessions(OperationContext* opCtx, boost::optional<BSONObj> singleSessionDoc) { const auto replCoord = repl::ReplicationCoordinator::get(opCtx); bool isReplSet = replCoord->getReplicationMode() == repl::ReplicationCoordinator::modeReplSet; if (isReplSet) { uassert(40528, str::stream() << "Direct writes against " << NamespaceString::kSessionTransactionsTableNamespace.ns() << " cannot be performed using a transaction or on a session.", !opCtx->getLogicalSessionId()); } 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>>(); if (singleSessionDoc) { sessionKillTokens->emplace_back(catalog->killSession(LogicalSessionId::parse( IDLParserErrorContext("lsid"), singleSessionDoc->getField("_id").Obj()))); } else { SessionKiller::Matcher matcher( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); catalog->scanSessions(matcher, [&sessionKillTokens](const ObservableSession& session) { sessionKillTokens->emplace_back(session.kill()); }); } killSessionTokensFunction(opCtx, sessionKillTokens); }
void yieldLocksForPreparedTransactions(OperationContext* opCtx) { // Create a new opCtx because we need an empty locker to refresh the locks. auto newClient = opCtx->getServiceContext()->makeClient("prepared-txns-yield-locks"); AlternativeClientRegion acr(newClient); auto newOpCtx = cc().makeOperationContext(); // Scan the sessions again to get the list of all sessions with prepared transaction // 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); } }); }
virtual bool run(OperationContext* opCtx, const std::string& db, const BSONObj& cmdObj, BSONObjBuilder& result) override { IDLParserErrorContext ctx("KillAllSessionsByPatternCmd"); auto ksc = KillAllSessionsByPatternCmd::parse(ctx, cmdObj); // The empty command kills all if (ksc.getKillAllSessionsByPattern().empty()) { ksc.setKillAllSessionsByPattern({makeKillAllSessionsByPattern(opCtx)}); } else { // If a pattern is passed, you may only pass impersonate data if you have the // impersonate privilege. auto authSession = AuthorizationSession::get(opCtx->getClient()); if (!authSession->isAuthorizedForPrivilege( Privilege(ResourcePattern::forClusterResource(), ActionType::impersonate))) { for (const auto& pattern : ksc.getKillAllSessionsByPattern()) { if (pattern.getUsers() || pattern.getRoles()) { uasserted(ErrorCodes::Unauthorized, "Not authorized to impersonate in killAllSessionsByPattern"); } } } } KillAllSessionsByPatternSet patterns{ksc.getKillAllSessionsByPattern().begin(), ksc.getKillAllSessionsByPattern().end()}; uassertStatusOK(killSessionsCmdHelper(opCtx, result, patterns)); return true; }
KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx, const LogicalSessionId& lsid) { KillAllSessionsByPattern kasbp = makeKillAllSessionsByPattern(opCtx); kasbp.setLsid(lsid); return kasbp; }
void killSessionsLocalShutdownAllTransactions(OperationContext* opCtx) { SessionKiller::Matcher matcherAllSessions( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); killSessionsAction(opCtx, matcherAllSessions, [](const ObservableSession&) { return true; }, [](OperationContext* opCtx, const SessionToKill& session) { TransactionParticipant::get(session.get())->shutdown(); }, ErrorCodes::InterruptedAtShutdown); }
KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx, const KillAllSessionsUser& kasu) { KillAllSessionsByPattern kasbp = makeKillAllSessionsByPattern(opCtx); auto authMgr = AuthorizationManager::get(opCtx->getServiceContext()); User* user; UserName un(kasu.getUser(), kasu.getDb()); uassertStatusOK(authMgr->acquireUser(opCtx, un, &user)); kasbp.setUid(user->getDigest()); authMgr->releaseUser(user); return kasbp; }
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(); }); }
void killAllExpiredTransactions(OperationContext* opCtx) { SessionKiller::Matcher matcherAllSessions( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); SessionCatalog::get(opCtx)->scanSessions( opCtx, matcherAllSessions, [](OperationContext* opCtx, Session* session) { try { TransactionParticipant::getFromNonCheckedOutSession(session) ->abortArbitraryTransactionIfExpired(); } catch (const DBException& ex) { Status status = ex.toStatus(); std::string errmsg = str::stream() << "May have failed to abort expired transaction with session id (lsid) '" << session->getSessionId() << "'." << " Caused by: " << status; warning() << errmsg; } }); }
void FeatureCompatibilityVersion::onInsertOrUpdate(OperationContext* opCtx, const BSONObj& doc) { auto idElement = doc["_id"]; if (idElement.type() != BSONType::String || idElement.String() != FeatureCompatibilityVersionParser::kParameterName) { return; } auto newVersion = uassertStatusOK(FeatureCompatibilityVersionParser::parse(doc)); // To avoid extra log messages when the targetVersion is set/unset, only log when the version // changes. bool isDifferent = serverGlobalParams.featureCompatibility.isVersionInitialized() ? serverGlobalParams.featureCompatibility.getVersion() != newVersion : true; if (isDifferent) { log() << "setting featureCompatibilityVersion to " << FeatureCompatibilityVersionParser::toString(newVersion); } opCtx->recoveryUnit()->onCommit([opCtx, newVersion](boost::optional<Timestamp>) { serverGlobalParams.featureCompatibility.setVersion(newVersion); updateMinWireVersion(); if (newVersion != ServerGlobalParams::FeatureCompatibility::Version::kFullyDowngradedTo36) { // Close all incoming connections from internal clients with binary versions lower than // ours. opCtx->getServiceContext()->getServiceEntryPoint()->endAllSessions( transport::Session::kLatestVersionInternalClientKeepOpen | transport::Session::kExternalClientKeepOpen); // Close all outgoing connections to servers with binary versions lower than ours. executor::EgressTagCloserManager::get(opCtx->getServiceContext()) .dropConnections(transport::Session::kKeepOpen); } if (newVersion != ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo40) { // Transactions are only allowed when the featureCompatibilityVersion is 4.0, so abort // any open transactions when downgrading featureCompatibilityVersion. SessionKiller::Matcher matcherAllSessions( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); killSessionsLocalKillTransactions(opCtx, matcherAllSessions); } }); }
void killAllExpiredTransactions(OperationContext* opCtx) { SessionKiller::Matcher matcherAllSessions( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); killSessionsAction( opCtx, matcherAllSessions, [when = opCtx->getServiceContext()->getPreciseClockSource()->now()]( const ObservableSession& session) { return TransactionParticipant::get(session).expiredAsOf(when); }, [](OperationContext* opCtx, const SessionToKill& session) { 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'"; txnParticipant.abortTransactionIfNotPrepared(opCtx); }, ErrorCodes::ExceededTimeLimit); }
void killAllExpiredTransactions(OperationContext* opCtx) { SessionKiller::Matcher matcherAllSessions( KillAllSessionsByPatternSet{makeKillAllSessionsByPattern(opCtx)}); killSessionsAction( opCtx, matcherAllSessions, [when = opCtx->getServiceContext()->getPreciseClockSource()->now()]( const ObservableSession& session) { return TransactionParticipant::get(session.get())->expired(); }, [](OperationContext* opCtx, const SessionToKill& session) { auto txnParticipant = TransactionParticipant::get(session.get()); LOG(0) << "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()); } }, ErrorCodes::ExceededTimeLimit); }
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"); }