feat add future
All checks were successful
linux-arm-gcc / linux-gcc-armhf (push) Successful in 2m15s
linux-mips64-gcc / linux-gcc-mips64el (Debug) (push) Successful in 2m14s
linux-aarch64-cpu-gcc / linux-gcc-aarch64 (push) Successful in 2m1s
linux-x64-gcc / linux-gcc (Debug) (push) Successful in 2m47s
linux-mips64-gcc / linux-gcc-mips64el (Release) (push) Successful in 2m33s
linux-x64-gcc / linux-gcc (Release) (push) Successful in 2m48s

This commit is contained in:
tqcq 2024-04-15 16:33:19 +00:00
parent b6e7c12aee
commit b385bca8e4
18 changed files with 551 additions and 2155 deletions

View File

@ -61,6 +61,8 @@ target_sources(
src/sled/debugging/demangle.cc src/sled/debugging/demangle.cc
src/sled/debugging/symbolize.cc src/sled/debugging/symbolize.cc
src/sled/event_bus/event_bus.cc src/sled/event_bus/event_bus.cc
src/sled/futures/future.cc
src/sled/futures/internal/failure_handling.cc
src/sled/filesystem/path.cc src/sled/filesystem/path.cc
src/sled/log/log.cc src/sled/log/log.cc
src/sled/network/async_resolver.cc src/sled/network/async_resolver.cc
@ -210,6 +212,7 @@ if(SLED_BUILD_TESTS)
sled_add_test(NAME sled_inja_test SRCS src/sled/nonstd/inja_test.cc) sled_add_test(NAME sled_inja_test SRCS src/sled/nonstd/inja_test.cc)
sled_add_test(NAME sled_fsm_test SRCS src/sled/nonstd/fsm_test.cc) sled_add_test(NAME sled_fsm_test SRCS src/sled/nonstd/fsm_test.cc)
sled_add_test(NAME sled_timestamp_test SRCS src/sled/units/timestamp_test.cc) sled_add_test(NAME sled_timestamp_test SRCS src/sled/units/timestamp_test.cc)
sled_add_test(NAME sled_future_test SRCS src/sled/futures/future_test.cc)
sled_add_test( sled_add_test(
NAME sled_cache_test SRCS src/sled/cache/lru_cache_test.cc NAME sled_cache_test SRCS src/sled/cache/lru_cache_test.cc
src/sled/cache/fifo_cache_test.cc src/sled/cache/expire_cache_test.cc) src/sled/cache/fifo_cache_test.cc src/sled/cache/expire_cache_test.cc)

View File

@ -11,7 +11,7 @@ TEST_SUITE("Async")
CHECK_EQ(value, 126); CHECK_EQ(value, 126);
return value; return value;
}); });
task1.wait(); // task1.wait();
CHECK_EQ(126, task1.get()); CHECK_EQ(126, task1.get());
} }

View File

@ -1,92 +0,0 @@
#ifndef SLED_FUTURES_DETAIL_DELAY_H
#define SLED_FUTURES_DETAIL_DELAY_H
#include "sled/units/time_delta.h"
#include "traits.h"
#include <exception>
namespace sled {
namespace detail {
template<typename R>
struct DelayReceiver {
R receiver;
sled::TimeDelta delta;
bool stopped = false;
template<typename U>
void SetValue(U &&val)
{
if (stopped) { return; }
receiver.SetValue(std::forward<U>(val));
}
void SetError(std::exception_ptr e)
{
if (stopped) { return; }
receiver.SetError(e);
}
void SetStopped()
{
if (stopped) { return; }
stopped = true;
receiver.SetStopped();
}
};
template<typename S, typename R>
struct DelayOperation {
ConnectResultT<S, R> op;
void Start() { op.Start(); }
void Stop() { op.Stop(); }
};
template<typename S>
struct DelaySender {
using result_t = typename S::result_t;
using this_type = DelaySender<S>;
S sender;
sled::TimeDelta delta;
template<typename R>
DelayOperation<S, DelayReceiver<R>> Connect(R receiver)
{
return {sender.Connect(DelayReceiver<R>{receiver, delta})};
}
template<typename Lazy>
friend ContinueResultT<this_type, Lazy> operator|(this_type sender, Lazy lazy)
{
return lazy.Continue(sender);
}
};
template<typename S>
DelaySender<S>
Delay(S sender, sled::TimeDelta const &delta)
{
return {sender, delta};
}
struct DelayLazy {
sled::TimeDelta delta;
template<typename S>
DelaySender<S> Continue(S sender) const
{
return {sender, delta};
}
};
inline DelayLazy
Delay(sled::TimeDelta const &delta)
{
return {delta};
}
}// namespace detail
}// namespace sled
#endif// SLED_FUTURES_DETAIL_DELAY_H

