/**
* Copyright (C) 2018 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.
*/
#pragma once
#include "mongo/stdx/condition_variable.h"
#include "mongo/stdx/mutex.h"
#include "mongo/util/time_support.h"
#include "mongo/util/waitable.h"
namespace mongo {
/**
* A type which can be used to wait on condition variables with a level triggered one-way interrupt.
* I.e. after the interrupt is triggered (via some non-public api call) subsequent calls to
* waitForConditionXXX will fail. Interrupts must unblock all callers of waitForConditionXXX.
*/
class Interruptible {
protected:
struct DeadlineState {
Date_t deadline;
ErrorCodes::Error error;
bool hasArtificialDeadline;
};
struct IgnoreInterruptsState {
bool ignoreInterrupts;
DeadlineState deadline;
};
/**
* A deadline guard provides a subsidiary deadline to the parent.
*/
class DeadlineGuard {
public:
DeadlineGuard(const DeadlineGuard&) = delete;
DeadlineGuard& operator=(const DeadlineGuard&) = delete;
DeadlineGuard(DeadlineGuard&& other)
: _interruptible(other._interruptible), _oldDeadline(other._oldDeadline) {
other._interruptible = nullptr;
}
DeadlineGuard& operator=(DeadlineGuard&& other) = delete;
~DeadlineGuard() {
if (_interruptible) {
_interruptible->popArtificialDeadline(_oldDeadline);
}
}
private:
friend Interruptible;
explicit DeadlineGuard(Interruptible& interruptible,
Date_t newDeadline,
ErrorCodes::Error error)
: _interruptible(&interruptible),
_oldDeadline(_interruptible->pushArtificialDeadline(newDeadline, error)) {}
Interruptible* _interruptible;
DeadlineState _oldDeadline;
};
DeadlineGuard makeDeadlineGuard(Date_t deadline, ErrorCodes::Error error) {
return DeadlineGuard(*this, deadline, error);
}
/**
* An interruption guard provides a region where interruption is ignored.
*
* Note that this causes the deadline to be reset to Date_t::max(), but that it can also be
* subsequently reduced in size after the fact.
*/
class IgnoreInterruptionsGuard {
public:
IgnoreInterruptionsGuard(const IgnoreInterruptionsGuard&) = delete;
IgnoreInterruptionsGuard& operator=(const IgnoreInterruptionsGuard&) = delete;
IgnoreInterruptionsGuard(IgnoreInterruptionsGuard&& other)
: _interruptible(other._interruptible), _oldState(other._oldState) {
other._interruptible = nullptr;
}
IgnoreInterruptionsGuard& operator=(IgnoreInterruptionsGuard&&) = delete;
~IgnoreInterruptionsGuard() {
if (_interruptible) {
_interruptible->popIgnoreInterrupts(_oldState);
}
}
private:
friend Interruptible;
explicit IgnoreInterruptionsGuard(Interruptible& interruptible)
: _interruptible(&interruptible), _oldState(_interruptible->pushIgnoreInterrupts()) {}
Interruptible* _interruptible;
IgnoreInterruptsState _oldState;
};
IgnoreInterruptionsGuard makeIgnoreInterruptionsGuard() {
return IgnoreInterruptionsGuard(*this);
}
public:
/**
* Returns a statically allocated instance that cannot be interrupted. Useful as a default
* argument to interruptible taking methods.
*/
static Interruptible* notInterruptible();
/**
* Invokes the passed callback with a deadline guard active initialized with the passed
* deadline. Additionally handles the dance of try/catching the invocation and checking
* checkForInterrupt with the guard inactive (to allow a higher level timeout to override a
* lower level one)
*/
template
decltype(auto) runWithDeadline(Date_t deadline, ErrorCodes::Error error, Callback&& cb) {
invariant(ErrorCodes::isExceededTimeLimitError(error));
try {
const auto guard = makeDeadlineGuard(deadline, error);
return std::forward(cb)();
} catch (const ExceptionForCat&) {
// May throw replacement exception
checkForInterrupt();
throw;
}
}
bool hasDeadline() const {
return getDeadline() != Date_t::max();
}
/**
* Returns the deadline for this interruptible, or Date_t::max() if there is no deadline.
*/
virtual Date_t getDeadline() const = 0;
/**
* Invokes the passed callback with an interruption guard active. Additionally handles the
* dance of try/catching the invocation and checking checkForInterrupt with the guard inactive
* (to allow a higher level timeout to override a lower level one, or for top level interruption
* to propagate)
*/
template
decltype(auto) runWithoutInterruption(Callback&& cb) {
try {
const auto guard = makeIgnoreInterruptionsGuard();
return std::forward(cb)();
} catch (const ExceptionForCat&) {
// May throw replacement exception
checkForInterrupt();
throw;
}
}
/**
* Raises a AssertionException if this operation is in a killed state.
*/
void checkForInterrupt() {
uassertStatusOK(checkForInterruptNoAssert());
}
/**
* Returns Status::OK() unless this operation is in a killed state.
*/
virtual Status checkForInterruptNoAssert() noexcept = 0;
/**
* Waits for either the condition "cv" to be signaled, this operation to be interrupted, or the
* deadline on this operation to expire. In the event of interruption or operation deadline
* expiration, raises a AssertionException with an error code indicating the interruption type.
*/
void waitForConditionOrInterrupt(stdx::condition_variable& cv,
stdx::unique_lock& m) {
uassertStatusOK(waitForConditionOrInterruptNoAssert(cv, m));
}
/**
* Waits on condition "cv" for "pred" until "pred" returns true, or this operation
* is interrupted or its deadline expires. Throws a DBException for interruption and
* deadline expiration.
*/
template
void waitForConditionOrInterrupt(stdx::condition_variable& cv,
stdx::unique_lock& m,
Pred pred) {
while (!pred()) {
waitForConditionOrInterrupt(cv, m);
}
}
/**
* Same as waitForConditionOrInterrupt, except returns a Status instead of throwing
* a DBException to report interruption.
*/
Status 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::OK();
}
/**
* Same as the predicate form of waitForConditionOrInterrupt, except that it returns a not okay
* status instead of throwing on interruption.
*/
template
Status waitForConditionOrInterruptNoAssert(stdx::condition_variable& cv,
stdx::unique_lock& m,
Pred pred) noexcept {
while (!pred()) {
auto status = waitForConditionOrInterruptNoAssert(cv, m);
if (!status.isOK()) {
return status;
}
}
return Status::OK();
}
/**
* Waits for condition "cv" to be signaled, or for the given "deadline" to expire, or
* for the operation to be interrupted, or for the operation's own deadline to expire.
*
* If the operation deadline expires or the operation is interrupted, throws a DBException. If
* the given "deadline" expires, returns cv_status::timeout. Otherwise, returns
* cv_status::no_timeout.
*/
stdx::cv_status waitForConditionOrInterruptUntil(stdx::condition_variable& cv,
stdx::unique_lock& m,
Date_t deadline) {
return uassertStatusOK(waitForConditionOrInterruptNoAssertUntil(cv, m, deadline));
}
/**
* Waits on condition "cv" for "pred" until "pred" returns true, or the given "deadline"
* expires, or this operation is interrupted, or this operation's own deadline expires.
*
*
* If the operation deadline expires or the operation is interrupted, throws a DBException. If
* the given "deadline" expires, returns cv_status::timeout. Otherwise, returns
* cv_status::no_timeout indicating that "pred" finally returned true.
*/
template
bool waitForConditionOrInterruptUntil(stdx::condition_variable& cv,
stdx::unique_lock& m,
Date_t deadline,
Pred pred) {
while (!pred()) {
if (stdx::cv_status::timeout == waitForConditionOrInterruptUntil(cv, m, deadline)) {
return pred();
}
}
return true;
}
/**
* Same as the non-predicate form of waitForConditionOrInterruptUntil, but takes a relative
* amount of time to wait instead of an absolute time point.
*/
stdx::cv_status waitForConditionOrInterruptFor(stdx::condition_variable& cv,
stdx::unique_lock& m,
Milliseconds ms) {
return uassertStatusOK(
waitForConditionOrInterruptNoAssertUntil(cv, m, getExpirationDateForWaitForValue(ms)));
}
/**
* Same as the predicate form of waitForConditionOrInterruptUntil, but takes a relative
* amount of time to wait instead of an absolute time point.
*/
template
bool waitForConditionOrInterruptFor(stdx::condition_variable& cv,
stdx::unique_lock& m,
Milliseconds ms,
Pred pred) {
while (!pred()) {
if (stdx::cv_status::timeout == waitForConditionOrInterruptFor(cv, m, ms)) {
return pred();
}
}
return true;
}
/**
* Same as waitForConditionOrInterruptUntil, except returns StatusWith and
* non-ok status indicates the error instead of a DBException.
*/
virtual StatusWith waitForConditionOrInterruptNoAssertUntil(
stdx::condition_variable& cv,
stdx::unique_lock& m,
Date_t deadline) noexcept = 0;
/**
* Sleeps until "deadline"; throws an exception if the interruptible is interrupted before then.
*/
void sleepUntil(Date_t deadline) {
stdx::mutex m;
stdx::condition_variable cv;
stdx::unique_lock lk(m);
invariant(!waitForConditionOrInterruptUntil(cv, lk, deadline, [] { return false; }));
}
/**
* Sleeps for "duration" ms; throws an exception if the interruptible is interrupted before
* then.
*/
void sleepFor(Milliseconds duration) {
stdx::mutex m;
stdx::condition_variable cv;
stdx::unique_lock lk(m);
invariant(!waitForConditionOrInterruptFor(cv, lk, duration, [] { return false; }));
}
protected:
/**
* Pushes an ignore interruption critical section into the interruptible. Until an associated
* popIgnoreInterrupts is invoked, the interruptible should ignore interruptions related to
* explicit interruption or previously set deadlines.
*
* Note that new deadlines can be set after this is called, which will again introduce the
* possibility of interruption.
*
* Returns state needed to pop interruption.
*/
virtual IgnoreInterruptsState pushIgnoreInterrupts() = 0;
/**
* Pops the ignored interruption critical section introduced by push.
*/
virtual void popIgnoreInterrupts(IgnoreInterruptsState iis) = 0;
/**
* Pushes a subsidiary deadline into the interruptible. Until an associated
* popArtificialDeadline is
* invoked, the interruptible will fail checkForInterrupt and waitForConditionOrInterrupt calls
* with the passed error code if the deadline has passed.
*
* Note that deadline's higher than the current value are constrained (such that the passed
* error code will be returned/thrown, but after the min(oldDeadline, newDeadline) has passed).
*
* Returns state needed to pop the deadline.
*/
virtual DeadlineState pushArtificialDeadline(Date_t deadline, ErrorCodes::Error error) = 0;
/**
* Pops the subsidiary deadline introduced by push.
*/
virtual void popArtificialDeadline(DeadlineState) = 0;
/**
* Returns the equivalent of Date_t::now() + waitFor for the interruptible's clock
*/
virtual Date_t getExpirationDateForWaitForValue(Milliseconds waitFor) = 0;
class NotInterruptible;
};
/**
* A not interruptible type which can be used as a lightweight default arg for interruptible taking
* functions.
*/
class Interruptible::NotInterruptible final : public Interruptible {
StatusWith waitForConditionOrInterruptNoAssertUntil(
stdx::condition_variable& cv,
stdx::unique_lock& m,
Date_t deadline) noexcept override {
if (deadline == Date_t::max()) {
cv.wait(m);
return stdx::cv_status::no_timeout;
}
return cv.wait_until(m, deadline.toSystemTimePoint());
}
Date_t getDeadline() const override {
return Date_t::max();
}
Status checkForInterruptNoAssert() noexcept override {
return Status::OK();
}
// It's invalid to call the deadline or ignore interruption guards on a possibly noop
// interruptible.
//
// The noop interruptible should only be invoked as a default arg at the bottom of the call
// stack (with types that won't modify it's invocation)
IgnoreInterruptsState pushIgnoreInterrupts() override {
MONGO_UNREACHABLE;
}
void popIgnoreInterrupts(IgnoreInterruptsState) override {
MONGO_UNREACHABLE;
}
DeadlineState pushArtificialDeadline(Date_t deadline, ErrorCodes::Error error) override {
MONGO_UNREACHABLE;
}
void popArtificialDeadline(DeadlineState) override {
MONGO_UNREACHABLE;
}
Date_t getExpirationDateForWaitForValue(Milliseconds waitFor) override {
return Date_t::now() + waitFor;
}
};
inline Interruptible* Interruptible::notInterruptible() {
static NotInterruptible notInterruptible{};
return ¬Interruptible;
}
} // namespace mongo