/** * Copyright (C) 2015 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault #include "mongo/platform/basic.h" #include "mongo/db/operation_context.h" #include "mongo/bson/inline_decls.h" #include "mongo/db/client.h" #include "mongo/db/service_context.h" #include "mongo/platform/random.h" #include "mongo/stdx/mutex.h" #include "mongo/util/assert_util.h" #include "mongo/util/clock_source.h" #include "mongo/util/fail_point_service.h" #include "mongo/util/log.h" #include "mongo/util/scopeguard.h" #include "mongo/util/system_tick_source.h" namespace mongo { namespace { // Enabling the maxTimeAlwaysTimeOut fail point will cause any query or command run with a // valid non-zero max time to fail immediately. Any getmore operation on a cursor already // created with a valid non-zero max time will also fail immediately. // // This fail point cannot be used with the maxTimeNeverTimeOut fail point. MONGO_FP_DECLARE(maxTimeAlwaysTimeOut); // Enabling the maxTimeNeverTimeOut fail point will cause the server to never time out any // query, command, or getmore operation, regardless of whether a max time is set. // // This fail point cannot be used with the maxTimeAlwaysTimeOut fail point. MONGO_FP_DECLARE(maxTimeNeverTimeOut); // Enabling the checkForInterruptFail fail point will start a game of random chance on the // connection specified in the fail point data, generating an interrupt with a given fixed // probability. Example invocation: // // {configureFailPoint: "checkForInterruptFail", // mode: "alwaysOn", // data: {conn: 17, chance: .01}} // // Both data fields must be specified. In the above example, all interrupt points on connection 17 // will generate a kill on the current operation with probability p(.01), including interrupt points // of nested operations. "chance" must be a double between 0 and 1, inclusive. MONGO_FP_DECLARE(checkForInterruptFail); } // namespace OperationContext::OperationContext(Client* client, unsigned int opId) : _client(client), _opId(opId), _elapsedTime(client ? client->getServiceContext()->getTickSource() : SystemTickSource::get()) {} void OperationContext::setDeadlineAndMaxTime(Date_t when, Microseconds maxTime) { invariant(!getClient()->isInDirectClient()); uassert(40120, "Illegal attempt to change operation deadline", !hasDeadline()); _deadline = when; _maxTime = maxTime; } Microseconds OperationContext::computeMaxTimeFromDeadline(Date_t when) { Microseconds maxTime; if (when == Date_t::max()) { maxTime = Microseconds::max(); } else { maxTime = when - getServiceContext()->getFastClockSource()->now(); if (maxTime < Microseconds::zero()) { maxTime = Microseconds::zero(); } } return maxTime; } OperationContext::DeadlineStash::DeadlineStash(OperationContext* opCtx) : _opCtx(opCtx), _originalDeadline(_opCtx->getDeadline()) { _opCtx->_deadline = Date_t::max(); _opCtx->_maxTime = _opCtx->computeMaxTimeFromDeadline(Date_t::max()); } OperationContext::DeadlineStash::~DeadlineStash() { _opCtx->_deadline = _originalDeadline; _opCtx->_maxTime = _opCtx->computeMaxTimeFromDeadline(_originalDeadline); } void OperationContext::setDeadlineByDate(Date_t when) { setDeadlineAndMaxTime(when, computeMaxTimeFromDeadline(when)); } void OperationContext::setDeadlineAfterNowBy(Microseconds maxTime) { Date_t when; if (maxTime < Microseconds::zero()) { maxTime = Microseconds::zero(); } if (maxTime == Microseconds::max()) { when = Date_t::max(); } else { auto clock = getServiceContext()->getFastClockSource(); when = clock->now(); if (maxTime > Microseconds::zero()) { when += clock->getPrecision() + maxTime; } } setDeadlineAndMaxTime(when, maxTime); } bool OperationContext::hasDeadlineExpired() const { if (!hasDeadline()) { return false; } if (MONGO_FAIL_POINT(maxTimeNeverTimeOut)) { return false; } if (MONGO_FAIL_POINT(maxTimeAlwaysTimeOut)) { return true; } // TODO: Remove once all OperationContexts are properly connected to Clients and ServiceContexts // in tests. if (MONGO_unlikely(!getClient() || !getServiceContext())) { return false; } const auto now = getServiceContext()->getFastClockSource()->now(); return now >= getDeadline(); } Milliseconds OperationContext::getRemainingMaxTimeMillis() const { if (!hasDeadline()) { return Milliseconds::max(); } return std::max(Milliseconds{0}, getDeadline() - getServiceContext()->getFastClockSource()->now()); } Microseconds OperationContext::getRemainingMaxTimeMicros() const { if (!hasDeadline()) { return Microseconds::max(); } return _maxTime - getElapsedTime(); } void OperationContext::checkForInterrupt() { uassertStatusOK(checkForInterruptNoAssert()); } namespace { // Helper function for checkForInterrupt fail point. Decides whether the operation currently // being run by the given Client meet the (probabilistic) conditions for interruption as // specified in the fail point info. bool opShouldFail(const OperationContext* opCtx, const BSONObj& failPointInfo) { // Only target the client with the specified connection number. if (opCtx->getClient()->getConnectionId() != failPointInfo["conn"].safeNumberLong()) { return false; } // Return true with (approx) probability p = "chance". Recall: 0 <= chance <= 1. double next = static_cast(std::abs(opCtx->getClient()->getPrng().nextInt64())); double upperBound = std::numeric_limits::max() * failPointInfo["chance"].numberDouble(); if (next > upperBound) { return false; } return true; } } // namespace Status OperationContext::checkForInterruptNoAssert() { // TODO: Remove the MONGO_likely(getClient()) once all operation contexts are constructed with // clients. if (MONGO_likely(getClient() && getServiceContext()) && getServiceContext()->getKillAllOperations()) { return Status(ErrorCodes::InterruptedAtShutdown, "interrupted at shutdown"); } if (hasDeadlineExpired()) { markKilled(ErrorCodes::ExceededTimeLimit); return Status(ErrorCodes::ExceededTimeLimit, "operation exceeded time limit"); } MONGO_FAIL_POINT_BLOCK(checkForInterruptFail, scopedFailPoint) { if (opShouldFail(this, scopedFailPoint.getData())) { log() << "set pending kill on op " << getOpID() << ", for checkForInterruptFail"; markKilled(); } } const auto killStatus = getKillStatus(); if (killStatus != ErrorCodes::OK) { return Status(killStatus, "operation was interrupted"); } return Status::OK(); } void OperationContext::sleepUntil(Date_t deadline) { stdx::mutex m; stdx::condition_variable cv; stdx::unique_lock lk(m); invariant(!waitForConditionOrInterruptUntil(cv, lk, deadline, [] { return false; })); } void OperationContext::sleepFor(Milliseconds duration) { stdx::mutex m; stdx::condition_variable cv; stdx::unique_lock lk(m); invariant(!waitForConditionOrInterruptFor(cv, lk, duration, [] { return false; })); } void OperationContext::waitForConditionOrInterrupt(stdx::condition_variable& cv, stdx::unique_lock& m) { uassertStatusOK(waitForConditionOrInterruptNoAssert(cv, m)); } Status OperationContext::waitForConditionOrInterruptNoAssert( stdx::condition_variable& cv, stdx::unique_lock& m) noexcept { auto status = waitForConditionOrInterruptNoAssertUntil(cv, m, Date_t::max()); if (!status.isOK()) { return status.getStatus(); } invariant(status.getValue() == stdx::cv_status::no_timeout); return status.getStatus(); } stdx::cv_status OperationContext::waitForConditionOrInterruptUntil( stdx::condition_variable& cv, stdx::unique_lock& m, Date_t deadline) { return uassertStatusOK(waitForConditionOrInterruptNoAssertUntil(cv, m, deadline)); } // Theory of operation for waitForConditionOrInterruptNoAssertUntil and markKilled: // // An operation indicates to potential killers that it is waiting on a condition variable by setting // _waitMutex and _waitCV, while holding the lock on its parent Client. It then unlocks its Client, // unblocking any killers, which are required to have locked the Client before calling markKilled. // // When _waitMutex and _waitCV are set, killers must lock _waitMutex before setting the _killCode, // and must signal _waitCV before releasing _waitMutex. Unfortunately, they must lock _waitMutex // without holding a lock on Client to avoid a deadlock with callers of // waitForConditionOrInterruptNoAssertUntil(). So, in the event that _waitMutex is set, the killer // increments _numKillers, drops the Client lock, acquires _waitMutex and then re-acquires the // Client lock. We know that the Client, its OperationContext and _waitMutex will remain valid // during this period because the caller of waitForConditionOrInterruptNoAssertUntil will not return // while _numKillers > 0 and will not return until it has itself reacquired _waitMutex. Instead, // that caller will keep waiting on _waitCV until _numKillers drops to 0. // // In essence, when _waitMutex is set, _killCode is guarded by _waitMutex and _waitCV, but when // _waitMutex is not set, it is guarded by the Client spinlock. Changing _waitMutex is itself // guarded by the Client spinlock and _numKillers. // // When _numKillers does drop to 0, the waiter will null out _waitMutex and _waitCV. // // This implementation adds a minimum of two spinlock acquire-release pairs to every condition // variable wait. StatusWith OperationContext::waitForConditionOrInterruptNoAssertUntil( stdx::condition_variable& cv, stdx::unique_lock& m, Date_t deadline) noexcept { invariant(getClient()); { stdx::lock_guard clientLock(*getClient()); invariant(!_waitMutex); invariant(!_waitCV); invariant(0 == _numKillers); // This interrupt check must be done while holding the client lock, so as not to race with a // concurrent caller of markKilled. auto status = checkForInterruptNoAssert(); if (!status.isOK()) { return status; } _waitMutex = m.mutex(); _waitCV = &cv; } // If the maxTimeNeverTimeOut failpoint is set, behave as though the operation's deadline does // not exist. Under normal circumstances, if the op has an existing deadline which is sooner // than the deadline passed into this method, we replace our deadline with the op's. This means // that we expect to time out at the same time as the existing deadline expires. If, when we // time out, we find that the op's deadline has not expired (as will always be the case if // maxTimeNeverTimeOut is set) then we assume that the incongruity is due to a clock mismatch // and return ExceededTimeLimit regardless. To prevent this behaviour, only consider the op's // deadline in the event that the maxTimeNeverTimeOut failpoint is not set. bool opHasDeadline = (hasDeadline() && !MONGO_FAIL_POINT(maxTimeNeverTimeOut)); if (opHasDeadline) { deadline = std::min(deadline, getDeadline()); } const auto waitStatus = [&] { if (Date_t::max() == deadline) { cv.wait(m); return stdx::cv_status::no_timeout; } return getServiceContext()->getPreciseClockSource()->waitForConditionUntil(cv, m, deadline); }(); // Continue waiting on cv until no other thread is attempting to kill this one. cv.wait(m, [this] { stdx::lock_guard clientLock(*getClient()); if (0 == _numKillers) { _waitMutex = nullptr; _waitCV = nullptr; return true; } return false; }); auto status = checkForInterruptNoAssert(); if (!status.isOK()) { return status; } if (opHasDeadline && waitStatus == stdx::cv_status::timeout && deadline == getDeadline()) { // It's possible that the system clock used in stdx::condition_variable::wait_until // is slightly ahead of the FastClock used in checkForInterrupt. In this case, // we treat the operation as though it has exceeded its time limit, just as if the // FastClock and system clock had agreed. markKilled(ErrorCodes::ExceededTimeLimit); return Status(ErrorCodes::ExceededTimeLimit, "operation exceeded time limit"); } return waitStatus; } void OperationContext::markKilled(ErrorCodes::Error killCode) { invariant(killCode != ErrorCodes::OK); stdx::unique_lock lkWaitMutex; if (_waitMutex) { invariant(++_numKillers > 0); getClient()->unlock(); ON_BLOCK_EXIT([this]() noexcept { getClient()->lock(); invariant(--_numKillers >= 0); }); lkWaitMutex = stdx::unique_lock{*_waitMutex}; } _killCode.compareAndSwap(ErrorCodes::OK, killCode); if (lkWaitMutex && _numKillers == 0) { invariant(_waitCV); _waitCV->notify_all(); } } void OperationContext::setLogicalSessionId(LogicalSessionId lsid) { invariant(!_lsid); _lsid = std::move(lsid); } void OperationContext::setTxnNumber(TxnNumber txnNumber) { invariant(_lsid); invariant(!_txnNumber); _txnNumber = txnNumber; } RecoveryUnit* OperationContext::releaseRecoveryUnit() { return _recoveryUnit.release(); } OperationContext::RecoveryUnitState OperationContext::setRecoveryUnit(RecoveryUnit* unit, RecoveryUnitState state) { _recoveryUnit.reset(unit); RecoveryUnitState oldState = _ruState; _ruState = state; return oldState; } std::unique_ptr OperationContext::releaseLockState() { dassert(_locker); return std::move(_locker); } void OperationContext::setLockState(std::unique_ptr locker) { dassert(!_locker); dassert(locker); _locker = std::move(locker); } Date_t OperationContext::getExpirationDateForWaitForValue(Milliseconds waitFor) { return getServiceContext()->getPreciseClockSource()->now() + waitFor; } } // namespace mongo