View File

@ -1,103 +0,0 @@
#ifndef SLED_FUTURES_DETAIL_FUTURE_H
#define SLED_FUTURES_DETAIL_FUTURE_H
#include "sled/synchronization/mutex.h"
#include "traits.h"
namespace sled {
namespace detail {
namespace {
enum class State {
kPending,
kError,
kValue,
};
template<typename T>
struct FutureState {
sled::Mutex mutex;
sled::ConditionVariable cv;
State state = State::kPending;
union {
T value;
std::exception_ptr error;
};
};
}// namespace
template<typename R>
struct FutureOperation {
std::shared_ptr<FutureState<typename R::result_t>> state;
R receiver;
mutable bool stopped = false;
void Start()
{
if (stopped) { return; }
sled::MutexLock lock(&state->mutex);
state->cv.Wait(lock, [&] { return state->state != State::kPending; });
if (state->state == State::kValue) {
receiver.SetValue(std::move(state->value));
} else {
receiver.SetError(state->error);
}
}
void Stop()
{
if (stopped) { return; }
stopped = true;
receiver.SetStopped();
}
};
template<typename T>
struct FutureSender {
using result_t = T;
using this_type = FutureSender<T>;
std::shared_ptr<FutureState<T>> state;
template<typename R>
FutureOperation<R> Connect(R receiver)
{
return {state, receiver};
}
template<typename Lazy>
friend ContinueResultT<this_type, Lazy> operator|(this_type sender, Lazy lazy)
{
return lazy.Continue(sender);
}
};
template<typename T>
struct Promise {
std::shared_ptr<FutureState<T>> state;
Promise() : state(std::make_shared<FutureState<T>>()) {}
template<typename U = T>
typename std::enable_if<std::is_convertible<U, T>::value>::type SetValue(U &&val)
{
sled::MutexLock lock(&state->mutex);
state->value = std::forward<T>(val);
state->state = State::kValue;
state->cv.NotifyAll();
}
void SetError(std::exception_ptr e)
{
sled::MutexLock lock(&state->mutex);
state->error = e;
state->state = State::kError;
state->cv.NotifyAll();
}
FutureSender<T> GetFuture() { return {state}; }
};
}// namespace detail
}// namespace sled
#endif// SLED_FUTURES_DETAIL_FUTURE_H

View File

@ -1,48 +0,0 @@
#ifndef SLED_FUTURES_DETAIL_JUST_H
#define SLED_FUTURES_DETAIL_JUST_H
#include "traits.h"
#include <memory>
namespace sled {
namespace detail {
template<typename T, typename R>
struct JustOperation {
T value;
R receiver;
void Start() { receiver.SetValue(std::forward<T>(value)); }
void Stop() { receiver.SetStopped(); }
};
template<typename T>
struct JustSender {
using result_t = T;
using this_type = JustSender<T>;
T value;
template<typename R>
JustOperation<T, R> Connect(R receiver)
{
return {std::forward<T>(value), receiver};
}
template<typename Lazy>
friend ContinueResultT<this_type, Lazy> operator|(this_type sender, Lazy lazy)
{
return lazy.Continue(sender);
}
};
template<typename T>
JustSender<T>
Just(T &&value)
{
return {std::forward<T>(value)};
}
}// namespace detail
}// namespace sled
#endif// SLED_FUTURES_DETAIL_JUST_H

View File

@ -1,3 +0,0 @@
#include <sled/futures/detail/just.h>
TEST(Just, basic) { auto s1 = sled::detail::Just(42); }

View File

