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
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:
parent
b6e7c12aee
commit
b385bca8e4
@ -61,6 +61,8 @@ target_sources(
|
||||
src/sled/debugging/demangle.cc
|
||||
src/sled/debugging/symbolize.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/log/log.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_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_future_test SRCS src/sled/futures/future_test.cc)
|
||||
sled_add_test(
|
||||
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)
|
||||
|
@ -11,7 +11,7 @@ TEST_SUITE("Async")
|
||||
CHECK_EQ(value, 126);
|
||||
return value;
|
||||
});
|
||||
task1.wait();
|
||||
// task1.wait();
|
||||
CHECK_EQ(126, task1.get());
|
||||
}
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -1,3 +0,0 @@
|
||||
#include <sled/futures/detail/just.h>
|
||||
|
||||
TEST(Just, basic) { auto s1 = sled::detail::Just(42); }
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
15
src/sled/futures/future.cc
Normal file
15
src/sled/futures/future.cc
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
#include "sled/futures/future.h"
|
||||
|
||||
namespace sled {
|
||||
namespace detail {
|
||||
void
|
||||
IncrementFuturesUsage()
|
||||
{}
|
||||
|
||||
void
|
||||
DecrementFuturesUsage()
|
||||
{}
|
||||
|
||||
}// namespace detail
|
||||
}// namespace sled
|
@ -1,6 +1,327 @@
|
||||
#ifndef 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
|
||||
|
26
src/sled/futures/future_test.cc
Normal file
26
src/sled/futures/future_test.cc
Normal 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") {}
|
||||
}
|
32
src/sled/futures/internal/failure_handling.cc
Normal file
32
src/sled/futures/internal/failure_handling.cc
Normal 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
|
108
src/sled/futures/internal/failure_handling.h
Normal file
108
src/sled/futures/internal/failure_handling.h
Normal 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
|
43
src/sled/futures/internal/promise.h
Normal file
43
src/sled/futures/internal/promise.h
Normal 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
@ -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();
|
||||
}
|
Loading…
Reference in New Issue
Block a user