Files
2024-09-23 12:00:04 -04:00

512 lines
15 KiB
C++

/*
* Copyright Onix Solutions Limited [OnixS]. All rights reserved.
*
* This software owned by Onix Solutions Limited [OnixS] and is protected by copyright law
* and international copyright treaties.
*
* Access to and use of the software is governed by the terms of the applicable OnixS Software
* Services Agreement (the Agreement) and Customer end user license agreements granting
* a non-assignable, non-transferable and non-exclusive license to use the software
* for it's own data processing purposes under the terms defined in the Agreement.
*
* Except as otherwise granted within the terms of the Agreement, copying or reproduction of any part
* of this source code or associated reference material to any other location for further reproduction
* or redistribution, and any amendments to this copyright notice, are expressly prohibited.
*
* Any reproduction or redistribution for sale or hiring of the Software not in accordance with
* the terms of the Agreement is a violation of copyright law.
*/
#pragma once
#include <OnixS/FIXEngine/ABI.h>
#include <OnixS/FIXEngine/Compiler.h>
#include <OnixS/FIXEngine/Threading/Definitions.h>
#ifdef ONIXS_FIXENGINE_CXX11
#include <exception>
#endif
namespace OnixS {
namespace System
{
class FutureSharedState;
}
namespace Threading {
/// The state of a \c SharedFuture object (similar to std::future_status, @see http://en.cppreference.com/w/cpp/thread/future_status ).
struct FutureStatus
{
enum Enum
{
/// The shared state is ready.
ready,
/// The shared state did not become ready before the specified timeout duration has passed.
timeout,
/// The shared state contains a deferred function, so the result will be computed only when explicitly requested.
deferred
};
};
namespace Implementation {
class FutureHelper;
class PromiseBase;
// Implementation details.
class ValueBase
{
public:
virtual ~ValueBase() ONIXS_FIXENGINE_DEFAULT;
virtual ValueBase * clone() const = 0;
};
/// Implementation details.
class FutureValue
{
public:
FutureValue(ValueBase * value);
FutureValue();
FutureValue(const FutureValue & other);
~FutureValue();
ValueBase * value() const { return value_; }
private:
ValueBase * value_;
};
/// Implementation details.
template<typename ValueType>
class Value : public ValueBase
{
typedef Value<ValueType> Type;
public:
static const ValueType & value(const ValueBase * value)
{
return static_cast<const Type*>(value)->value();
}
static ValueBase * create(const ValueType & value)
{
return new Type(value);
}
ValueBase * clone() const ONIXS_FIXENGINE_OVERRIDE
{
return create(value_);
}
private:
explicit Value(const ValueType & value) : value_(value) {}
const ValueType & value() const { return value_; }
const ValueType value_;
};
/// The base implementation of the SharedFuture<T>.
class FutureBase
{
public:
/// Check if a future instance is associated with an asynchronous result.
///
/// Returns \c true if the *this has an associated asynchronous result, false otherwise.
bool valid() const ONIXS_FIXENGINE_NOTHROW
{
return state_ != ONIXS_FIXENGINE_NULLPTR;
}
/// Returns \c true if the asynchronous result associated with this Future is ready
/// (has a value or an exception stored in the shared state), \c false otherwise.
///
/// There are often situations where a \c get() call on a Future may not be a blocking call,
/// or is only a blocking call under certain circumstances.
/// This method gives the ability to test for early completion and allows us to avoid
/// associating a continuation, which needs to be scheduled with some non-trivial overhead
/// and near-certain loss of the cache efficiency.
///
/// @throw The std::logic_error if this instance does not refer to a shared state.
ONIXS_FIXENGINE_API bool isReady() const;
/// Returns \c true if the asynchronous result associated with this Future has a stored value,
/// \c false otherwise.
///
/// @throw The std::logic_error if this instance does not refer to a shared state.
ONIXS_FIXENGINE_API bool hasValue() const;
/// Returns \c true if the asynchronous result associated with this Future has a stored exception,
/// \c false otherwise.
///
/// @throw The std::logic_error if this instance does not refer to a shared state.
ONIXS_FIXENGINE_API bool hasException() const;
#ifdef ONIXS_FIXENGINE_CXX11
/// Returns the stored exception.
///
/// @throw The std::logic_error if this instance does not refer to a shared state.
/// @throw The std::logic_error if the operation has *not* finished with an error.
ONIXS_FIXENGINE_API std::exception_ptr getExceptionPtr() const;
#endif
enum { InfiniteTimeout = -1 };
/// Waits for the result to become available during the timeout.
///
/// \note Calling \c wait on the same Future from multiple threads is \e not safe;
/// the intended use is for each thread that waits on the same shared state to have a \e copy of a Future.
///
/// \throw The std::logic_error if this instance does not refer to a shared state.
ONIXS_FIXENGINE_API FutureStatus::Enum wait(int timeoutInMs = InfiniteTimeout) const;
protected:
FutureBase()
: state_() {}
ONIXS_FIXENGINE_API FutureBase(const FutureBase & other) ONIXS_FIXENGINE_NOTHROW;
ONIXS_FIXENGINE_API FutureBase & operator=(const FutureBase & other) ONIXS_FIXENGINE_NOTHROW;
#ifdef ONIXS_FIXENGINE_CXX11
ONIXS_FIXENGINE_API FutureBase(FutureBase && other) ONIXS_FIXENGINE_NOTHROW;
ONIXS_FIXENGINE_API FutureBase & operator=(FutureBase && other) ONIXS_FIXENGINE_NOTHROW;
#endif
struct moving_init_t {};
/// Initializes the instance with shared state.
ONIXS_FIXENGINE_API FutureBase(const System::FutureSharedState * state) ONIXS_FIXENGINE_NOTHROW;
/// Initializes the instance with shared state.
ONIXS_FIXENGINE_API FutureBase(const System::FutureSharedState * state, moving_init_t) ONIXS_FIXENGINE_NOTHROW;
/// Destroys a future object.
///
/// If this is the last reference to the asynchronous result associated with *this (if any), then destroy that asynchronous result.
ONIXS_FIXENGINE_API ~FutureBase() ONIXS_FIXENGINE_NOTHROW;
ONIXS_FIXENGINE_API void swap(FutureBase & other) ONIXS_FIXENGINE_NOTHROW;
ONIXS_FIXENGINE_API const FutureValue & getValue() const;
ONIXS_FIXENGINE_API void getVoid() const;
private:
const System::FutureSharedState * state_;
friend class FutureHelper;
friend class PromiseBase;
};
template<typename T>
struct FutureGetReturn
{
typedef const T & Type;
};
template<>
struct FutureGetReturn<void>
{
typedef void Type;
};
}
/// Represents a future result of an asynchronous operation - a result that will eventually appear in the Future after
/// the processing is complete.
///
/// The class template Future provides a mechanism to access the result of \e asynchronous operations :
/// \li An asynchronous operation (e.g. created via Promise) can provide a Future object to the creator of that asynchronous
/// operation (e.g. via the Promise::getFuture).
/// \li The creator of the asynchronous operation can then use a variety of methods to query, wait for, or extract a value from Future.
/// These methods may block if the asynchronous operation has not yet provided a value.
/// \li When the asynchronous operation is ready to send a result to the creator, it can do so by modifying the <em> shared state </em>
/// (e.g. via the Promise::setValue) that is linked to the creator's Future.
///
/// The Future is the synchronization object constructed around the \e receiving end of the Promise channel. It allows for the separation
/// of the initiation of an operation and the act of waiting for its result.
///
/// The Future is \e copiable and multiple SharedFuture objects may refer to the same shared state.
///
/// Unlike the std::future, this implementation has an extended API, allowing you to query the state of SharedFuture.
/// The internal implementation is lock-free; therefore, setting and polling states do not lead to blocking of threads and/or switching
/// of an execution context.
///
/// \note An access to the same shared state from multiple threads is safe if each thread does it through its own copy of
/// a SharedFuture object.
///
/// \see http://en.cppreference.com/w/cpp/thread/shared_future .
template <typename T>
class SharedFuture : public Implementation::FutureBase
{
public:
SharedFuture() ONIXS_FIXENGINE_NOTHROW
: FutureBase() {}
/// The copy constructor.
SharedFuture(const SharedFuture<T> & other) ONIXS_FIXENGINE_NOTHROW
: FutureBase(other) {}
/// The copy assignment.
SharedFuture<T> & operator=(const SharedFuture<T> & other) ONIXS_FIXENGINE_NOTHROW
{
FutureBase::operator=(other);
return *this;
}
#ifdef ONIXS_FIXENGINE_CXX11
SharedFuture(SharedFuture<T> && other) ONIXS_FIXENGINE_NOTHROW
: FutureBase(std::move(other)) {}
FutureBase & operator=(SharedFuture<T> && other) ONIXS_FIXENGINE_NOTHROW
{
FutureBase::operator=(std::move(other));
return *this;
}
#endif
/// Returns the result. If the result is not ready, the method will block. When completes, it either returns a value or throws an exception.
///
/// This method waits until the Future has a valid result and retrieves it. It effectively calls the wait() in order to wait for the result.
///
/// \exception If an exception was stored in the shared state referenced by this Future (e.g. via a call to the Promise::setException()) then that exception will be thrown.
typename Implementation::FutureGetReturn<T>::Type get() const
{
return Implementation::Value<T>::value(getValue().value());
}
/// swaps two SharedFuture objects
void swap(SharedFuture<T> & other) ONIXS_FIXENGINE_NOTHROW
{
FutureBase::swap(other);
}
private:
/// Initializes the instance with shared state.
SharedFuture(const System::FutureSharedState * state)
: FutureBase(state) {}
/// Initializes the instance with shared state.
SharedFuture(const System::FutureSharedState * state, moving_init_t t) ONIXS_FIXENGINE_NOTHROW
: FutureBase(state, t) {}
friend class Implementation::FutureHelper;
friend class Implementation::PromiseBase;
};
template<>
inline void SharedFuture<void>::get() const
{
getVoid();
}
namespace Implementation {
/// The base implementation of the PromiseFuture<T>.
class PromiseBase
{
public:
/// Check if a future instance is associated with an asynchronous result.
///
/// Returns \c true if the *this has an associated asynchronous result, false otherwise.
bool valid();
ONIXS_FIXENGINE_API void setExceptionImpl();
protected:
ONIXS_FIXENGINE_API PromiseBase() ONIXS_FIXENGINE_NOTHROW;
ONIXS_FIXENGINE_API PromiseBase(const PromiseBase & other) ONIXS_FIXENGINE_NOTHROW;
ONIXS_FIXENGINE_API PromiseBase & operator=(const PromiseBase & other) ONIXS_FIXENGINE_NOTHROW;
#ifdef ONIXS_FIXENGINE_CXX11
ONIXS_FIXENGINE_API PromiseBase(PromiseBase && other) ONIXS_FIXENGINE_NOTHROW;
ONIXS_FIXENGINE_API PromiseBase & operator=(PromiseBase && other) ONIXS_FIXENGINE_NOTHROW;
#endif
/// Destroys a future object.
///
/// If this is the last reference to the asynchronous result associated with *this (if any), then destroy that asynchronous result.
ONIXS_FIXENGINE_API ~PromiseBase() ONIXS_FIXENGINE_NOTHROW;
ONIXS_FIXENGINE_API void swap(PromiseBase & other) ONIXS_FIXENGINE_NOTHROW;
ONIXS_FIXENGINE_API void setValue(ValueBase * value);
ONIXS_FIXENGINE_API void setVoid();
ONIXS_FIXENGINE_API SharedFuture<FutureValue> getFutureImpl() const;
template<typename T>
SharedFuture<T> getTypedFuture() const
{
return SharedFuture<T>(getFutureImpl().state_);
}
private:
/// Implementation details.
struct Impl;
Impl * impl_;
};
}
/// Provides a facility to store a value or an exception that can be acquired asynchronously via a `SharedFuture` object created by the `Promise` object.
/// Each `Promise` object is associated with a shared state, which contains a some state information and a result which may be not yet evaluated, evaluated to a value or evaluated to an exception.
/// The promise stores the result or the exception in the shared state. Marks the state ready and unblocks any thread waiting on a future associated with the shared state.
template <typename T>
class Promise : public Implementation::PromiseBase
{
public:
Promise() ONIXS_FIXENGINE_NOTHROW
: PromiseBase() {}
/// The copy constructor.
Promise(const Promise<T> & other) ONIXS_FIXENGINE_NOTHROW
: PromiseBase(other) {}
/// The copy assignment.
Promise<T> & operator=(const Promise<T> & other) ONIXS_FIXENGINE_NOTHROW
{
PromiseBase::operator=(other);
return *this;
}
#ifdef ONIXS_FIXENGINE_CXX11
Promise(Promise<T> && other) ONIXS_FIXENGINE_NOTHROW
: PromiseBase(std::move(other)) {}
Promise & operator=(Promise<T> && other) ONIXS_FIXENGINE_NOTHROW
{
PromiseBase::operator=(std::move(other));
return *this;
}
#endif
/// Swaps two Promise objects
void swap(Promise<T> & other) ONIXS_FIXENGINE_NOTHROW
{
PromiseBase::swap(other);
}
SharedFuture<T> getFuture() const
{
return getTypedFuture<T>();
}
/// Sets the successful result value to the given shared state.
void set(const T & value)
{
setValue(Implementation::Value<T>::create(value));
}
/// Sets the result to indicate an exception.
///
/// @note Does nothing if the shared state already stores a value or exception.
template<typename ExceptionType>
void setException(const ExceptionType & exception)
{
try
{
throw exception;
}
catch(...)
{
setExceptionImpl();
}
}
};
template<>
class Promise<void> : public Implementation::PromiseBase
{
public:
Promise() ONIXS_FIXENGINE_NOTHROW
: PromiseBase() {}
/// The copy constructor.
Promise(const Promise<void> & other) ONIXS_FIXENGINE_NOTHROW
: PromiseBase(other) {}
/// The copy assignment.
Promise<void> & operator=(const Promise<void> & other) ONIXS_FIXENGINE_NOTHROW
{
PromiseBase::operator=(other);
return *this;
}
#ifdef ONIXS_FIXENGINE_CXX11
Promise(Promise<void> && other) ONIXS_FIXENGINE_NOTHROW
: PromiseBase(std::move(other)) {}
Promise & operator=(Promise<void> && other) ONIXS_FIXENGINE_NOTHROW
{
PromiseBase::operator=(std::move(other));
return *this;
}
#endif
/// Swaps two Promise objects
void swap(Promise<void> & other) ONIXS_FIXENGINE_NOTHROW
{
PromiseBase::swap(other);
}
SharedFuture<void> getFuture() const
{
return getTypedFuture<void>();
}
/// Sets the successful result to the given shared state.
void set()
{
setVoid();
}
/// Sets the result to indicate an exception.
///
/// @note Does nothing if the shared state already stores a value or exception.
template<typename ExceptionType>
void setException(const ExceptionType & exception)
{
try
{
throw exception;
}
catch(...)
{
setExceptionImpl();
}
}
};
}
}