Example #1
0
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);
}
Example #2
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);
            }
        });
}
    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;
    }
Example #4
0
KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx,
                                                      const LogicalSessionId& lsid) {
    KillAllSessionsByPattern kasbp = makeKillAllSessionsByPattern(opCtx);
    kasbp.setLsid(lsid);

    return kasbp;
}
Example #5
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 #6
0
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;
}
Example #7
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 #8
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);
        }
    });
}
Example #10
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).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 #11
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);
}
Example #12
0
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");
}