blob: 3c9454a575015caf2ae0d3930cfebb59804c4b54 [file] [log] [blame]
/*
* Copyright 2015 Nest Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <cassert>
#include <mutex>
#include <condition_variable>
#include <memory>
#ifdef __GNUC__
#define FOLLY_CANCELLATION_WARN_UNUSED __attribute__((warn_unused_result))
#else
#define FOLLY_CANCELLATION_WARN_UNUSED
#endif
#include <folly/detail/CancellationDetail.h>
namespace folly {
/**
* RAII object to prevent a Cancellation state from changing
*
* CancellationStateLock shall not be re-lockable. This is because
* after you've unlocked it, it is possible that the underlying
* cancellation was cancelled. There isn't a reliable way to represent
* this other than throwing an exception.
*
* This object is convertable to bool. If it becomes `true', then
* Cancellation IS NOT cancelled and you may proceed as normal. If it
* becomes `false' then the Cancellation has been CANCELLED.
*
* <code>
* auto lock = Cancellation.hold_state();
* if (lock) { // i.e. static_cast<bool>(lock)
* // NOT cancelled
* } else {
* // CANCELLED
* }
* </code>
*
* This is implemented using the "try_lock()" idiom of
* std::unique_lock<T>. Thus operator bool() is implemented to return
* 'true' if the lock is acquired, and 'false' if not. In the case of
* a Cancellation object, returning `true' means that we've acquired a
* hold on the "un-cancelled" state. `false' means that the
* Cancellation has been cancelled.
*/
class CancellationStateLock : public std::unique_lock<detail::CancellationSharedState>
{
public:
friend class Cancellation;
using base_type = std::unique_lock<detail::CancellationSharedState>;
/* Should only be constructed in Cancellation::hold_state() */
CancellationStateLock() = delete;
~CancellationStateLock() = default;
/* non-copyable */
CancellationStateLock(const CancellationStateLock& o) = delete;
CancellationStateLock& operator= (const CancellationStateLock& o) = delete;
/**
* move constructor
*/
CancellationStateLock(CancellationStateLock&& o) :
base_type(std::forward<base_type>(o))
{}
/**
* move operator
*/
CancellationStateLock& operator= (CancellationStateLock&& o)
{
base_type::operator=(std::forward<base_type>(o));
return *this;
}
private:
/**
* Construct a lock that refers to a specific CancellationSharedState
*
*/
CancellationStateLock(detail::CancellationSharedState& ss, std::try_to_lock_t tag)
: base_type(ss, tag)
{
}
/**
* This is made private to prevent its use (i.e. for re-lock()).
*/
void lock();
};
/**
* A thread-safe Cancellation object
*
* This is a token that is used to indicate whether or not some
* operation or object is cancelled, deleted, out of date,
* whatever. Like a mutex, this is an advisory token, and therefore it
* is up to the programmer to use it properly.
*
* It is implemented so that all objects refer to a shared state (much
* like a shared_ptr). Thus, all copies of the object refer to the
* same shared state object. Therefore a Cancellation token can be
* freely copied (by value), retaining a reference to the same shared
* state as the copied-from token.
*
* It has a very simple state machine:
*
* <code>
* cancel()
* start --> [NOT CANCELLED] ----------------> [CANCELLED]
* </code>
*
* The CANCELLED state is terminal.
*/
class Cancellation
{
public:
/**
* Construct a new cancellation object.
*/
Cancellation() :
_d(new detail::CancellationSharedState)
{}
/**
* Copy constructor
*
* The new Cancellation will refer to the same shared state as `o'.
*
* @param o an existing Cancellation object from whom we get our
* shared state.
*/
Cancellation(const Cancellation& o) :
_d(o._d)
{}
/**
* Assignment operator
*
* On return, the Cancellation will refer to the same shared state as `o'.
* We will drop the reference to the `current' shared state.
*
* @param o an existing Cancellation object from whom we get our
* shared state.
**/
Cancellation& operator=(const Cancellation& o)
{
_d = o._d;
return *this;
}
/**
* Move constructor
*
* The new Cancellation will refer to the same shared state as `o'.
*
* @param o an existing Cancellation object from whom we get our
* shared state.
*/
Cancellation(Cancellation&& o) :
_d(std::move(o._d))
{}
/**
* Move operator
*
* On return, the new Cancellation will refer to the same shared
* state as `o'. We will drop the reference to the `current' shared state.
*
* @param o an existing Cancellation object from whom we get our
* shared state.
*/
Cancellation& operator=(Cancellation&& o)
{
_d = std::move(o._d);
return *this;
}
/**
* Returns the current cancellation state of the object.
*
* It is generally not recommended that you use this function
* because it is not thread-safe. Use hold_state(), instead.
*
* Once an object is Cancelled, this function returns `true'. Once
* that happens, this function will never again return `false'.
*
* However, if this function returns `false' -- there's a
* possibility that the object is actually Cancelled. Therefore
* you should never rely on this value.
*
* @return true if the object has been cancelled. false if the
* object has NOT been cancelled.
*/
bool is_cancelled() const
{
return _d->is_cancelled();
}
/**
* Set the state of the object to "cancelled"
*
* If the object is alread cancelled, this function returns
* immediately.
*
* If not already cancelled, this function may block until all
* state holds are released.
*/
void cancel()
{
_d->cancel();
}
/**
* Acquire a hold on the state of the object
*
* If the object is not not cancelled, the returned object will
* prevent cancellation until it is deleted or released
* (via. unlock()).
*
* If the object is cancelled, it returns immediately.
*
* You MUST check the validity of the returned lock. When cast to
* bool, if it returns false then the object is cancelled.
*
* @return a CancellationStateLock object the prevents the state
* of the object from changing.
*/
CancellationStateLock hold_state() const FOLLY_CANCELLATION_WARN_UNUSED {
CancellationStateLock lock(*_d, std::try_to_lock);
return lock;
}
private:
std::shared_ptr<detail::CancellationSharedState> _d;
};
} /* namespace folly */