Improve the use_continuable_t asio completion tokens such that special mappings are aptable

* Also allow to ignore certain error types through use_continuable_ignoring
* Ref #32
This commit is contained in:
Denis Blank 2020-04-09 17:23:01 +02:00
parent d80f5ef3ec
commit 8187c16ede
3 changed files with 239 additions and 65 deletions

View File

@ -30,6 +30,7 @@
#ifndef CONTINUABLE_DETAIL_ASIO_HPP_INCLUDED
#define CONTINUABLE_DETAIL_ASIO_HPP_INCLUDED
#include <array>
#include <utility>
#include <continuable/continuable-base.hpp>
#include <continuable/detail/core/base.hpp>
@ -110,26 +111,28 @@ using system_error_t = ::boost::system::system_error;
// Binds `promise` to the first argument of a continuable resolver, giving it
// the signature of an ASIO handler.
template <typename Promise>
auto promise_resolver_handler(Promise&& promise) noexcept {
return [promise = std::forward<Promise>(promise)](
error_code_t e, auto&&... args) mutable noexcept {
template <typename Promise, typename Token>
auto promise_resolver_handler(Promise&& promise, Token&& token) noexcept {
return [promise = std::forward<Promise>(promise),
token = std::forward<Token>(token)](error_code_t e,
auto&&... args) mutable noexcept {
if (e) {
if (e != basic_errors_t::operation_aborted) {
if (!token.is_ignored(e)) {
if (token.is_cancellation(e)) {
promise.set_canceled();
return;
} else {
#if defined(CONTINUABLE_HAS_EXCEPTIONS)
promise.set_exception(
std::make_exception_ptr(system_error_t(std::move(e))));
#else
promise.set_exception(exception_t(e.value(), e.category()));
#endif
} else {
// Continuable uses a default constructed exception type to signal
// cancellation to the followed asynchronous control flow.
promise.set_exception(exception_t{});
return;
}
}
}
} else {
promise.set_value(std::forward<decltype(args)>(args)...);
}
};
}
@ -155,6 +158,51 @@ template <typename... Args>
struct initiate_make_continuable<void(error_code_t const&, Args...)>
: initiate_make_continuable<void(error_code_t, Args...)> {};
struct map_default {
constexpr map_default() noexcept {}
bool is_cancellation(error_code_t const& ec) const noexcept {
// Continuable uses a default constructed exception type to signal
// cancellation to the followed asynchronous control flow.
return ec == basic_errors_t::operation_aborted;
}
bool is_ignored(error_code_t const& /*ec*/) const noexcept {
return false;
}
};
struct map_none {
constexpr map_none() noexcept {}
bool is_cancellation(error_code_t const& /*ec*/) const noexcept {
return false;
}
bool is_ignored(error_code_t const& /*ec*/) const noexcept {
return false;
}
};
template <std::size_t Size>
class map_ignore {
public:
map_ignore(std::array<basic_errors_t, Size> ignored) noexcept
: ignored_(ignored) {}
bool is_cancellation(error_code_t const& ec) const noexcept {
return ec == basic_errors_t::operation_aborted;
}
bool is_ignored(error_code_t const& ec) const noexcept {
for (basic_errors_t ignored : ignored_) {
if (ec == ignored) {
return true;
}
}
return false;
}
private:
std::array<basic_errors_t, Size> ignored_;
};
} // namespace asio
} // namespace detail
} // namespace cti

View File

