mirror of
https://github.com/Naios/continuable.git
synced 2025-12-06 16:56:44 +08:00
Align the cancel behaviour of result to the one used in the unhandled exception handler
* Introduce cancellation_result to represent a cancelled async task * Add cancellation unit tests * This doesn't allow cancellation of continuables, it is meant for treating the special state action canceled on the receiver side. Cancellation of a chain is still up to the user.
This commit is contained in:
parent
c8c4325b5b
commit
957d3fa375
@ -180,6 +180,26 @@ public:
|
|||||||
data_ = nullptr;
|
data_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves the continuation with the cancellation token which is represented
|
||||||
|
/// by a default constructed exception_t.
|
||||||
|
///
|
||||||
|
/// \throws This method never throws an exception.
|
||||||
|
///
|
||||||
|
/// \attention This method may only be called once,
|
||||||
|
/// when the promise is valid operator bool() returns true.
|
||||||
|
/// Calling this method will invalidate the promise such that
|
||||||
|
/// subsequent calls to operator bool() will return false.
|
||||||
|
/// This behaviour is only consistent in promise_base and
|
||||||
|
/// non type erased promises may behave differently.
|
||||||
|
/// Invoking an invalid promise_base is undefined!
|
||||||
|
///
|
||||||
|
/// \since 4.0.0
|
||||||
|
void set_canceled() noexcept {
|
||||||
|
assert(data_);
|
||||||
|
std::move(data_)(exception_arg_t{}, exception_t{});
|
||||||
|
data_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the continuation is valid (non empty).
|
/// Returns true if the continuation is valid (non empty).
|
||||||
///
|
///
|
||||||
/// \throws This method never throws an exception.
|
/// \throws This method never throws an exception.
|
||||||
|
|||||||
@ -46,16 +46,25 @@ namespace cti {
|
|||||||
/// - *no result*: If the operation didn't finish
|
/// - *no result*: If the operation didn't finish
|
||||||
/// - *a value*: If the operation finished successfully
|
/// - *a value*: If the operation finished successfully
|
||||||
/// - *an exception*: If the operation finished with an exception
|
/// - *an exception*: If the operation finished with an exception
|
||||||
|
/// or was cancelled.
|
||||||
/// \{
|
/// \{
|
||||||
|
|
||||||
/// A class which is convertible to any \ref result and that definitly holds no
|
/// A class which is convertible to any \ref result and that definitely holds no
|
||||||
/// value so the real result gets invalidated when this object is passed to it.
|
/// value so the real result gets invalidated when this object is passed to it.
|
||||||
///
|
///
|
||||||
/// \since 4.0.0
|
/// \since 4.0.0
|
||||||
///
|
///
|
||||||
struct empty_result {};
|
struct empty_result {};
|
||||||
|
|
||||||
/// A class which is convertible to any result and that definitly holds
|
/// A class which is convertible to any \ref result and that definitely holds
|
||||||
|
/// a default constructed exception which signals the cancellation of the
|
||||||
|
/// asynchronous control flow.
|
||||||
|
///
|
||||||
|
/// \since 4.0.0
|
||||||
|
///
|
||||||
|
struct cancellation_result {};
|
||||||
|
|
||||||
|
/// A class which is convertible to any result and that holds
|
||||||
/// an exception which is then passed to the converted result object.
|
/// an exception which is then passed to the converted result object.
|
||||||
///
|
///
|
||||||
/// \since 4.0.0
|
/// \since 4.0.0
|
||||||
@ -72,9 +81,8 @@ public:
|
|||||||
~exceptional_result() = default;
|
~exceptional_result() = default;
|
||||||
|
|
||||||
explicit exceptional_result(exception_t exception)
|
explicit exceptional_result(exception_t exception)
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
|
||||||
: exception_(std::move(exception)) {
|
: exception_(std::move(exception)) {}
|
||||||
}
|
|
||||||
|
|
||||||
exceptional_result& operator=(exception_t exception) {
|
exceptional_result& operator=(exception_t exception) {
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
|
||||||
@ -110,6 +118,7 @@ public:
|
|||||||
/// - *no result*: If the operation didn't finish
|
/// - *no result*: If the operation didn't finish
|
||||||
/// - *a value*: If the operation finished successfully
|
/// - *a value*: If the operation finished successfully
|
||||||
/// - *an exception*: If the operation finished with an exception
|
/// - *an exception*: If the operation finished with an exception
|
||||||
|
/// or was cancelled.
|
||||||
///
|
///
|
||||||
/// The interface of the result object is similar to the one proposed in
|
/// The interface of the result object is similar to the one proposed in
|
||||||
/// the `std::expected` proposal:
|
/// the `std::expected` proposal:
|
||||||
@ -132,20 +141,17 @@ class result {
|
|||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
explicit result(detail::init_arg_t, Args&&... values)
|
explicit result(detail::init_arg_t, Args&&... values)
|
||||||
: variant_(trait_t::wrap(std::forward<Args>(values)...)) {
|
: variant_(trait_t::wrap(std::forward<Args>(values)...)) {}
|
||||||
}
|
|
||||||
explicit result(detail::init_arg_t, exception_t exception)
|
explicit result(detail::init_arg_t, exception_t exception)
|
||||||
: variant_(std::move(exception)) {
|
: variant_(std::move(exception)) {}
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using value_t = typename trait_t::value_t;
|
using value_t = typename trait_t::value_t;
|
||||||
|
|
||||||
template <typename FirstArg, typename... Args>
|
template <typename FirstArg, typename... Args>
|
||||||
explicit result(FirstArg&& first, Args&&... values)
|
explicit result(FirstArg&& first, Args&&... values)
|
||||||
: variant_(trait_t::wrap(std::forward<FirstArg>(first),
|
: variant_(trait_t::wrap(std::forward<FirstArg>(first),
|
||||||
std::forward<Args>(values)...)) {
|
std::forward<Args>(values)...)) {}
|
||||||
}
|
|
||||||
|
|
||||||
result() = default;
|
result() = default;
|
||||||
result(result const&) = default;
|
result(result const&) = default;
|
||||||
@ -154,13 +160,13 @@ public:
|
|||||||
result& operator=(result&&) = default;
|
result& operator=(result&&) = default;
|
||||||
~result() = default;
|
~result() = default;
|
||||||
|
|
||||||
explicit result(exception_t exception) : variant_(std::move(exception)) {
|
explicit result(exception_t exception)
|
||||||
}
|
: variant_(std::move(exception)) {}
|
||||||
result(empty_result) {
|
/* implicit */ result(empty_result) {}
|
||||||
}
|
/* implicit */ result(exceptional_result exceptional_result)
|
||||||
result(exceptional_result exceptional_result)
|
: variant_(std::move(exceptional_result.get_exception())) {}
|
||||||
: variant_(std::move(exceptional_result.get_exception())) {
|
/* implicit */ result(cancellation_result)
|
||||||
}
|
: variant_(exception_t{}) {}
|
||||||
|
|
||||||
result& operator=(empty_result) {
|
result& operator=(empty_result) {
|
||||||
set_empty();
|
set_empty();
|
||||||
@ -183,6 +189,10 @@ public:
|
|||||||
void set_exception(exception_t exception) {
|
void set_exception(exception_t exception) {
|
||||||
variant_ = std::move(exception);
|
variant_ = std::move(exception);
|
||||||
}
|
}
|
||||||
|
/// Set the result into a state which holds the cancellation token
|
||||||
|
void set_canceled() {
|
||||||
|
variant_ = exception_t{};
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the state of the result is empty
|
/// Returns true if the state of the result is empty
|
||||||
bool is_empty() const noexcept {
|
bool is_empty() const noexcept {
|
||||||
@ -192,7 +202,7 @@ public:
|
|||||||
bool is_value() const noexcept {
|
bool is_value() const noexcept {
|
||||||
return variant_.template is<surrogate_t>();
|
return variant_.template is<surrogate_t>();
|
||||||
}
|
}
|
||||||
/// Returns true if the state of the result holds an exception
|
/// Returns true if the state of the result holds a present exception
|
||||||
bool is_exception() const noexcept {
|
bool is_exception() const noexcept {
|
||||||
return variant_.template is<exception_t>();
|
return variant_.template is<exception_t>();
|
||||||
}
|
}
|
||||||
@ -313,18 +323,18 @@ namespace std {
|
|||||||
// The GCC standard library defines tuple_size as class and struct which
|
// The GCC standard library defines tuple_size as class and struct which
|
||||||
// triggers a warning here.
|
// triggers a warning here.
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma GCC diagnostic push
|
# pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wmismatched-tags"
|
# pragma GCC diagnostic ignored "-Wmismatched-tags"
|
||||||
#endif
|
#endif
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
struct tuple_size<cti::result<Args...>>
|
struct tuple_size<cti::result<Args...>>
|
||||||
: std::integral_constant<size_t, sizeof...(Args)> {};
|
: std::integral_constant<size_t, sizeof...(Args)> {};
|
||||||
|
|
||||||
template <std::size_t I, typename... Args>
|
template <std::size_t I, typename... Args>
|
||||||
struct tuple_element<I, cti::result<Args...>>
|
struct tuple_element<I, cti::result<Args...>>
|
||||||
: tuple_element<I, tuple<Args...>> {};
|
: tuple_element<I, tuple<Args...>> {};
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma GCC diagnostic pop
|
# pragma GCC diagnostic pop
|
||||||
#endif
|
#endif
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
|
|||||||
@ -381,7 +381,7 @@ inline auto invoker_of(identity<void>) {
|
|||||||
identity<>{});
|
identity<>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// - empty_result -> <cancel>
|
/// - empty_result -> <abort>
|
||||||
inline auto invoker_of(identity<empty_result>) {
|
inline auto invoker_of(identity<empty_result>) {
|
||||||
return make_invoker(
|
return make_invoker(
|
||||||
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
||||||
@ -392,7 +392,27 @@ inline auto invoker_of(identity<empty_result>) {
|
|||||||
std::forward<decltype(args)>(args)...);
|
std::forward<decltype(args)>(args)...);
|
||||||
|
|
||||||
// Don't invoke anything here since returning an empty result
|
// Don't invoke anything here since returning an empty result
|
||||||
// cancels the asynchronous chain effectively.
|
// aborts the asynchronous chain effectively.
|
||||||
|
(void)result;
|
||||||
|
CONTINUABLE_BLOCK_TRY_END
|
||||||
|
},
|
||||||
|
identity<>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// - cancellation_result -> <cancel>
|
||||||
|
inline auto invoker_of(identity<cancellation_result>) {
|
||||||
|
return make_invoker(
|
||||||
|
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
||||||
|
(void)next_callback;
|
||||||
|
CONTINUABLE_BLOCK_TRY_BEGIN
|
||||||
|
cancellation_result result = invoke_callback(
|
||||||
|
std::forward<decltype(callback)>(callback),
|
||||||
|
std::forward<decltype(args)>(args)...);
|
||||||
|
|
||||||
|
// Forward the cancellation to the next available exception handler
|
||||||
|
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
|
||||||
|
exception_arg_t{}, exception_t{});
|
||||||
|
|
||||||
(void)result;
|
(void)result;
|
||||||
CONTINUABLE_BLOCK_TRY_END
|
CONTINUABLE_BLOCK_TRY_END
|
||||||
},
|
},
|
||||||
@ -409,7 +429,7 @@ inline auto invoker_of(identity<exceptional_result>) {
|
|||||||
invoke_callback(std::forward<decltype(callback)>(callback),
|
invoke_callback(std::forward<decltype(callback)>(callback),
|
||||||
std::forward<decltype(args)>(args)...);
|
std::forward<decltype(args)>(args)...);
|
||||||
|
|
||||||
// Forward the exception to the next available handler
|
// Forward the exception to the next available exception handler
|
||||||
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
|
invoke_no_except(std::forward<decltype(next_callback)>(next_callback),
|
||||||
exception_arg_t{},
|
exception_arg_t{},
|
||||||
std::move(result).get_exception());
|
std::move(result).get_exception());
|
||||||
@ -441,9 +461,13 @@ auto invoker_of(identity<result<Args...>>) {
|
|||||||
|
|
||||||
} else if (result.is_exception()) {
|
} else if (result.is_exception()) {
|
||||||
// Forward the exception to the next available handler
|
// Forward the exception to the next available handler
|
||||||
invoke_no_except(
|
invoke_no_except(std::forward<decltype(next_callback)>(
|
||||||
std::forward<decltype(next_callback)>(next_callback),
|
next_callback),
|
||||||
exception_arg_t{}, std::move(result).get_exception());
|
exception_arg_t{},
|
||||||
|
std::move(result).get_exception());
|
||||||
|
} else {
|
||||||
|
// Aborts the continuation of the chain
|
||||||
|
assert(result.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise the result is empty and we are cancelling our
|
// Otherwise the result is empty and we are cancelling our
|
||||||
@ -543,6 +567,10 @@ public:
|
|||||||
std::move(next_callback_)(exception_arg_t{}, std::move(exception));
|
std::move(next_callback_)(exception_arg_t{}, std::move(exception));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_canceled() noexcept {
|
||||||
|
std::move(next_callback_)(exception_arg_t{}, exception_t{});
|
||||||
|
}
|
||||||
|
|
||||||
explicit operator bool() const noexcept {
|
explicit operator bool() const noexcept {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -699,9 +727,13 @@ struct callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
|
|||||||
std::move (*this)(std::move(args)...);
|
std::move (*this)(std::move(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves the continuation with the given error variable.
|
/// Resolves the continuation with the given exception.
|
||||||
void set_exception(exception_t error) noexcept {
|
void set_exception(exception_t exception) noexcept {
|
||||||
std::move (*this)(exception_arg_t{}, std::move(error));
|
std::move (*this)(exception_arg_t{}, std::move(exception));
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_canceled() noexcept {
|
||||||
|
std::move (*this)(exception_arg_t{}, exception_t{});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true because this is a present continuation
|
/// Returns true because this is a present continuation
|
||||||
@ -768,6 +800,10 @@ struct final_callback : util::non_copyable {
|
|||||||
std::move (*this)(exception_arg_t{}, std::move(exception));
|
std::move (*this)(exception_arg_t{}, std::move(exception));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_canceled() noexcept {
|
||||||
|
std::move (*this)(exception_arg_t{}, exception_t{});
|
||||||
|
}
|
||||||
|
|
||||||
explicit operator bool() const noexcept {
|
explicit operator bool() const noexcept {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -872,6 +908,8 @@ struct chained_continuation<identity<Args...>, identity<NextArgs...>,
|
|||||||
} else if (result.is_exception()) {
|
} else if (result.is_exception()) {
|
||||||
util::invoke(std::move(proxy), exception_arg_t{},
|
util::invoke(std::move(proxy), exception_arg_t{},
|
||||||
std::move(result.get_exception()));
|
std::move(result.get_exception()));
|
||||||
|
} else {
|
||||||
|
assert(result.is_empty());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Invoke the continuation with a proxy callback.
|
// Invoke the continuation with a proxy callback.
|
||||||
@ -927,6 +965,8 @@ struct chained_continuation<identity<Args...>, identity<NextArgs...>,
|
|||||||
} else if (result.is_exception()) {
|
} else if (result.is_exception()) {
|
||||||
util::invoke(std::move(proxy), exception_arg_t{},
|
util::invoke(std::move(proxy), exception_arg_t{},
|
||||||
std::move(result.get_exception()));
|
std::move(result.get_exception()));
|
||||||
|
} else {
|
||||||
|
assert(result.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,6 +93,10 @@ public:
|
|||||||
std::move (*this)(exception_arg_t{}, std::move(error));
|
std::move (*this)(exception_arg_t{}, std::move(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_canceled() noexcept {
|
||||||
|
std::move (*this)(exception_arg_t{}, exception_t{});
|
||||||
|
}
|
||||||
|
|
||||||
explicit operator bool() const noexcept {
|
explicit operator bool() const noexcept {
|
||||||
bool is_valid = operator_bool_or<First, true>::get(first_);
|
bool is_valid = operator_bool_or<First, true>::get(first_);
|
||||||
traverse_pack(
|
traverse_pack(
|
||||||
|
|||||||
@ -111,7 +111,7 @@ void assert_async_never_completed(C&& continuable) {
|
|||||||
|
|
||||||
FAIL();
|
FAIL();
|
||||||
})
|
})
|
||||||
.fail([](cti::exception_t error) {
|
.fail([](cti::exception_t) {
|
||||||
// ...
|
// ...
|
||||||
FAIL();
|
FAIL();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -104,6 +104,12 @@ TYPED_TEST(single_dimension_tests, are_not_finished_when_not_continued) {
|
|||||||
auto chain = create_incomplete(this);
|
auto chain = create_incomplete(this);
|
||||||
ASSERT_ASYNC_INCOMPLETION(std::move(chain).then(this->supply()));
|
ASSERT_ASYNC_INCOMPLETION(std::move(chain).then(this->supply()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ASSERT_ASYNC_INCOMPLETION(this->supply().then([] {
|
||||||
|
return empty_result();
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TYPED_TEST(single_dimension_tests, are_not_finished_when_cancelling) {
|
TYPED_TEST(single_dimension_tests, are_not_finished_when_cancelling) {
|
||||||
@ -118,6 +124,46 @@ TYPED_TEST(single_dimension_tests, are_not_finished_when_cancelling) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TYPED_TEST(single_dimension_tests, are_not_finished_when_cancelling_hook) {
|
||||||
|
{
|
||||||
|
ASSERT_ASYNC_CANCELLATION(
|
||||||
|
this->make(identity<>{}, identity<void>{}, [](auto&& callback) mutable {
|
||||||
|
std::forward<decltype(callback)>(callback).set_canceled();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ASSERT_ASYNC_CANCELLATION(
|
||||||
|
this->make(identity<>{}, identity<void>{}, [](auto&& callback) mutable {
|
||||||
|
std::forward<decltype(callback)>(callback).set_exception({});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ASSERT_ASYNC_CANCELLATION(this->supply().then([] {
|
||||||
|
return exceptional_result(exception_t{});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ASSERT_ASYNC_CANCELLATION(this->supply().then([]() -> result<> {
|
||||||
|
return exceptional_result(exception_t{});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ASSERT_ASYNC_CANCELLATION(this->supply().then([] {
|
||||||
|
return cancellation_result();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ASSERT_ASYNC_CANCELLATION(this->supply().then([]() -> result<> {
|
||||||
|
return cancellation_result();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TYPED_TEST(single_dimension_tests, freeze_is_kept_across_the_chain) {
|
TYPED_TEST(single_dimension_tests, freeze_is_kept_across_the_chain) {
|
||||||
{
|
{
|
||||||
auto chain = this->supply().freeze().then([=] {
|
auto chain = this->supply().freeze().then([=] {
|
||||||
|
|||||||
@ -56,7 +56,7 @@ TEST(promisify_tests, promisify_with) {
|
|||||||
auto c = cti::promisify<int>::with(
|
auto c = cti::promisify<int>::with(
|
||||||
[](auto&& promise, auto&& /*e*/, int const& value) {
|
[](auto&& promise, auto&& /*e*/, int const& value) {
|
||||||
EXPECT_EQ(value, 36354);
|
EXPECT_EQ(value, 36354);
|
||||||
promise.set_exception(cti::exception_t{});
|
promise.set_exception(supply_test_exception());
|
||||||
},
|
},
|
||||||
[&](auto&&... args) {
|
[&](auto&&... args) {
|
||||||
async_supply(std::forward<decltype(args)>(args)...);
|
async_supply(std::forward<decltype(args)>(args)...);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user