@ -1,114 +0,0 @@
#ifndef SLED_FUTURES_DETAIL_VIA_H
#define SLED_FUTURES_DETAIL_VIA_H
#include "traits.h"
#include <exception>
#include <functional>
#include <memory>
namespace sled {
namespace detail {
template<typename R, typename F>
struct OnReceiver {
R receiver;
F schedule;
bool stopped = false;
template<typename U>
typename std::enable_if<std::is_rvalue_reference<U>::value
|| (!std::is_copy_assignable<U>::value && !std::is_copy_constructible<U>::value)>::type
SetValue(U &&val)
{
static_assert(std::is_rvalue_reference<decltype(val)>::value, "U must be an rvalue reference");
if (stopped) { return; }
try {
auto moved = make_move_on_copy(val);
schedule([this, moved]() mutable { receiver.SetValue(std::move(moved.value)); });
} catch (...) {
SetError(std::current_exception());
}
}
template<typename U>
typename std::enable_if<!std::is_rvalue_reference<U>::value
&& (std::is_copy_assignable<U>::value || std::is_copy_constructible<U>::value)>::type
SetValue(U &&val)
{
if (stopped) { return; }
try {
schedule([this, val] { receiver.SetValue(val); });
} catch (...) {
SetError(std::current_exception());
}
}
void SetError(std::exception_ptr e)
{
if (stopped) { return; }
receiver.SetError(e);
}
void SetStopped()
{
if (stopped) { return; }
stopped = true;
receiver.SetStopped();
}
};
template<typename S, typename R>
struct OnOperation {
ConnectResultT<S, R> op;
void Start() { op.Start(); }
void Stop() { op.Stop(); }
};
template<typename S, typename F>
struct OnSender {
using result_t = typename S::result_t;
using this_type = OnSender<S, F>;
S sender;
F schedule;
template<typename R>
OnOperation<S, OnReceiver<R, F>> Connect(R receiver)
{
return {sender.Connect(OnReceiver<R, F>{receiver, schedule})};
}
template<typename Lazy>
friend ContinueResultT<this_type, Lazy> operator|(this_type sender, Lazy lazy)
{
return lazy.Continue(sender);
}
};
template<typename F>
struct OnLazy {
F func;
template<typename S>
OnSender<S, F> Continue(S sender) const
{
return {sender, func};
}
};
template<typename S, typename F>
OnSender<S, F>
On(S sender, F &&schedule)
{
return {sender, std::forward<F>(schedule)};
}
template<typename F>
OnLazy<F>
On(F &&schedule)
{
return {schedule};
}
}// namespace detail
}// namespace sled
#endif// SLED_FUTURES_DETAIL_VIA_H

View File

@ -1,144 +0,0 @@
#ifndef SLED_FUTURES_DETAIL_RETRY_H
#define SLED_FUTURES_DETAIL_RETRY_H
#include "sled/log/log.h"
#include "sled/synchronization/mutex.h"
#include "traits.h"
#include <memory>
namespace sled {
namespace detail {
namespace {
struct RetryState {
enum State { kPending, kDone, kRetry };
sled::Mutex mutex;
sled::ConditionVariable cv;
int retry_count = 0;
State state = kPending;
};
}// namespace
template<typename R>
struct RetryReceiver {
std::shared_ptr<RetryState> state;
R receiver;
bool stopped = false;
template<typename U>
void SetValue(U &&val)
{
{
sled::MutexLock lock(&state->mutex);
if (stopped) { return; }
state->state = RetryState::kDone;
state->cv.NotifyAll();
}
receiver.SetValue(std::forward<U>(val));
}
void SetError(std::exception_ptr e)
{
// notify
{
sled::MutexLock lock(&state->mutex);
if (stopped) { return; }
if (state->retry_count > 0) {
--state->retry_count;
state->state = RetryState::kRetry;
return;
} else {
state->state = RetryState::kDone;
state->cv.NotifyAll();
}
}
receiver.SetError(e);
}
void SetStopped()
{
{
sled::MutexLock lock(&state->mutex);
if (stopped) { return; }
stopped = true;
state->state = RetryState::kDone;
state->cv.NotifyAll();
}
receiver.SetStopped();
}
};
template<typename S, typename R>
struct RetryOperation {
int retry_count;
std::shared_ptr<RetryState> state;
ConnectResultT<S, R> op;
void Start()
{
{
sled::MutexLock lock(&state->mutex);
state->retry_count = retry_count;
state->state = RetryState::kPending;
}
do {
op.Start();
sled::MutexLock lock(&state->mutex);
state->cv.Wait(lock, [this] { return state->state != RetryState::kPending; });
if (state->state == RetryState::kDone) { break; }
state->state = RetryState::kPending;
} while (true);
}
void Stop() { op.Stop(); }
};
template<typename S>
struct RetrySender {
using result_t = typename S::result_t;
using this_type = RetrySender<S>;
S sender;
int retry_count;
template<typename R>
RetryOperation<S, RetryReceiver<R>> Connect(R receiver)
{
auto state = std::make_shared<RetryState>();
auto op = sender.Connect(RetryReceiver<R>{state, receiver});
return {retry_count, state, op};
}
template<typename Lazy>
friend ContinueResultT<this_type, Lazy> operator|(this_type sender, Lazy lazy)
{
return lazy.Continue(sender);
}
};
template<typename S>
RetrySender<S>
Retry(S sender, int retry_count)
{
return {sender, retry_count};
}
struct RetryLazy {
int retry_count;
template<typename S>
RetrySender<S> Continue(S sender) const
{
return {sender, retry_count};
}
};
inline RetryLazy
Retry(int retry_count)
{
return {retry_count};
}
}// namespace detail
}// namespace sled
#endif// SLED_FUTURES_DETAIL_RETRY_H

