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); } }); }
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); }
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); }
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 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)}); 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); }