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;
|
||||
}
|
||||
|
||||
/// 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).
|
||||
///
|
||||
/// \throws This method never throws an exception.
|
||||
|
||||
@ -46,16 +46,25 @@ namespace cti {
|
||||
/// - *no result*: If the operation didn't finish
|
||||
/// - *a value*: If the operation finished successfully
|
||||
/// - *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.
|
||||
///
|
||||
/// \since 4.0.0
|
||||
///
|
||||
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.
|
||||
///
|
||||
/// \since 4.0.0
|
||||
@ -73,8 +82,7 @@ public:
|
||||
|
||||
explicit exceptional_result(exception_t exception)
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
|
||||
: exception_(std::move(exception)) {
|
||||
}
|
||||
: exception_(std::move(exception)) {}
|
||||
|
||||
exceptional_result& operator=(exception_t exception) {
|
||||
// NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg)
|
||||
@ -110,6 +118,7 @@ public:
|
||||
/// - *no result*: If the operation didn't finish
|
||||
/// - *a value*: If the operation finished successfully
|
||||
/// - *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 `std::expected` proposal:
|
||||
@ -132,11 +141,9 @@ class result {
|
||||
|
||||
template <typename... Args>
|
||||
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)
|
||||
: variant_(std::move(exception)) {
|
||||
}
|
||||
: variant_(std::move(exception)) {}
|
||||
|
||||
public:
|
||||
using value_t = typename trait_t::value_t;
|
||||
@ -144,8 +151,7 @@ public:
|
||||
template <typename FirstArg, typename... Args>
|
||||
explicit result(FirstArg&& first, Args&&... values)
|
||||
: variant_(trait_t::wrap(std::forward<FirstArg>(first),
|
||||
std::forward<Args>(values)...)) {
|
||||
}
|
||||
std::forward<Args>(values)...)) {}
|
||||
|
||||
result() = default;
|
||||
result(result const&) = default;
|
||||
@ -154,13 +160,13 @@ public:
|
||||
result& operator=(result&&) = default;
|
||||
~result() = default;
|
||||
|
||||
explicit result(exception_t exception) : variant_(std::move(exception)) {
|
||||
}
|
||||
result(empty_result) {
|
||||
}
|
||||
result(exceptional_result exceptional_result)
|
||||
: variant_(std::move(exceptional_result.get_exception())) {
|
||||
}
|
||||
explicit result(exception_t exception)
|
||||
: variant_(std::move(exception)) {}
|
||||
/* implicit */ result(empty_result) {}
|
||||
/* implicit */ result(exceptional_result exceptional_result)
|
||||
: variant_(std::move(exceptional_result.get_exception())) {}
|
||||
/* implicit */ result(cancellation_result)
|
||||
: variant_(exception_t{}) {}
|
||||
|
||||
result& operator=(empty_result) {
|
||||
set_empty();
|
||||
@ -183,6 +189,10 @@ public:
|
||||
void set_exception(exception_t 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
|
||||
bool is_empty() const noexcept {
|
||||
@ -192,7 +202,7 @@ public:
|
||||
bool is_value() const noexcept {
|
||||
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 {
|
||||
return variant_.template is<exception_t>();
|
||||
}
|
||||
|
||||
@ -381,7 +381,7 @@ inline auto invoker_of(identity<void>) {
|
||||
identity<>{});
|
||||
}
|
||||
|
||||
/// - empty_result -> <cancel>
|
||||
/// - empty_result -> <abort>
|
||||
inline auto invoker_of(identity<empty_result>) {
|
||||
return make_invoker(
|
||||
[](auto&& callback, auto&& next_callback, auto&&... args) {
|
||||
@ -392,7 +392,27 @@ inline auto invoker_of(identity<empty_result>) {
|
||||
std::forward<decltype(args)>(args)...);
|
||||
|
||||
// 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;
|
||||
CONTINUABLE_BLOCK_TRY_END
|
||||
},
|
||||
@ -409,7 +429,7 @@ inline auto invoker_of(identity<exceptional_result>) {
|
||||
invoke_callback(std::forward<decltype(callback)>(callback),
|
||||
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),
|
||||
exception_arg_t{},
|
||||
std::move(result).get_exception());
|
||||
@ -441,9 +461,13 @@ auto invoker_of(identity<result<Args...>>) {
|
||||
|
||||
} else if (result.is_exception()) {
|
||||
// Forward the exception to the next available handler
|
||||
invoke_no_except(
|
||||
std::forward<decltype(next_callback)>(next_callback),
|
||||
exception_arg_t{}, std::move(result).get_exception());
|
||||
invoke_no_except(std::forward<decltype(next_callback)>(
|
||||
next_callback),
|
||||
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
|
||||
@ -543,6 +567,10 @@ public:
|
||||
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 {
|
||||
return true;
|
||||
}
|
||||
@ -699,9 +727,13 @@ struct callback_base<identity<Args...>, HandleResults, HandleErrors, Callback,
|
||||
std::move (*this)(std::move(args)...);
|
||||
}
|
||||
|
||||
/// Resolves the continuation with the given error variable.
|
||||
void set_exception(exception_t error) noexcept {
|
||||
std::move (*this)(exception_arg_t{}, std::move(error));
|
||||
/// Resolves the continuation with the given exception.
|
||||
void set_exception(exception_t exception) noexcept {
|
||||
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
|
||||
@ -768,6 +800,10 @@ struct final_callback : util::non_copyable {
|
||||
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 {
|
||||
return true;
|
||||
}
|
||||
@ -872,6 +908,8 @@ struct chained_continuation<identity<Args...>, identity<NextArgs...>,
|
||||
} else if (result.is_exception()) {
|
||||
util::invoke(std::move(proxy), exception_arg_t{},
|
||||
std::move(result.get_exception()));
|
||||
} else {
|
||||
assert(result.is_empty());
|
||||
}
|
||||
} else {
|
||||
// Invoke the continuation with a proxy callback.
|
||||
@ -927,6 +965,8 @@ struct chained_continuation<identity<Args...>, identity<NextArgs...>,
|
||||
} else if (result.is_exception()) {
|
||||
util::invoke(std::move(proxy), exception_arg_t{},
|
||||
std::move(result.get_exception()));
|
||||
} else {
|
||||
assert(result.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -93,6 +93,10 @@ public:
|
||||
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 {
|
||||
bool is_valid = operator_bool_or<First, true>::get(first_);
|
||||
traverse_pack(
|
||||
|
||||
@ -111,7 +111,7 @@ void assert_async_never_completed(C&& continuable) {
|
||||
|
||||
FAIL();
|
||||
})
|
||||
.fail([](cti::exception_t error) {
|
||||
.fail([](cti::exception_t) {
|
||||
// ...
|
||||
FAIL();
|
||||
});
|
||||
|
||||
@ -104,6 +104,12 @@ TYPED_TEST(single_dimension_tests, are_not_finished_when_not_continued) {
|
||||
auto chain = create_incomplete(this);
|
||||
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) {
|
||||
@ -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) {
|
||||
{
|
||||
auto chain = this->supply().freeze().then([=] {
|
||||
|
||||
@ -56,7 +56,7 @@ TEST(promisify_tests, promisify_with) {
|
||||
auto c = cti::promisify<int>::with(
|
||||
[](auto&& promise, auto&& /*e*/, int const& value) {
|
||||
EXPECT_EQ(value, 36354);
|
||||
promise.set_exception(cti::exception_t{});
|
||||
promise.set_exception(supply_test_exception());
|
||||
},
|
||||
[&](auto&&... args) {
|
||||
async_supply(std::forward<decltype(args)>(args)...);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user