View File

@ -1,97 +0,0 @@
#ifndef SLED_FUTURES_DETAIL_THEN_H
#define SLED_FUTURES_DETAIL_THEN_H
#include "traits.h"
#include <memory>
namespace sled {
namespace detail {
template<typename R, typename F>
struct ThenReceiver {
R receiver;
F func;
bool stopped = false;
template<typename U, typename Ret = invoke_result_t<F, U>>
void SetValue(U &&val)
{
if (stopped) { return; }
try {
receiver.SetValue(std::forward<Ret>(func(std::forward<U>(val))));
} catch (...) {
SetError(std::current_exception());
}
}
void SetError(std::exception_ptr e)
{
if (stopped) { return; }
receiver.SetError(e);
}
void SetStopped()
{
if (stopped) { return; }
stopped = true;
receiver.SetStopped();
}
};
template<typename S, typename R>
struct ThenOperation {
ConnectResultT<S, R> op;
void Start() { op.Start(); }
void Stop() { op.Stop(); }
};
template<typename S, typename F>
struct ThenSender {
using result_t = invoke_result_t<F, typename decay_t<S>::result_t>;
using this_type = ThenSender<S, F>;
S sender;
F func;
template<typename R>
ThenOperation<S, ThenReceiver<R, F>> Connect(R receiver)
{
return {sender.Connect(ThenReceiver<R, F>{receiver, func})};
}
template<typename Lazy>
friend ContinueResultT<this_type, Lazy> operator|(this_type sender, Lazy lazy)
{
return lazy.Continue(sender);
}
};
template<typename S, typename F>
ThenSender<S, F>
Then(S &&sender, F &&func)
{
return {std::forward<S>(sender), std::forward<F>(func)};
}
template<typename F>
struct ThenLazy {
F func;
template<typename S>
ThenSender<S, F> Continue(S sender) const
{
return {sender, func};
}
};
template<typename F>
ThenLazy<F>
Then(F &&func)
{
return {func};
}
}// namespace detail
}// namespace sled
#endif// SLED_FUTURES_DETAIL_THEN_H

View File

@ -1,56 +0,0 @@
#ifndef SLED_FUTURES_DETAIL_TRAITS_H
#define SLED_FUTURES_DETAIL_TRAITS_H
#include "sled/exec/detail/invoke_result.h"
#include <memory>
#include <type_traits>
namespace sled {
namespace detail {
template<typename S, typename R>
struct ConnectResult {
typedef decltype(std::declval<S>().Connect(std::declval<R>())) type;
};
template<typename S, typename R>
using ConnectResultT = typename ConnectResult<S, R>::type;
template<typename S, typename Lazy>
struct ContinueResult {
typedef decltype(std::declval<Lazy>().Continue(std::declval<S>())) type;
};
template<typename S, typename Lazy>
using ContinueResultT = typename ContinueResult<S, Lazy>::type;
template<typename F, typename... Args>
using invoke_result_t = eggs::invoke_result_t<F, Args...>;
template<typename T>
using decay_t = typename std::decay<T>::type;
template<typename T>
struct move_on_copy {
using type = typename std::remove_reference<T>::type;
move_on_copy(type &&value) : value(std::move(value)) {}
move_on_copy(const move_on_copy &other) : value(std::move(other.value)) {}
move_on_copy(move_on_copy &&) = delete;
move_on_copy &operator=(const move_on_copy &) = delete;
mutable type value;
};
template<typename T>
move_on_copy<T>
make_move_on_copy(T &&value)
{
return {std::move<T>(value)};
}
}// namespace detail
}// namespace sled
#endif// SLED_FUTURES_DETAIL_TRAITS_H