@ -35,6 +35,21 @@
#include <continuable/detail/utility/traits.hpp>
namespace cti {
/// The error code type used by your asio distribution
///
/// \since 4.1.0
using asio_error_code_t = detail::asio::error_code_t;
/// The basic error code enum used by your asio distribution
///
/// \since 4.1.0
using asio_basic_errors_t = detail::asio::basic_errors_t;
/// The system error type used by your asio distribution
///
/// \since 4.1.0
using asio_system_error_t = detail::asio::system_error_t;
/// Type used as an ASIO completion token to specify an asynchronous operation
/// that should return a continuable_base.
///
@ -60,19 +75,77 @@ namespace cti {
/// });
/// ```
///
/// \tparam Mapper The token can be instantiated with a custom mapper
/// for asio error codes which makes it possible to ignore
/// errors or treat them as cancellation types.
/// The mapper has the following form:
/// ```
/// struct my_mapper {
/// constexpr my_mapper() noexcept {}
///
/// /// Returns true when the error_code_t is a type which represents
/// /// cancellation and
/// bool is_cancellation(error_code_t const& /*ec*/) const noexcept {
/// return false;
/// }
/// bool is_ignored(error_code_t const& /*ec*/) const noexcept {
/// return false;
/// }
/// };
/// ```
///
/// \attention `asio::error::basic_errors::operation_aborted` errors returned
/// by asio are automatically transformed into a default constructed
/// exception type which represents "operation canceled" by the
/// user or program. If you intend to retrieve the full
/// asio::error_code without remapping use the use_continuable_raw_t
/// completion token instead!
///
/// \since 4.0.0
struct use_continuable_t {};
template <typename Mapper = detail::asio::map_default>
struct use_continuable_t : public Mapper {
using Mapper::Mapper;
};
/// Special value for instance of `asio_token_t`
/// \copydoc use_continuable_t
///
/// The raw async completion handler token does not remap the asio error
/// `asio::error::basic_errors::operation_aborted` to a default constructed
/// exception type.
///
/// \since 4.1.0
using use_continuable_raw_t = use_continuable_t<detail::asio::map_none>;
/// Special value for instance of use_continuable_t which performs remapping
/// of asio error codes to align the cancellation behaviour with the library.
///
/// \copydetails use_continuable_t
constexpr use_continuable_t use_continuable{};
constexpr use_continuable_t<> use_continuable{};
/// Special value for instance of use_continuable_raw_t which doesn't perform
/// remapping of asio error codes and rethrows the raw error code.
///
/// \copydetails use_continuable_raw_t
constexpr use_continuable_raw_t use_continuable_raw{};
/// Represents a special asio completion token which treats the given
/// asio basic error codes as success instead of failure.
///
/// `asio::error::basic_errors::operation_aborted` is mapped
/// as cancellation token.
///
/// \since 4.1.0
template <typename... Args>
auto use_continuable_ignoring(Args&&... args) noexcept {
return use_continuable_t<detail::asio::map_ignore<sizeof...(Args)>>{
{asio_basic_errors_t(std::forward<Args>(args))...}};
}
} // namespace cti
CTI_DETAIL_ASIO_NAMESPACE_BEGIN
template <typename Signature>
class async_result<cti::use_continuable_t, Signature> {
template <typename Signature, typename Matcher>
class async_result<cti::use_continuable_t<Matcher>, Signature> {
public:
#if defined(CTI_DETAIL_ASIO_HAS_EXPLICIT_RET_TYPE_INTEGRATION)
using return_type = typename cti::detail::asio::initiate_make_continuable<
@ -80,16 +153,16 @@ public:
#endif
template <typename Initiation, typename... Args>
static auto initiate(Initiation initiation, cti::use_continuable_t,
Args... args) {
static auto initiate(Initiation initiation,
cti::use_continuable_t<Matcher> token, Args... args) {
return cti::detail::asio::initiate_make_continuable<Signature>{}(
[initiation = std::move(initiation),
[initiation = std::move(initiation), token = std::move(token),
init_args = std::make_tuple(std::move(args)...)](
auto&& promise) mutable {
cti::detail::traits::unpack(
[initiation = std::move(initiation),
handler = cti::detail::asio::promise_resolver_handler(
std::forward<decltype(promise)>(promise))](
std::forward<decltype(promise)>(promise), std::move(token))](
auto&&... args) mutable {
std::move(initiation)(std::move(handler),
std::forward<decltype(args)>(args)...);

View File

@ -44,9 +44,7 @@ public:
}) {}
~async_test_helper() {
assert(work_);
timer_.cancel();
work_.reset();
cancel();
thread_.join();
}
@ -55,6 +53,11 @@ public:
return timer_.async_wait(use_continuable);
}
void cancel() {
timer_.cancel();
work_.reset();
}
private:
asio::io_context context_;
asio::steady_timer timer_;
@ -141,3 +144,53 @@ TYPED_TEST(single_dimension_tests, wait_for_test_async) {
result<> res = helper.wait_for(500ms).apply(cti::transforms::wait_for(50ms));
ASSERT_FALSE(res.is_exception());
}
TYPED_TEST(single_dimension_tests, token_remap_canceled) {
asio::io_context io(1);
asio::steady_timer timer(io, 50ms);
result<> value;
timer.async_wait(use_continuable).next([&](auto&&... args) {
value = result<>::from(std::forward<decltype(args)>(args)...);
});
timer.cancel();
io.run();
ASSERT_TRUE(value.is_exception());
ASSERT_FALSE(bool(value.get_exception()));
}
TYPED_TEST(single_dimension_tests, token_remap_none_raw) {
asio::io_context io(1);
asio::steady_timer timer(io, 50ms);
result<> value;
timer.async_wait(use_continuable_raw).next([&](auto&&... args) {
value = result<>::from(std::forward<decltype(args)>(args)...);
});
timer.cancel();
io.run();
ASSERT_TRUE(value.is_exception());
ASSERT_TRUE(bool(value.get_exception()));
}
TYPED_TEST(single_dimension_tests, token_remap_ignore) {
asio::io_context io(1);
asio::steady_timer timer(io, 50ms);
result<> value;
timer
.async_wait(
use_continuable_ignoring(asio_basic_errors_t::operation_aborted))
.next([&](auto&&... args) {
value = result<>::from(std::forward<decltype(args)>(args)...);
});
timer.cancel();
io.run();
ASSERT_TRUE(value.is_value());
}