Example #1
0
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);
            }
        });
}
Example #2
0
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);
}
Example #3
0
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();
        });
}
Example #4
0
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);
}
Example #7
0
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);
}