View File

@ -0,0 +1,15 @@
#include "sled/futures/future.h"
namespace sled {
namespace detail {
void
IncrementFuturesUsage()
{}
void
DecrementFuturesUsage()
{}
}// namespace detail
}// namespace sled

View File

@ -1,6 +1,327 @@
#ifndef SLED_FUTURES_FUTURE_H #ifndef SLED_FUTURES_FUTURE_H
#define SLED_FUTURES_FUTURE_H #define SLED_FUTURES_FUTURE_H
namespace sled {} #pragma once
#include "sled/futures/internal/failure_handling.h"
#include "sled/futures/internal/promise.h"
#include "sled/lang/attributes.h"
#include "sled/log/log.h"
#include "sled/synchronization/event.h"
#include "sled/synchronization/mutex.h"
#include "sled/variant.h"
#include <atomic>
#include <list>
namespace sled {
namespace detail {
template<typename F, typename... Args>
struct is_invocable : std::is_constructible<std::function<void(Args...)>,
std::reference_wrapper<typename std::remove_reference<F>::type>> {};
template<typename R, typename F, typename... Args>
struct is_invocable_r : std::is_constructible<std::function<R(Args...)>,
std::reference_wrapper<typename std::remove_reference<F>::type>> {};
enum FutureState {
kNotCompletedFuture = 0,
kSuccessFuture = 1,
kFailedFuture = 2,
};
SLED_EXPORT void IncrementFuturesUsage();
SLED_EXPORT void DecrementFuturesUsage();
template<typename T, typename FailureT>
struct FutureData {
FutureData() { IncrementFuturesUsage(); }
FutureData(const FutureData &) = delete;
FutureData(FutureData &&) = delete;
FutureData &operator=(const FutureData &) = delete;
FutureData &operator=(FutureData &&) = delete;
~FutureData() { DecrementFuturesUsage(); }
std::atomic_int state{kNotCompletedFuture};
sled::variant<sled::monostate, T, FailureT> value;
std::list<std::function<void(const T &)>> success_callbacks;
std::list<std::function<void(const FailureT &)>> failure_callbacks;
sled::Mutex mutex_;
};
}// namespace detail
//
template<typename T, typename FailureT>
class Future {
static_assert(!std::is_same<T, void>::value, "Future<void, _> is not allowed. Use Future<bool, _> instead");
static_assert(!std::is_same<FailureT, void>::value, "Future<_, void> is not allowed. Use Future<_, bool> instead");
template<typename T2, typename FailureT2>
friend class Future;
friend class Promise<T, FailureT>;
friend struct detail::FutureData<T, FailureT>;
public:
using Value = T;
using Failure = FailureT;
Future() noexcept = default;
explicit Future(const Promise<T, FailureT> &promise) { data_ = promise.future().data_; }
Future(const Future<T, FailureT> &) noexcept = default;
Future(Future<T, FailureT> &&) noexcept = default;
Future<T, FailureT> &operator=(const Future<T, FailureT> &) noexcept = default;
Future<T, FailureT> &operator=(Future<T, FailureT> &&) noexcept = default;
~Future() = default;
bool operator==(const Future<T, FailureT> &other) const noexcept { return data_ == other.data_; }
bool operator!=(const Future<T, FailureT> &other) const noexcept { return !operator==(other); }
bool IsCompleted() const noexcept
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
int value = data_->state.load(std::memory_order_acquire);
return value == detail::kSuccessFuture || value == detail::kFailedFuture;
}
bool IsFailed() const noexcept
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
return data_->state.load(std::memory_order_acquire) == detail::kFailedFuture;
}
bool IsSucceeded() const noexcept
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
return data_->state.load(std::memory_order_acquire) == detail::kSuccessFuture;
}
bool IsValid() const noexcept { return static_cast<bool>(data_); }
bool Wait(int64_t timeout_ms) const noexcept { return Wait(sled::TimeDelta::Millis(timeout_ms)); }
bool Wait(sled::TimeDelta timeout = sled::Event::kForever) const noexcept
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
if (IsCompleted()) { return true; }
bool wait_forever = timeout <= sled::TimeDelta::Zero();
sled::TimeDelta wait_time = wait_forever ? sled::Event::kForever : timeout;
auto event_ptr = std::make_shared<sled::Event>();
OnComplete([event_ptr]() { event_ptr->Set(); });
event_ptr->Wait(wait_time);
return IsCompleted();
}
template<typename Dummy = void,
typename = typename std::enable_if<std::is_copy_constructible<T>::value, Dummy>::type>
T Result() const noexcept
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
if (!IsCompleted()) Wait();
if (IsSucceeded()) {
try {
return sled::get<T>(data_->value);
} catch (...) {}
}
return T();
}
const T &ResultRef() const
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
if (!IsCompleted()) { Wait(); }
return sled::get<T>(data_->value);
}
FailureT FailureReason() const
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
if (!IsCompleted()) { Wait(); }
if (IsFailed()) {
try {
return sled::get<FailureT>(data_->value);
} catch (...) {}
}
return FailureT();
}
template<typename Func, typename = typename std::enable_if<detail::is_invocable<Func, T>::value>::type>
Future<T, FailureT> OnSuccess(Func &&f) const noexcept
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
bool call_it = false;
{
sled::MutexLock lock(&data_->mutex_);
if (IsCompleted()) {
call_it = IsSucceeded();
} else {
try {
data_->success_callbacks.emplace_back(std::forward<Func>(f));
} catch (std::exception &e) {
return Future<T, FailureT>::Failed(detail::ExceptionFailure<FailureT>(e));
} catch (...) {
return Future<T, FailureT>::Failed(detail::ExceptionFailure<FailureT>());
}
}
}
if (call_it) {
try {
f(sled::get<T>(data_->value));
} catch (...) {}
}
return Future<T, FailureT>(data_);
}
template<typename Func, typename = typename std::enable_if<detail::is_invocable<Func, FailureT>::value>::type>
Future<T, FailureT> OnFailure(Func &&f) const noexcept
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
bool call_it = false;
{
sled::MutexLock lock(&data_->mutex_);
if (IsCompleted()) {
call_it = IsFailed();
} else {
try {
data_->failure_callbacks.emplace_back(std::forward<Func>(f));
} catch (std::exception &e) {
return Future<T, FailureT>::Failed(detail::ExceptionFailure<FailureT>(e));
} catch (...) {
return Future<T, FailureT>::Failed(detail::ExceptionFailure<FailureT>());
}
}
}
if (call_it) {
try {
f(sled::get<FailureT>(data_->value));
} catch (...) {}
}
return Future<T, FailureT>(data_);
}
template<typename Func, typename = typename std::enable_if<detail::is_invocable<Func>::value>::type>
Future<T, FailureT> OnComplete(Func &&f) const noexcept
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
OnSuccess([f](const T &) noexcept { f(); });
OnFailure([f](const FailureT &) noexcept { f(); });
return Future<T, FailureT>(data_);
}
// template<typename Func, typename = typename std::enable_if<detail::is_invocable<Func>::value>::type>
// Future<T, FailureT> OnComplete(Func &&F) const noexcept
// {
// SLED_ASSERT(data_ != nullptr, "Future is not valid");
// OnSuccess([f](const auto &) noexcept { f(); })
// }
static Future<typename std::decay<T>::type, FailureT> Successful(T &&value) noexcept
{
Future<typename std::decay<T>::type, FailureT> result
= Future<typename std::decay<T>::type, FailureT>::Create();
result.FillSuccess(std::forward<T>(value));
return result;
}
static Future<T, FailureT> successful(const T &value) noexcept
{
Future<T, FailureT> result = Future<T, FailureT>::Create();
result.FillSuccess(value);
return result;
}
static Future<T, FailureT> Successful() { return Future<T, FailureT>::Successful(T()); }
static Future<T, FailureT> Failed(const FailureT &failure) noexcept
{
Future<T, FailureT> result = Future<T, FailureT>::Create();
result.FillFailure(failure);
return result;
}
private:
explicit Future(std::shared_ptr<detail::FutureData<T, FailureT>> other_data) { data_ = other_data; }
inline static Future<T, FailureT> Create()
{
Future<T, FailureT> result;
result.data_ = std::make_shared<detail::FutureData<T, FailureT>>();
return result;
}
void FillSuccess(const T &value)
{
T copy = value;
FillSuccess(std::move(copy));
}
void FillSuccess(T &&value)
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
if (detail::HasLastFailure()) {
FailureT failure = detail::LastFailure<FailureT>();
detail::InvalidateLastFailure();
FillFailure(std::move(failure));
return;
}
std::list<std::function<void(const T &)>> callbacks;
{
sled::MutexLock lock(&data_->mutex_);
if (IsCompleted()) { return; }
try {
data_->value.template emplace<T>(std::move(value));
} catch (...) {}
data_->state.store(detail::kSuccessFuture, std::memory_order_release);
callbacks = std::move(data_->success_callbacks);
data_->success_callbacks = std::list<std::function<void(const T &)>>();
data_->failure_callbacks.clear();
}
for (const auto &f : callbacks) {
try {
f(sled::get<T>(data_->value));
} catch (...) {}
}
}
void FillFailure(const FailureT &reason)
{
FailureT copy = reason;
FillFailure(std::move(copy));
}
void FillFailure(FailureT &&reason)
{
SLED_ASSERT(data_ != nullptr, "Future is not valid");
std::list<std::function<void(const FailureT &)>> callbacks;
{
sled::MutexLock lock(&data_->mutex_);
if (IsCompleted()) { return; }
try {
data_->value.template emplace<FailureT>(std::move(reason));
} catch (...) {}
data_->state.store(detail::kFailedFuture, std::memory_order_release);
callbacks = std::move(data_->failure_callbacks);
data_->failure_callbacks = std::list<std::function<void(const FailureT &)>>();
data_->success_callbacks.clear();
}
for (const auto &f : callbacks) {
try {
f(sled::get<FailureT>(data_->value));
} catch (...) {}
}
}
std::shared_ptr<detail::FutureData<T, FailureT>> data_;
};
}// namespace sled
#endif// SLED_FUTURES_FUTURE_H #endif// SLED_FUTURES_FUTURE_H

View File

@ -0,0 +1,26 @@
#include <sled/futures/future.h>
TEST_SUITE("future")
{
TEST_CASE("base success")
{
sled::Promise<int, std::string> p;
auto f = p.GetFuture();
p.Success(42);
CHECK(f.Wait(-1));
CHECK(f.IsValid());
CHECK_EQ(f.Result(), 42);
}
TEST_CASE("base failed")
{
sled::Promise<int, std::string> p;
auto f = p.GetFuture();
p.Failure("error");
REQUIRE(p.IsFilled());
REQUIRE(f.IsCompleted());
CHECK(f.Wait(-1));
CHECK(f.IsValid());
CHECK_EQ(f.FailureReason(), "error");
}
TEST_CASE("thread success") {}
}

View File

@ -0,0 +1,32 @@
#include "sled/futures/internal/failure_handling.h"
namespace sled {
namespace detail {
static thread_local sled::any last_failure;
bool
HasLastFailure() noexcept
{
return last_failure.has_value();
}
void
InvalidateLastFailure() noexcept
{
last_failure.reset();
}
const sled::any &
LastFailureAny() noexcept
{
return last_failure;
}
void
SetLastFailure(const sled::any &failure) noexcept
{
last_failure = failure;
}
}// namespace detail
}// namespace sled

View File

@ -0,0 +1,108 @@
#ifndef SLED_FUTURES_INTERNAL_FAILURE_HANDLING_H
#define SLED_FUTURES_INTERNAL_FAILURE_HANDLING_H
#pragma once
#include "sled/any.h"
#include <string>
namespace sled {
namespace failure {
template<typename FailureT>
inline FailureT
FailureFromString(std::string &&)
{
return FailureT();
}
template<typename FailureT>
inline FailureT
FailureFromString(const std::string &str)
{
std::string copy = str;
return FailureFromString<FailureT>(std::move(copy));
}
template<>
inline std::string
FailureFromString<std::string>(std::string &&str)
{
return std::move(str);
}
}// namespace failure
namespace detail {
bool HasLastFailure() noexcept;
void InvalidateLastFailure() noexcept;
const sled::any &LastFailureAny() noexcept;
void SetLastFailure(const sled::any &) noexcept;
template<typename FailureT>
inline FailureT
LastFailure() noexcept
{
if (!HasLastFailure()) { return FailureT(); }
try {
return sled::any_cast<FailureT>(LastFailureAny());
} catch (...) {
return FailureT();
}
}
template<typename FailureT>
inline void
SetLastFailure(const FailureT &failure) noexcept
{
SetLastFailure(sled::any(failure));
}
template<typename FailureT>
FailureT
ExceptionFailure(const std::exception &e)
{
return failure::FailureFromString<FailureT>(e.what());
}
template<typename FailureT>
FailureT
ExceptionFailure()
{
return failure::FailureFromString<FailureT>("Exception");
}
}// namespace detail
template<typename T, typename FailureT>
class Future;
template<typename FailureT>
struct WithFuture {
explicit WithFuture(const FailureT &f = FailureT()) noexcept : failure_(f) {}
explicit WithFuture(FailureT &&f) noexcept : failure_(std::move(f)) {}
template<typename... Args>
explicit WithFuture(Args &&...args) noexcept : failure_(std::forward<Args>(args)...)
{}
template<typename T>
operator T() const noexcept
{
detail::SetLastFailure(std::move(failure_));
return T();
}
template<typename T>
operator Future<T, FailureT>() noexcept
{
Future<T, FailureT> result = Future<T, FailureT>::Create();
result.Faillure(failure_);
return result;
}
private:
FailureT failure_;
};
}// namespace sled
#endif// SLED_FUTURES_INTERNAL_FAILURE_HANDLING_H

View File

@ -0,0 +1,43 @@
#ifndef SLED_FUTURES_INTERNAL_PROMISE_H
#define SLED_FUTURES_INTERNAL_PROMISE_H
#pragma once
#include <memory>
#include <type_traits>
namespace sled {
template<typename T, typename FailureT>
class Future;
template<typename T, typename FailureT>
class Promise {
static_assert(!std::is_same<T, void>::value, "Promise<void, _> is not allowed. Use Promise<bool, _> instead");
static_assert(!std::is_same<FailureT, void>::value,
"Promise<_, void> is not allowed. Use Promise<_, bool> instead");
public:
using Value = T;
Promise() = default;
Promise(const Promise &) noexcept = default;
Promise(Promise &&) noexcept = default;
Promise &operator=(const Promise &) noexcept = default;
Promise &operator=(Promise &&) noexcept = default;
~Promise() = default;
Future<T, FailureT> GetFuture() const { return future_; };
bool IsFilled() const noexcept { return future_.IsCompleted(); }
void Failure(const FailureT &reason) { return future_.FillFailure(reason); }
void Success(const T &value) { return future_.FillSuccess(value); }
void Success(T &&value) { return future_.FillSuccess(std::move(value)); }
private:
Future<T, FailureT> future_ = Future<T, FailureT>::Create();
};
}// namespace sled
#endif// SLED_FUTURES_INTERNAL_PROMISE_H

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
#include <gtest/gtest.h>
#include <sled/futures/promise.h>
TEST(Promise, Basic)
{
auto p = sled::Promise<int>();
auto v = p.Then([](int v) {
EXPECT_EQ(v, 1);
return v + 10;
})
.Tap([](int v) {
EXPECT_EQ(v, 11);
// no effect
return v + 1;
})
.Then([](int v) {
EXPECT_EQ(v, 11);
return v + 10;
});
p.Resolve(1);
}
TEST(Future, Basic)
{
auto p = sled::Promise<int>();
auto future = p.GetFuture()
.Then([](int v) {
EXPECT_EQ(v, 1);
return v + 10;
})
.Then([](int v) {
EXPECT_EQ(v, 11);
return v + 10;
});
p.Resolve(1);
EXPECT_EQ(future.Get(), 21);
}
TEST(Future, Except)
{
auto p = sled::Promise<int>();
p.Resolve(1);
p.GetFuture()
.Then([](int) {
return 1;
// throw std::runtime_error("test");
})
.Except([](std::exception_ptr e) {
try {
std::rethrow_exception(e);
} catch (const std::exception &e) {
EXPECT_STREQ(e.what(), "test");
}
return false;
})
.Get();
}