Initial Commit
This commit is contained in:
132
include/asio/experimental/as_single.hpp
Normal file
132
include/asio/experimental/as_single.hpp
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// experimental/as_single.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_AS_SINGLE_HPP
|
||||
#define ASIO_EXPERIMENTAL_AS_SINGLE_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
|
||||
/// A @ref completion_token adapter used to specify that the completion handler
|
||||
/// arguments should be combined into a single argument.
|
||||
/**
|
||||
* The as_single_t class is used to indicate that any arguments to the
|
||||
* completion handler should be combined and passed as a single argument.
|
||||
* If there is already one argument, that argument is passed as-is. If
|
||||
* there is more than argument, the arguments are first moved into a
|
||||
* @c std::tuple and that tuple is then passed to the completion handler.
|
||||
*/
|
||||
template <typename CompletionToken>
|
||||
class as_single_t
|
||||
{
|
||||
public:
|
||||
/// Tag type used to prevent the "default" constructor from being used for
|
||||
/// conversions.
|
||||
struct default_constructor_tag {};
|
||||
|
||||
/// Default constructor.
|
||||
/**
|
||||
* This constructor is only valid if the underlying completion token is
|
||||
* default constructible and move constructible. The underlying completion
|
||||
* token is itself defaulted as an argument to allow it to capture a source
|
||||
* location.
|
||||
*/
|
||||
constexpr as_single_t(
|
||||
default_constructor_tag = default_constructor_tag(),
|
||||
CompletionToken token = CompletionToken())
|
||||
: token_(static_cast<CompletionToken&&>(token))
|
||||
{
|
||||
}
|
||||
|
||||
/// Constructor.
|
||||
template <typename T>
|
||||
constexpr explicit as_single_t(
|
||||
T&& completion_token)
|
||||
: token_(static_cast<T&&>(completion_token))
|
||||
{
|
||||
}
|
||||
|
||||
/// Adapts an executor to add the @c as_single_t completion token as the
|
||||
/// default.
|
||||
template <typename InnerExecutor>
|
||||
struct executor_with_default : InnerExecutor
|
||||
{
|
||||
/// Specify @c as_single_t as the default completion token type.
|
||||
typedef as_single_t default_completion_token_type;
|
||||
|
||||
/// Construct the adapted executor from the inner executor type.
|
||||
executor_with_default(const InnerExecutor& ex) noexcept
|
||||
: InnerExecutor(ex)
|
||||
{
|
||||
}
|
||||
|
||||
/// Convert the specified executor to the inner executor type, then use
|
||||
/// that to construct the adapted executor.
|
||||
template <typename OtherExecutor>
|
||||
executor_with_default(const OtherExecutor& ex,
|
||||
constraint_t<
|
||||
is_convertible<OtherExecutor, InnerExecutor>::value
|
||||
> = 0) noexcept
|
||||
: InnerExecutor(ex)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// Type alias to adapt an I/O object to use @c as_single_t as its
|
||||
/// default completion token type.
|
||||
template <typename T>
|
||||
using as_default_on_t = typename T::template rebind_executor<
|
||||
executor_with_default<typename T::executor_type>>::other;
|
||||
|
||||
/// Function helper to adapt an I/O object to use @c as_single_t as its
|
||||
/// default completion token type.
|
||||
template <typename T>
|
||||
static typename decay_t<T>::template rebind_executor<
|
||||
executor_with_default<typename decay_t<T>::executor_type>
|
||||
>::other
|
||||
as_default_on(T&& object)
|
||||
{
|
||||
return typename decay_t<T>::template rebind_executor<
|
||||
executor_with_default<typename decay_t<T>::executor_type>
|
||||
>::other(static_cast<T&&>(object));
|
||||
}
|
||||
|
||||
//private:
|
||||
CompletionToken token_;
|
||||
};
|
||||
|
||||
/// Adapt a @ref completion_token to specify that the completion handler
|
||||
/// arguments should be combined into a single argument.
|
||||
template <typename CompletionToken>
|
||||
ASIO_NODISCARD inline
|
||||
constexpr as_single_t<decay_t<CompletionToken>>
|
||||
as_single(CompletionToken&& completion_token)
|
||||
{
|
||||
return as_single_t<decay_t<CompletionToken>>(
|
||||
static_cast<CompletionToken&&>(completion_token));
|
||||
}
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#include "asio/experimental/impl/as_single.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_AS_SINGLE_HPP
|
||||
536
include/asio/experimental/awaitable_operators.hpp
Normal file
536
include/asio/experimental/awaitable_operators.hpp
Normal file
@@ -0,0 +1,536 @@
|
||||
//
|
||||
// experimental/awaitable_operators.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_AWAITABLE_OPERATORS_HPP
|
||||
#define ASIO_EXPERIMENTAL_AWAITABLE_OPERATORS_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <variant>
|
||||
#include "asio/awaitable.hpp"
|
||||
#include "asio/co_spawn.hpp"
|
||||
#include "asio/deferred.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/experimental/parallel_group.hpp"
|
||||
#include "asio/multiple_exceptions.hpp"
|
||||
#include "asio/this_coro.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace awaitable_operators {
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename Executor>
|
||||
awaitable<T, Executor> awaitable_wrap(awaitable<T, Executor> a,
|
||||
constraint_t<is_constructible<T>::value>* = 0)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
template <typename T, typename Executor>
|
||||
awaitable<std::optional<T>, Executor> awaitable_wrap(awaitable<T, Executor> a,
|
||||
constraint_t<!is_constructible<T>::value>* = 0)
|
||||
{
|
||||
co_return std::optional<T>(co_await std::move(a));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& awaitable_unwrap(conditional_t<true, T, void>& r,
|
||||
constraint_t<is_constructible<T>::value>* = 0)
|
||||
{
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& awaitable_unwrap(std::optional<conditional_t<true, T, void>>& r,
|
||||
constraint_t<!is_constructible<T>::value>* = 0)
|
||||
{
|
||||
return *r;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Wait for both operations to succeed.
|
||||
/**
|
||||
* If one operations fails, the other is cancelled as the AND-condition can no
|
||||
* longer be satisfied.
|
||||
*/
|
||||
template <typename Executor>
|
||||
awaitable<void, Executor> operator&&(
|
||||
awaitable<void, Executor> t, awaitable<void, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, ex1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, std::move(t), deferred),
|
||||
co_spawn(ex, std::move(u), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_error(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (ex0 && ex1)
|
||||
throw multiple_exceptions(ex0);
|
||||
if (ex0)
|
||||
std::rethrow_exception(ex0);
|
||||
if (ex1)
|
||||
std::rethrow_exception(ex1);
|
||||
co_return;
|
||||
}
|
||||
|
||||
/// Wait for both operations to succeed.
|
||||
/**
|
||||
* If one operations fails, the other is cancelled as the AND-condition can no
|
||||
* longer be satisfied.
|
||||
*/
|
||||
template <typename U, typename Executor>
|
||||
awaitable<U, Executor> operator&&(
|
||||
awaitable<void, Executor> t, awaitable<U, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, ex1, r1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, std::move(t), deferred),
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_error(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (ex0 && ex1)
|
||||
throw multiple_exceptions(ex0);
|
||||
if (ex0)
|
||||
std::rethrow_exception(ex0);
|
||||
if (ex1)
|
||||
std::rethrow_exception(ex1);
|
||||
co_return std::move(detail::awaitable_unwrap<U>(r1));
|
||||
}
|
||||
|
||||
/// Wait for both operations to succeed.
|
||||
/**
|
||||
* If one operations fails, the other is cancelled as the AND-condition can no
|
||||
* longer be satisfied.
|
||||
*/
|
||||
template <typename T, typename Executor>
|
||||
awaitable<T, Executor> operator&&(
|
||||
awaitable<T, Executor> t, awaitable<void, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, r0, ex1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred),
|
||||
co_spawn(ex, std::move(u), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_error(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (ex0 && ex1)
|
||||
throw multiple_exceptions(ex0);
|
||||
if (ex0)
|
||||
std::rethrow_exception(ex0);
|
||||
if (ex1)
|
||||
std::rethrow_exception(ex1);
|
||||
co_return std::move(detail::awaitable_unwrap<T>(r0));
|
||||
}
|
||||
|
||||
/// Wait for both operations to succeed.
|
||||
/**
|
||||
* If one operations fails, the other is cancelled as the AND-condition can no
|
||||
* longer be satisfied.
|
||||
*/
|
||||
template <typename T, typename U, typename Executor>
|
||||
awaitable<std::tuple<T, U>, Executor> operator&&(
|
||||
awaitable<T, Executor> t, awaitable<U, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, r0, ex1, r1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred),
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_error(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (ex0 && ex1)
|
||||
throw multiple_exceptions(ex0);
|
||||
if (ex0)
|
||||
std::rethrow_exception(ex0);
|
||||
if (ex1)
|
||||
std::rethrow_exception(ex1);
|
||||
co_return std::make_tuple(
|
||||
std::move(detail::awaitable_unwrap<T>(r0)),
|
||||
std::move(detail::awaitable_unwrap<U>(r1)));
|
||||
}
|
||||
|
||||
/// Wait for both operations to succeed.
|
||||
/**
|
||||
* If one operations fails, the other is cancelled as the AND-condition can no
|
||||
* longer be satisfied.
|
||||
*/
|
||||
template <typename... T, typename Executor>
|
||||
awaitable<std::tuple<T..., std::monostate>, Executor> operator&&(
|
||||
awaitable<std::tuple<T...>, Executor> t, awaitable<void, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, r0, ex1, r1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred),
|
||||
co_spawn(ex, std::move(u), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_error(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (ex0 && ex1)
|
||||
throw multiple_exceptions(ex0);
|
||||
if (ex0)
|
||||
std::rethrow_exception(ex0);
|
||||
if (ex1)
|
||||
std::rethrow_exception(ex1);
|
||||
co_return std::move(detail::awaitable_unwrap<std::tuple<T...>>(r0));
|
||||
}
|
||||
|
||||
/// Wait for both operations to succeed.
|
||||
/**
|
||||
* If one operations fails, the other is cancelled as the AND-condition can no
|
||||
* longer be satisfied.
|
||||
*/
|
||||
template <typename... T, typename U, typename Executor>
|
||||
awaitable<std::tuple<T..., U>, Executor> operator&&(
|
||||
awaitable<std::tuple<T...>, Executor> t, awaitable<U, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, r0, ex1, r1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred),
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_error(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (ex0 && ex1)
|
||||
throw multiple_exceptions(ex0);
|
||||
if (ex0)
|
||||
std::rethrow_exception(ex0);
|
||||
if (ex1)
|
||||
std::rethrow_exception(ex1);
|
||||
co_return std::tuple_cat(
|
||||
std::move(detail::awaitable_unwrap<std::tuple<T...>>(r0)),
|
||||
std::make_tuple(std::move(detail::awaitable_unwrap<U>(r1))));
|
||||
}
|
||||
|
||||
/// Wait for one operation to succeed.
|
||||
/**
|
||||
* If one operations succeeds, the other is cancelled as the OR-condition is
|
||||
* already satisfied.
|
||||
*/
|
||||
template <typename Executor>
|
||||
awaitable<std::variant<std::monostate, std::monostate>, Executor> operator||(
|
||||
awaitable<void, Executor> t, awaitable<void, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, ex1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, std::move(t), deferred),
|
||||
co_spawn(ex, std::move(u), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_success(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (order[0] == 0)
|
||||
{
|
||||
if (!ex0)
|
||||
co_return std::variant<std::monostate, std::monostate>{
|
||||
std::in_place_index<0>};
|
||||
if (!ex1)
|
||||
co_return std::variant<std::monostate, std::monostate>{
|
||||
std::in_place_index<1>};
|
||||
throw multiple_exceptions(ex0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ex1)
|
||||
co_return std::variant<std::monostate, std::monostate>{
|
||||
std::in_place_index<1>};
|
||||
if (!ex0)
|
||||
co_return std::variant<std::monostate, std::monostate>{
|
||||
std::in_place_index<0>};
|
||||
throw multiple_exceptions(ex1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for one operation to succeed.
|
||||
/**
|
||||
* If one operations succeeds, the other is cancelled as the OR-condition is
|
||||
* already satisfied.
|
||||
*/
|
||||
template <typename U, typename Executor>
|
||||
awaitable<std::variant<std::monostate, U>, Executor> operator||(
|
||||
awaitable<void, Executor> t, awaitable<U, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, ex1, r1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, std::move(t), deferred),
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_success(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (order[0] == 0)
|
||||
{
|
||||
if (!ex0)
|
||||
co_return std::variant<std::monostate, U>{
|
||||
std::in_place_index<0>};
|
||||
if (!ex1)
|
||||
co_return std::variant<std::monostate, U>{
|
||||
std::in_place_index<1>,
|
||||
std::move(detail::awaitable_unwrap<U>(r1))};
|
||||
throw multiple_exceptions(ex0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ex1)
|
||||
co_return std::variant<std::monostate, U>{
|
||||
std::in_place_index<1>,
|
||||
std::move(detail::awaitable_unwrap<U>(r1))};
|
||||
if (!ex0)
|
||||
co_return std::variant<std::monostate, U>{
|
||||
std::in_place_index<0>};
|
||||
throw multiple_exceptions(ex1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for one operation to succeed.
|
||||
/**
|
||||
* If one operations succeeds, the other is cancelled as the OR-condition is
|
||||
* already satisfied.
|
||||
*/
|
||||
template <typename T, typename Executor>
|
||||
awaitable<std::variant<T, std::monostate>, Executor> operator||(
|
||||
awaitable<T, Executor> t, awaitable<void, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, r0, ex1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred),
|
||||
co_spawn(ex, std::move(u), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_success(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (order[0] == 0)
|
||||
{
|
||||
if (!ex0)
|
||||
co_return std::variant<T, std::monostate>{
|
||||
std::in_place_index<0>,
|
||||
std::move(detail::awaitable_unwrap<T>(r0))};
|
||||
if (!ex1)
|
||||
co_return std::variant<T, std::monostate>{
|
||||
std::in_place_index<1>};
|
||||
throw multiple_exceptions(ex0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ex1)
|
||||
co_return std::variant<T, std::monostate>{
|
||||
std::in_place_index<1>};
|
||||
if (!ex0)
|
||||
co_return std::variant<T, std::monostate>{
|
||||
std::in_place_index<0>,
|
||||
std::move(detail::awaitable_unwrap<T>(r0))};
|
||||
throw multiple_exceptions(ex1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for one operation to succeed.
|
||||
/**
|
||||
* If one operations succeeds, the other is cancelled as the OR-condition is
|
||||
* already satisfied.
|
||||
*/
|
||||
template <typename T, typename U, typename Executor>
|
||||
awaitable<std::variant<T, U>, Executor> operator||(
|
||||
awaitable<T, Executor> t, awaitable<U, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, r0, ex1, r1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred),
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_success(),
|
||||
deferred
|
||||
);
|
||||
|
||||
if (order[0] == 0)
|
||||
{
|
||||
if (!ex0)
|
||||
co_return std::variant<T, U>{
|
||||
std::in_place_index<0>,
|
||||
std::move(detail::awaitable_unwrap<T>(r0))};
|
||||
if (!ex1)
|
||||
co_return std::variant<T, U>{
|
||||
std::in_place_index<1>,
|
||||
std::move(detail::awaitable_unwrap<U>(r1))};
|
||||
throw multiple_exceptions(ex0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ex1)
|
||||
co_return std::variant<T, U>{
|
||||
std::in_place_index<1>,
|
||||
std::move(detail::awaitable_unwrap<U>(r1))};
|
||||
if (!ex0)
|
||||
co_return std::variant<T, U>{
|
||||
std::in_place_index<0>,
|
||||
std::move(detail::awaitable_unwrap<T>(r0))};
|
||||
throw multiple_exceptions(ex1);
|
||||
}
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename... T>
|
||||
struct widen_variant
|
||||
{
|
||||
template <std::size_t I, typename SourceVariant>
|
||||
static std::variant<T...> call(SourceVariant& source)
|
||||
{
|
||||
if (source.index() == I)
|
||||
return std::variant<T...>{
|
||||
std::in_place_index<I>, std::move(std::get<I>(source))};
|
||||
else if constexpr (I + 1 < std::variant_size_v<SourceVariant>)
|
||||
return call<I + 1>(source);
|
||||
else
|
||||
throw std::logic_error("empty variant");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Wait for one operation to succeed.
|
||||
/**
|
||||
* If one operations succeeds, the other is cancelled as the OR-condition is
|
||||
* already satisfied.
|
||||
*/
|
||||
template <typename... T, typename Executor>
|
||||
awaitable<std::variant<T..., std::monostate>, Executor> operator||(
|
||||
awaitable<std::variant<T...>, Executor> t, awaitable<void, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, r0, ex1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred),
|
||||
co_spawn(ex, std::move(u), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_success(),
|
||||
deferred
|
||||
);
|
||||
|
||||
using widen = detail::widen_variant<T..., std::monostate>;
|
||||
if (order[0] == 0)
|
||||
{
|
||||
if (!ex0)
|
||||
co_return widen::template call<0>(
|
||||
detail::awaitable_unwrap<std::variant<T...>>(r0));
|
||||
if (!ex1)
|
||||
co_return std::variant<T..., std::monostate>{
|
||||
std::in_place_index<sizeof...(T)>};
|
||||
throw multiple_exceptions(ex0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ex1)
|
||||
co_return std::variant<T..., std::monostate>{
|
||||
std::in_place_index<sizeof...(T)>};
|
||||
if (!ex0)
|
||||
co_return widen::template call<0>(
|
||||
detail::awaitable_unwrap<std::variant<T...>>(r0));
|
||||
throw multiple_exceptions(ex1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for one operation to succeed.
|
||||
/**
|
||||
* If one operations succeeds, the other is cancelled as the OR-condition is
|
||||
* already satisfied.
|
||||
*/
|
||||
template <typename... T, typename U, typename Executor>
|
||||
awaitable<std::variant<T..., U>, Executor> operator||(
|
||||
awaitable<std::variant<T...>, Executor> t, awaitable<U, Executor> u)
|
||||
{
|
||||
auto ex = co_await this_coro::executor;
|
||||
|
||||
auto [order, ex0, r0, ex1, r1] =
|
||||
co_await make_parallel_group(
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(t)), deferred),
|
||||
co_spawn(ex, detail::awaitable_wrap(std::move(u)), deferred)
|
||||
).async_wait(
|
||||
wait_for_one_success(),
|
||||
deferred
|
||||
);
|
||||
|
||||
using widen = detail::widen_variant<T..., U>;
|
||||
if (order[0] == 0)
|
||||
{
|
||||
if (!ex0)
|
||||
co_return widen::template call<0>(
|
||||
detail::awaitable_unwrap<std::variant<T...>>(r0));
|
||||
if (!ex1)
|
||||
co_return std::variant<T..., U>{
|
||||
std::in_place_index<sizeof...(T)>,
|
||||
std::move(detail::awaitable_unwrap<U>(r1))};
|
||||
throw multiple_exceptions(ex0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ex1)
|
||||
co_return std::variant<T..., U>{
|
||||
std::in_place_index<sizeof...(T)>,
|
||||
std::move(detail::awaitable_unwrap<U>(r1))};
|
||||
if (!ex0)
|
||||
co_return widen::template call<0>(
|
||||
detail::awaitable_unwrap<std::variant<T...>>(r0));
|
||||
throw multiple_exceptions(ex1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace awaitable_operators
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_AWAITABLE_OPERATORS_HPP
|
||||
514
include/asio/experimental/basic_channel.hpp
Normal file
514
include/asio/experimental/basic_channel.hpp
Normal file
@@ -0,0 +1,514 @@
|
||||
//
|
||||
// experimental/basic_channel.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_BASIC_CHANNEL_HPP
|
||||
#define ASIO_EXPERIMENTAL_BASIC_CHANNEL_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/detail/non_const_lvalue.hpp"
|
||||
#include "asio/detail/null_mutex.hpp"
|
||||
#include "asio/execution/executor.hpp"
|
||||
#include "asio/execution_context.hpp"
|
||||
#include "asio/experimental/detail/channel_send_functions.hpp"
|
||||
#include "asio/experimental/detail/channel_service.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// A channel for messages.
|
||||
/**
|
||||
* The basic_channel class template is used for sending messages between
|
||||
* different parts of the same application. A <em>message</em> is defined as a
|
||||
* collection of arguments to be passed to a completion handler, and the set of
|
||||
* messages supported by a channel is specified by its @c Traits and
|
||||
* <tt>Signatures...</tt> template parameters. Messages may be sent and received
|
||||
* using asynchronous or non-blocking synchronous operations.
|
||||
*
|
||||
* Unless customising the traits, applications will typically use the @c
|
||||
* experimental::channel alias template. For example:
|
||||
* @code void send_loop(int i, steady_timer& timer,
|
||||
* channel<void(error_code, int)>& ch)
|
||||
* {
|
||||
* if (i < 10)
|
||||
* {
|
||||
* timer.expires_after(chrono::seconds(1));
|
||||
* timer.async_wait(
|
||||
* [i, &timer, &ch](error_code error)
|
||||
* {
|
||||
* if (!error)
|
||||
* {
|
||||
* ch.async_send(error_code(), i,
|
||||
* [i, &timer, &ch](error_code error)
|
||||
* {
|
||||
* if (!error)
|
||||
* {
|
||||
* send_loop(i + 1, timer, ch);
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* ch.close();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* void receive_loop(channel<void(error_code, int)>& ch)
|
||||
* {
|
||||
* ch.async_receive(
|
||||
* [&ch](error_code error, int i)
|
||||
* {
|
||||
* if (!error)
|
||||
* {
|
||||
* std::cout << "Received " << i << "\n";
|
||||
* receive_loop(ch);
|
||||
* }
|
||||
* });
|
||||
* } @endcode
|
||||
*
|
||||
* @par Thread Safety
|
||||
* @e Distinct @e objects: Safe.@n
|
||||
* @e Shared @e objects: Unsafe.
|
||||
*
|
||||
* The basic_channel class template is not thread-safe, and would typically be
|
||||
* used for passing messages between application code that runs on the same
|
||||
* thread or in the same strand. Consider using @ref basic_concurrent_channel,
|
||||
* and its alias template @c experimental::concurrent_channel, to pass messages
|
||||
* between code running in different threads.
|
||||
*/
|
||||
template <typename Executor, typename Traits, typename... Signatures>
|
||||
class basic_channel
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
: public detail::channel_send_functions<
|
||||
basic_channel<Executor, Traits, Signatures...>,
|
||||
Executor, Signatures...>
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
{
|
||||
private:
|
||||
class initiate_async_send;
|
||||
class initiate_async_receive;
|
||||
typedef detail::channel_service<asio::detail::null_mutex> service_type;
|
||||
typedef typename service_type::template implementation_type<
|
||||
Traits, Signatures...>::payload_type payload_type;
|
||||
|
||||
template <typename... PayloadSignatures,
|
||||
ASIO_COMPLETION_TOKEN_FOR(PayloadSignatures...) CompletionToken>
|
||||
auto do_async_receive(
|
||||
asio::detail::completion_payload<PayloadSignatures...>*,
|
||||
CompletionToken&& token)
|
||||
-> decltype(
|
||||
async_initiate<CompletionToken, PayloadSignatures...>(
|
||||
declval<initiate_async_receive>(), token))
|
||||
{
|
||||
return async_initiate<CompletionToken, PayloadSignatures...>(
|
||||
initiate_async_receive(this), token);
|
||||
}
|
||||
|
||||
public:
|
||||
/// The type of the executor associated with the channel.
|
||||
typedef Executor executor_type;
|
||||
|
||||
/// Rebinds the channel type to another executor.
|
||||
template <typename Executor1>
|
||||
struct rebind_executor
|
||||
{
|
||||
/// The channel type when rebound to the specified executor.
|
||||
typedef basic_channel<Executor1, Traits, Signatures...> other;
|
||||
};
|
||||
|
||||
/// The traits type associated with the channel.
|
||||
typedef typename Traits::template rebind<Signatures...>::other traits_type;
|
||||
|
||||
/// Construct a basic_channel.
|
||||
/**
|
||||
* This constructor creates and channel.
|
||||
*
|
||||
* @param ex The I/O executor that the channel will use, by default, to
|
||||
* dispatch handlers for any asynchronous operations performed on the channel.
|
||||
*
|
||||
* @param max_buffer_size The maximum number of messages that may be buffered
|
||||
* in the channel.
|
||||
*/
|
||||
basic_channel(const executor_type& ex, std::size_t max_buffer_size = 0)
|
||||
: service_(&asio::use_service<service_type>(
|
||||
basic_channel::get_context(ex))),
|
||||
impl_(),
|
||||
executor_(ex)
|
||||
{
|
||||
service_->construct(impl_, max_buffer_size);
|
||||
}
|
||||
|
||||
/// Construct and open a basic_channel.
|
||||
/**
|
||||
* This constructor creates and opens a channel.
|
||||
*
|
||||
* @param context An execution context which provides the I/O executor that
|
||||
* the channel will use, by default, to dispatch handlers for any asynchronous
|
||||
* operations performed on the channel.
|
||||
*
|
||||
* @param max_buffer_size The maximum number of messages that may be buffered
|
||||
* in the channel.
|
||||
*/
|
||||
template <typename ExecutionContext>
|
||||
basic_channel(ExecutionContext& context, std::size_t max_buffer_size = 0,
|
||||
constraint_t<
|
||||
is_convertible<ExecutionContext&, execution_context&>::value,
|
||||
defaulted_constraint
|
||||
> = defaulted_constraint())
|
||||
: service_(&asio::use_service<service_type>(context)),
|
||||
impl_(),
|
||||
executor_(context.get_executor())
|
||||
{
|
||||
service_->construct(impl_, max_buffer_size);
|
||||
}
|
||||
|
||||
/// Move-construct a basic_channel from another.
|
||||
/**
|
||||
* This constructor moves a channel from one object to another.
|
||||
*
|
||||
* @param other The other basic_channel object from which the move will occur.
|
||||
*
|
||||
* @note Following the move, the moved-from object is in the same state as if
|
||||
* constructed using the @c basic_channel(const executor_type&) constructor.
|
||||
*/
|
||||
basic_channel(basic_channel&& other)
|
||||
: service_(other.service_),
|
||||
executor_(other.executor_)
|
||||
{
|
||||
service_->move_construct(impl_, other.impl_);
|
||||
}
|
||||
|
||||
/// Move-assign a basic_channel from another.
|
||||
/**
|
||||
* This assignment operator moves a channel from one object to another.
|
||||
* Cancels any outstanding asynchronous operations associated with the target
|
||||
* object.
|
||||
*
|
||||
* @param other The other basic_channel object from which the move will occur.
|
||||
*
|
||||
* @note Following the move, the moved-from object is in the same state as if
|
||||
* constructed using the @c basic_channel(const executor_type&)
|
||||
* constructor.
|
||||
*/
|
||||
basic_channel& operator=(basic_channel&& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
service_->move_assign(impl_, *other.service_, other.impl_);
|
||||
executor_.~executor_type();
|
||||
new (&executor_) executor_type(other.executor_);
|
||||
service_ = other.service_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// All channels have access to each other's implementations.
|
||||
template <typename, typename, typename...>
|
||||
friend class basic_channel;
|
||||
|
||||
/// Move-construct a basic_channel from another.
|
||||
/**
|
||||
* This constructor moves a channel from one object to another.
|
||||
*
|
||||
* @param other The other basic_channel object from which the move will occur.
|
||||
*
|
||||
* @note Following the move, the moved-from object is in the same state as if
|
||||
* constructed using the @c basic_channel(const executor_type&)
|
||||
* constructor.
|
||||
*/
|
||||
template <typename Executor1>
|
||||
basic_channel(
|
||||
basic_channel<Executor1, Traits, Signatures...>&& other,
|
||||
constraint_t<
|
||||
is_convertible<Executor1, Executor>::value
|
||||
> = 0)
|
||||
: service_(other.service_),
|
||||
executor_(other.executor_)
|
||||
{
|
||||
service_->move_construct(impl_, other.impl_);
|
||||
}
|
||||
|
||||
/// Move-assign a basic_channel from another.
|
||||
/**
|
||||
* This assignment operator moves a channel from one object to another.
|
||||
* Cancels any outstanding asynchronous operations associated with the target
|
||||
* object.
|
||||
*
|
||||
* @param other The other basic_channel object from which the move will
|
||||
* occur.
|
||||
*
|
||||
* @note Following the move, the moved-from object is in the same state as if
|
||||
* constructed using the @c basic_channel(const executor_type&)
|
||||
* constructor.
|
||||
*/
|
||||
template <typename Executor1>
|
||||
constraint_t<
|
||||
is_convertible<Executor1, Executor>::value,
|
||||
basic_channel&
|
||||
> operator=(basic_channel<Executor1, Traits, Signatures...>&& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
service_->move_assign(impl_, *other.service_, other.impl_);
|
||||
executor_.~executor_type();
|
||||
new (&executor_) executor_type(other.executor_);
|
||||
service_ = other.service_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Destructor.
|
||||
~basic_channel()
|
||||
{
|
||||
service_->destroy(impl_);
|
||||
}
|
||||
|
||||
/// Get the executor associated with the object.
|
||||
const executor_type& get_executor() noexcept
|
||||
{
|
||||
return executor_;
|
||||
}
|
||||
|
||||
/// Get the capacity of the channel's buffer.
|
||||
std::size_t capacity() noexcept
|
||||
{
|
||||
return service_->capacity(impl_);
|
||||
}
|
||||
|
||||
/// Determine whether the channel is open.
|
||||
bool is_open() const noexcept
|
||||
{
|
||||
return service_->is_open(impl_);
|
||||
}
|
||||
|
||||
/// Reset the channel to its initial state.
|
||||
void reset()
|
||||
{
|
||||
service_->reset(impl_);
|
||||
}
|
||||
|
||||
/// Close the channel.
|
||||
void close()
|
||||
{
|
||||
service_->close(impl_);
|
||||
}
|
||||
|
||||
/// Cancel all asynchronous operations waiting on the channel.
|
||||
/**
|
||||
* All outstanding send operations will complete with the error
|
||||
* @c asio::experimental::error::channel_cancelled. Outstanding receive
|
||||
* operations complete with the result as determined by the channel traits.
|
||||
*/
|
||||
void cancel()
|
||||
{
|
||||
service_->cancel(impl_);
|
||||
}
|
||||
|
||||
/// Determine whether a message can be received without blocking.
|
||||
bool ready() const noexcept
|
||||
{
|
||||
return service_->ready(impl_);
|
||||
}
|
||||
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
/// Try to send a message without blocking.
|
||||
/**
|
||||
* Fails if the buffer is full and there are no waiting receive operations.
|
||||
*
|
||||
* @returns @c true on success, @c false on failure.
|
||||
*/
|
||||
template <typename... Args>
|
||||
bool try_send(Args&&... args);
|
||||
|
||||
/// Try to send a message without blocking, using dispatch semantics to call
|
||||
/// the receive operation's completion handler.
|
||||
/**
|
||||
* Fails if the buffer is full and there are no waiting receive operations.
|
||||
*
|
||||
* The receive operation's completion handler may be called from inside this
|
||||
* function.
|
||||
*
|
||||
* @returns @c true on success, @c false on failure.
|
||||
*/
|
||||
template <typename... Args>
|
||||
bool try_send_via_dispatch(Args&&... args);
|
||||
|
||||
/// Try to send a number of messages without blocking.
|
||||
/**
|
||||
* @returns The number of messages that were sent.
|
||||
*/
|
||||
template <typename... Args>
|
||||
std::size_t try_send_n(std::size_t count, Args&&... args);
|
||||
|
||||
/// Try to send a number of messages without blocking, using dispatch
|
||||
/// semantics to call the receive operations' completion handlers.
|
||||
/**
|
||||
* The receive operations' completion handlers may be called from inside this
|
||||
* function.
|
||||
*
|
||||
* @returns The number of messages that were sent.
|
||||
*/
|
||||
template <typename... Args>
|
||||
std::size_t try_send_n_via_dispatch(std::size_t count, Args&&... args);
|
||||
|
||||
/// Asynchronously send a message.
|
||||
/**
|
||||
* @par Completion Signature
|
||||
* @code void(asio::error_code) @endcode
|
||||
*/
|
||||
template <typename... Args,
|
||||
ASIO_COMPLETION_TOKEN_FOR(void (asio::error_code))
|
||||
CompletionToken ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
auto async_send(Args&&... args,
|
||||
CompletionToken&& token);
|
||||
|
||||
#endif // defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
/// Try to receive a message without blocking.
|
||||
/**
|
||||
* Fails if the buffer is full and there are no waiting receive operations.
|
||||
*
|
||||
* @returns @c true on success, @c false on failure.
|
||||
*/
|
||||
template <typename Handler>
|
||||
bool try_receive(Handler&& handler)
|
||||
{
|
||||
return service_->try_receive(impl_, static_cast<Handler&&>(handler));
|
||||
}
|
||||
|
||||
/// Asynchronously receive a message.
|
||||
/**
|
||||
* @par Completion Signature
|
||||
* As determined by the <tt>Signatures...</tt> template parameter and the
|
||||
* channel traits.
|
||||
*/
|
||||
template <typename CompletionToken
|
||||
ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
auto async_receive(
|
||||
CompletionToken&& token
|
||||
ASIO_DEFAULT_COMPLETION_TOKEN(Executor))
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
-> decltype(
|
||||
this->do_async_receive(static_cast<payload_type*>(0),
|
||||
static_cast<CompletionToken&&>(token)))
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
{
|
||||
return this->do_async_receive(static_cast<payload_type*>(0),
|
||||
static_cast<CompletionToken&&>(token));
|
||||
}
|
||||
|
||||
private:
|
||||
// Disallow copying and assignment.
|
||||
basic_channel(const basic_channel&) = delete;
|
||||
basic_channel& operator=(const basic_channel&) = delete;
|
||||
|
||||
template <typename, typename, typename...>
|
||||
friend class detail::channel_send_functions;
|
||||
|
||||
// Helper function to get an executor's context.
|
||||
template <typename T>
|
||||
static execution_context& get_context(const T& t,
|
||||
enable_if_t<execution::is_executor<T>::value>* = 0)
|
||||
{
|
||||
return asio::query(t, execution::context);
|
||||
}
|
||||
|
||||
// Helper function to get an executor's context.
|
||||
template <typename T>
|
||||
static execution_context& get_context(const T& t,
|
||||
enable_if_t<!execution::is_executor<T>::value>* = 0)
|
||||
{
|
||||
return t.context();
|
||||
}
|
||||
|
||||
class initiate_async_send
|
||||
{
|
||||
public:
|
||||
typedef Executor executor_type;
|
||||
|
||||
explicit initiate_async_send(basic_channel* self)
|
||||
: self_(self)
|
||||
{
|
||||
}
|
||||
|
||||
const executor_type& get_executor() const noexcept
|
||||
{
|
||||
return self_->get_executor();
|
||||
}
|
||||
|
||||
template <typename SendHandler>
|
||||
void operator()(SendHandler&& handler,
|
||||
payload_type&& payload) const
|
||||
{
|
||||
asio::detail::non_const_lvalue<SendHandler> handler2(handler);
|
||||
self_->service_->async_send(self_->impl_,
|
||||
static_cast<payload_type&&>(payload),
|
||||
handler2.value, self_->get_executor());
|
||||
}
|
||||
|
||||
private:
|
||||
basic_channel* self_;
|
||||
};
|
||||
|
||||
class initiate_async_receive
|
||||
{
|
||||
public:
|
||||
typedef Executor executor_type;
|
||||
|
||||
explicit initiate_async_receive(basic_channel* self)
|
||||
: self_(self)
|
||||
{
|
||||
}
|
||||
|
||||
const executor_type& get_executor() const noexcept
|
||||
{
|
||||
return self_->get_executor();
|
||||
}
|
||||
|
||||
template <typename ReceiveHandler>
|
||||
void operator()(ReceiveHandler&& handler) const
|
||||
{
|
||||
asio::detail::non_const_lvalue<ReceiveHandler> handler2(handler);
|
||||
self_->service_->async_receive(self_->impl_,
|
||||
handler2.value, self_->get_executor());
|
||||
}
|
||||
|
||||
private:
|
||||
basic_channel* self_;
|
||||
};
|
||||
|
||||
// The service associated with the I/O object.
|
||||
service_type* service_;
|
||||
|
||||
// The underlying implementation of the I/O object.
|
||||
typename service_type::template implementation_type<
|
||||
Traits, Signatures...> impl_;
|
||||
|
||||
// The associated executor.
|
||||
Executor executor_;
|
||||
};
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_BASIC_CHANNEL_HPP
|
||||
514
include/asio/experimental/basic_concurrent_channel.hpp
Normal file
514
include/asio/experimental/basic_concurrent_channel.hpp
Normal file
@@ -0,0 +1,514 @@
|
||||
//
|
||||
// experimental/basic_concurrent_channel.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_BASIC_CONCURRENT_CHANNEL_HPP
|
||||
#define ASIO_EXPERIMENTAL_BASIC_CONCURRENT_CHANNEL_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/detail/non_const_lvalue.hpp"
|
||||
#include "asio/detail/mutex.hpp"
|
||||
#include "asio/execution/executor.hpp"
|
||||
#include "asio/execution_context.hpp"
|
||||
#include "asio/experimental/detail/channel_send_functions.hpp"
|
||||
#include "asio/experimental/detail/channel_service.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// A channel for messages.
|
||||
/**
|
||||
* The basic_concurrent_channel class template is used for sending messages
|
||||
* between different parts of the same application. A <em>message</em> is
|
||||
* defined as a collection of arguments to be passed to a completion handler,
|
||||
* and the set of messages supported by a channel is specified by its @c Traits
|
||||
* and <tt>Signatures...</tt> template parameters. Messages may be sent and
|
||||
* received using asynchronous or non-blocking synchronous operations.
|
||||
*
|
||||
* Unless customising the traits, applications will typically use the @c
|
||||
* experimental::concurrent_channel alias template. For example:
|
||||
* @code void send_loop(int i, steady_timer& timer,
|
||||
* concurrent_channel<void(error_code, int)>& ch)
|
||||
* {
|
||||
* if (i < 10)
|
||||
* {
|
||||
* timer.expires_after(chrono::seconds(1));
|
||||
* timer.async_wait(
|
||||
* [i, &timer, &ch](error_code error)
|
||||
* {
|
||||
* if (!error)
|
||||
* {
|
||||
* ch.async_send(error_code(), i,
|
||||
* [i, &timer, &ch](error_code error)
|
||||
* {
|
||||
* if (!error)
|
||||
* {
|
||||
* send_loop(i + 1, timer, ch);
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
* });
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* ch.close();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* void receive_loop(concurent_channel<void(error_code, int)>& ch)
|
||||
* {
|
||||
* ch.async_receive(
|
||||
* [&ch](error_code error, int i)
|
||||
* {
|
||||
* if (!error)
|
||||
* {
|
||||
* std::cout << "Received " << i << "\n";
|
||||
* receive_loop(ch);
|
||||
* }
|
||||
* });
|
||||
* } @endcode
|
||||
*
|
||||
* @par Thread Safety
|
||||
* @e Distinct @e objects: Safe.@n
|
||||
* @e Shared @e objects: Safe.
|
||||
*
|
||||
* The basic_concurrent_channel class template is thread-safe, and would
|
||||
* typically be used for passing messages between application code that run on
|
||||
* different threads. Consider using @ref basic_channel, and its alias template
|
||||
* @c experimental::channel, to pass messages between code running in a single
|
||||
* thread or on the same strand.
|
||||
*/
|
||||
template <typename Executor, typename Traits, typename... Signatures>
|
||||
class basic_concurrent_channel
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
: public detail::channel_send_functions<
|
||||
basic_concurrent_channel<Executor, Traits, Signatures...>,
|
||||
Executor, Signatures...>
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
{
|
||||
private:
|
||||
class initiate_async_send;
|
||||
class initiate_async_receive;
|
||||
typedef detail::channel_service<asio::detail::mutex> service_type;
|
||||
typedef typename service_type::template implementation_type<
|
||||
Traits, Signatures...>::payload_type payload_type;
|
||||
|
||||
template <typename... PayloadSignatures,
|
||||
ASIO_COMPLETION_TOKEN_FOR(PayloadSignatures...) CompletionToken>
|
||||
auto do_async_receive(
|
||||
asio::detail::completion_payload<PayloadSignatures...>*,
|
||||
CompletionToken&& token)
|
||||
-> decltype(
|
||||
async_initiate<CompletionToken, PayloadSignatures...>(
|
||||
declval<initiate_async_receive>(), token))
|
||||
{
|
||||
return async_initiate<CompletionToken, PayloadSignatures...>(
|
||||
initiate_async_receive(this), token);
|
||||
}
|
||||
|
||||
public:
|
||||
/// The type of the executor associated with the channel.
|
||||
typedef Executor executor_type;
|
||||
|
||||
/// Rebinds the channel type to another executor.
|
||||
template <typename Executor1>
|
||||
struct rebind_executor
|
||||
{
|
||||
/// The channel type when rebound to the specified executor.
|
||||
typedef basic_concurrent_channel<Executor1, Traits, Signatures...> other;
|
||||
};
|
||||
|
||||
/// The traits type associated with the channel.
|
||||
typedef typename Traits::template rebind<Signatures...>::other traits_type;
|
||||
|
||||
/// Construct a basic_concurrent_channel.
|
||||
/**
|
||||
* This constructor creates and channel.
|
||||
*
|
||||
* @param ex The I/O executor that the channel will use, by default, to
|
||||
* dispatch handlers for any asynchronous operations performed on the channel.
|
||||
*
|
||||
* @param max_buffer_size The maximum number of messages that may be buffered
|
||||
* in the channel.
|
||||
*/
|
||||
basic_concurrent_channel(const executor_type& ex,
|
||||
std::size_t max_buffer_size = 0)
|
||||
: service_(&asio::use_service<service_type>(
|
||||
basic_concurrent_channel::get_context(ex))),
|
||||
impl_(),
|
||||
executor_(ex)
|
||||
{
|
||||
service_->construct(impl_, max_buffer_size);
|
||||
}
|
||||
|
||||
/// Construct and open a basic_concurrent_channel.
|
||||
/**
|
||||
* This constructor creates and opens a channel.
|
||||
*
|
||||
* @param context An execution context which provides the I/O executor that
|
||||
* the channel will use, by default, to dispatch handlers for any asynchronous
|
||||
* operations performed on the channel.
|
||||
*
|
||||
* @param max_buffer_size The maximum number of messages that may be buffered
|
||||
* in the channel.
|
||||
*/
|
||||
template <typename ExecutionContext>
|
||||
basic_concurrent_channel(ExecutionContext& context,
|
||||
std::size_t max_buffer_size = 0,
|
||||
constraint_t<
|
||||
is_convertible<ExecutionContext&, execution_context&>::value,
|
||||
defaulted_constraint
|
||||
> = defaulted_constraint())
|
||||
: service_(&asio::use_service<service_type>(context)),
|
||||
impl_(),
|
||||
executor_(context.get_executor())
|
||||
{
|
||||
service_->construct(impl_, max_buffer_size);
|
||||
}
|
||||
|
||||
/// Move-construct a basic_concurrent_channel from another.
|
||||
/**
|
||||
* This constructor moves a channel from one object to another.
|
||||
*
|
||||
* @param other The other basic_concurrent_channel object from which the move
|
||||
* will occur.
|
||||
*
|
||||
* @note Following the move, the moved-from object is in the same state as if
|
||||
* constructed using the @c basic_concurrent_channel(const executor_type&)
|
||||
* constructor.
|
||||
*/
|
||||
basic_concurrent_channel(basic_concurrent_channel&& other)
|
||||
: service_(other.service_),
|
||||
executor_(other.executor_)
|
||||
{
|
||||
service_->move_construct(impl_, other.impl_);
|
||||
}
|
||||
|
||||
/// Move-assign a basic_concurrent_channel from another.
|
||||
/**
|
||||
* This assignment operator moves a channel from one object to another.
|
||||
* Cancels any outstanding asynchronous operations associated with the target
|
||||
* object.
|
||||
*
|
||||
* @param other The other basic_concurrent_channel object from which the move
|
||||
* will occur.
|
||||
*
|
||||
* @note Following the move, the moved-from object is in the same state as if
|
||||
* constructed using the @c basic_concurrent_channel(const executor_type&)
|
||||
* constructor.
|
||||
*/
|
||||
basic_concurrent_channel& operator=(basic_concurrent_channel&& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
service_->move_assign(impl_, *other.service_, other.impl_);
|
||||
executor_.~executor_type();
|
||||
new (&executor_) executor_type(other.executor_);
|
||||
service_ = other.service_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// All channels have access to each other's implementations.
|
||||
template <typename, typename, typename...>
|
||||
friend class basic_concurrent_channel;
|
||||
|
||||
/// Move-construct a basic_concurrent_channel from another.
|
||||
/**
|
||||
* This constructor moves a channel from one object to another.
|
||||
*
|
||||
* @param other The other basic_concurrent_channel object from which the move
|
||||
* will occur.
|
||||
*
|
||||
* @note Following the move, the moved-from object is in the same state as if
|
||||
* constructed using the @c basic_concurrent_channel(const executor_type&)
|
||||
* constructor.
|
||||
*/
|
||||
template <typename Executor1>
|
||||
basic_concurrent_channel(
|
||||
basic_concurrent_channel<Executor1, Traits, Signatures...>&& other,
|
||||
constraint_t<
|
||||
is_convertible<Executor1, Executor>::value
|
||||
> = 0)
|
||||
: service_(other.service_),
|
||||
executor_(other.executor_)
|
||||
{
|
||||
service_->move_construct(impl_, other.impl_);
|
||||
}
|
||||
|
||||
/// Move-assign a basic_concurrent_channel from another.
|
||||
/**
|
||||
* This assignment operator moves a channel from one object to another.
|
||||
* Cancels any outstanding asynchronous operations associated with the target
|
||||
* object.
|
||||
*
|
||||
* @param other The other basic_concurrent_channel object from which the move
|
||||
* will occur.
|
||||
*
|
||||
* @note Following the move, the moved-from object is in the same state as if
|
||||
* constructed using the @c basic_concurrent_channel(const executor_type&)
|
||||
* constructor.
|
||||
*/
|
||||
template <typename Executor1>
|
||||
constraint_t<
|
||||
is_convertible<Executor1, Executor>::value,
|
||||
basic_concurrent_channel&
|
||||
> operator=(
|
||||
basic_concurrent_channel<Executor1, Traits, Signatures...>&& other)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
service_->move_assign(impl_, *other.service_, other.impl_);
|
||||
executor_.~executor_type();
|
||||
new (&executor_) executor_type(other.executor_);
|
||||
service_ = other.service_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Destructor.
|
||||
~basic_concurrent_channel()
|
||||
{
|
||||
service_->destroy(impl_);
|
||||
}
|
||||
|
||||
/// Get the executor associated with the object.
|
||||
const executor_type& get_executor() noexcept
|
||||
{
|
||||
return executor_;
|
||||
}
|
||||
|
||||
/// Get the capacity of the channel's buffer.
|
||||
std::size_t capacity() noexcept
|
||||
{
|
||||
return service_->capacity(impl_);
|
||||
}
|
||||
|
||||
/// Determine whether the channel is open.
|
||||
bool is_open() const noexcept
|
||||
{
|
||||
return service_->is_open(impl_);
|
||||
}
|
||||
|
||||
/// Reset the channel to its initial state.
|
||||
void reset()
|
||||
{
|
||||
service_->reset(impl_);
|
||||
}
|
||||
|
||||
/// Close the channel.
|
||||
void close()
|
||||
{
|
||||
service_->close(impl_);
|
||||
}
|
||||
|
||||
/// Cancel all asynchronous operations waiting on the channel.
|
||||
/**
|
||||
* All outstanding send operations will complete with the error
|
||||
* @c asio::experimental::error::channel_cancelled. Outstanding receive
|
||||
* operations complete with the result as determined by the channel traits.
|
||||
*/
|
||||
void cancel()
|
||||
{
|
||||
service_->cancel(impl_);
|
||||
}
|
||||
|
||||
/// Determine whether a message can be received without blocking.
|
||||
bool ready() const noexcept
|
||||
{
|
||||
return service_->ready(impl_);
|
||||
}
|
||||
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
/// Try to send a message without blocking.
|
||||
/**
|
||||
* Fails if the buffer is full and there are no waiting receive operations.
|
||||
*
|
||||
* @returns @c true on success, @c false on failure.
|
||||
*/
|
||||
template <typename... Args>
|
||||
bool try_send(Args&&... args);
|
||||
|
||||
/// Try to send a message without blocking, using dispatch semantics to call
|
||||
/// the receive operation's completion handler.
|
||||
/**
|
||||
* Fails if the buffer is full and there are no waiting receive operations.
|
||||
*
|
||||
* The receive operation's completion handler may be called from inside this
|
||||
* function.
|
||||
*
|
||||
* @returns @c true on success, @c false on failure.
|
||||
*/
|
||||
template <typename... Args>
|
||||
bool try_send_via_dispatch(Args&&... args);
|
||||
|
||||
/// Try to send a number of messages without blocking.
|
||||
/**
|
||||
* @returns The number of messages that were sent.
|
||||
*/
|
||||
template <typename... Args>
|
||||
std::size_t try_send_n(std::size_t count, Args&&... args);
|
||||
|
||||
/// Try to send a number of messages without blocking, using dispatch
|
||||
/// semantics to call the receive operations' completion handlers.
|
||||
/**
|
||||
* The receive operations' completion handlers may be called from inside this
|
||||
* function.
|
||||
*
|
||||
* @returns The number of messages that were sent.
|
||||
*/
|
||||
template <typename... Args>
|
||||
std::size_t try_send_n_via_dispatch(std::size_t count, Args&&... args);
|
||||
|
||||
/// Asynchronously send a message.
|
||||
template <typename... Args,
|
||||
ASIO_COMPLETION_TOKEN_FOR(void (asio::error_code))
|
||||
CompletionToken ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
auto async_send(Args&&... args,
|
||||
CompletionToken&& token);
|
||||
|
||||
#endif // defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
/// Try to receive a message without blocking.
|
||||
/**
|
||||
* Fails if the buffer is full and there are no waiting receive operations.
|
||||
*
|
||||
* @returns @c true on success, @c false on failure.
|
||||
*/
|
||||
template <typename Handler>
|
||||
bool try_receive(Handler&& handler)
|
||||
{
|
||||
return service_->try_receive(impl_, static_cast<Handler&&>(handler));
|
||||
}
|
||||
|
||||
/// Asynchronously receive a message.
|
||||
template <typename CompletionToken
|
||||
ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>
|
||||
auto async_receive(
|
||||
CompletionToken&& token
|
||||
ASIO_DEFAULT_COMPLETION_TOKEN(Executor))
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
-> decltype(
|
||||
this->do_async_receive(static_cast<payload_type*>(0),
|
||||
static_cast<CompletionToken&&>(token)))
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
{
|
||||
return this->do_async_receive(static_cast<payload_type*>(0),
|
||||
static_cast<CompletionToken&&>(token));
|
||||
}
|
||||
|
||||
private:
|
||||
// Disallow copying and assignment.
|
||||
basic_concurrent_channel(
|
||||
const basic_concurrent_channel&) = delete;
|
||||
basic_concurrent_channel& operator=(
|
||||
const basic_concurrent_channel&) = delete;
|
||||
|
||||
template <typename, typename, typename...>
|
||||
friend class detail::channel_send_functions;
|
||||
|
||||
// Helper function to get an executor's context.
|
||||
template <typename T>
|
||||
static execution_context& get_context(const T& t,
|
||||
enable_if_t<execution::is_executor<T>::value>* = 0)
|
||||
{
|
||||
return asio::query(t, execution::context);
|
||||
}
|
||||
|
||||
// Helper function to get an executor's context.
|
||||
template <typename T>
|
||||
static execution_context& get_context(const T& t,
|
||||
enable_if_t<!execution::is_executor<T>::value>* = 0)
|
||||
{
|
||||
return t.context();
|
||||
}
|
||||
|
||||
class initiate_async_send
|
||||
{
|
||||
public:
|
||||
typedef Executor executor_type;
|
||||
|
||||
explicit initiate_async_send(basic_concurrent_channel* self)
|
||||
: self_(self)
|
||||
{
|
||||
}
|
||||
|
||||
const executor_type& get_executor() const noexcept
|
||||
{
|
||||
return self_->get_executor();
|
||||
}
|
||||
|
||||
template <typename SendHandler>
|
||||
void operator()(SendHandler&& handler,
|
||||
payload_type&& payload) const
|
||||
{
|
||||
asio::detail::non_const_lvalue<SendHandler> handler2(handler);
|
||||
self_->service_->async_send(self_->impl_,
|
||||
static_cast<payload_type&&>(payload),
|
||||
handler2.value, self_->get_executor());
|
||||
}
|
||||
|
||||
private:
|
||||
basic_concurrent_channel* self_;
|
||||
};
|
||||
|
||||
class initiate_async_receive
|
||||
{
|
||||
public:
|
||||
typedef Executor executor_type;
|
||||
|
||||
explicit initiate_async_receive(basic_concurrent_channel* self)
|
||||
: self_(self)
|
||||
{
|
||||
}
|
||||
|
||||
const executor_type& get_executor() const noexcept
|
||||
{
|
||||
return self_->get_executor();
|
||||
}
|
||||
|
||||
template <typename ReceiveHandler>
|
||||
void operator()(ReceiveHandler&& handler) const
|
||||
{
|
||||
asio::detail::non_const_lvalue<ReceiveHandler> handler2(handler);
|
||||
self_->service_->async_receive(self_->impl_,
|
||||
handler2.value, self_->get_executor());
|
||||
}
|
||||
|
||||
private:
|
||||
basic_concurrent_channel* self_;
|
||||
};
|
||||
|
||||
// The service associated with the I/O object.
|
||||
service_type* service_;
|
||||
|
||||
// The underlying implementation of the I/O object.
|
||||
typename service_type::template implementation_type<
|
||||
Traits, Signatures...> impl_;
|
||||
|
||||
// The associated executor.
|
||||
Executor executor_;
|
||||
};
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_BASIC_CONCURRENT_CHANNEL_HPP
|
||||
139
include/asio/experimental/cancellation_condition.hpp
Normal file
139
include/asio/experimental/cancellation_condition.hpp
Normal file
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// experimental/cancellation_condition.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_CANCELLATION_CONDITION_HPP
|
||||
#define ASIO_EXPERIMENTAL_CANCELLATION_CONDITION_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/cancellation_type.hpp"
|
||||
#include "asio/disposition.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
|
||||
/// Wait for all operations to complete.
|
||||
class wait_for_all
|
||||
{
|
||||
public:
|
||||
template <typename... Args>
|
||||
constexpr cancellation_type_t operator()(Args&&...) const noexcept
|
||||
{
|
||||
return cancellation_type::none;
|
||||
}
|
||||
};
|
||||
|
||||
/// Wait until an operation completes, then cancel the others.
|
||||
class wait_for_one
|
||||
{
|
||||
public:
|
||||
constexpr explicit wait_for_one(
|
||||
cancellation_type_t cancel_type = cancellation_type::all)
|
||||
: cancel_type_(cancel_type)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
constexpr cancellation_type_t operator()(Args&&...) const noexcept
|
||||
{
|
||||
return cancel_type_;
|
||||
}
|
||||
|
||||
private:
|
||||
cancellation_type_t cancel_type_;
|
||||
};
|
||||
|
||||
/// Wait until an operation completes without an error, then cancel the others.
|
||||
/**
|
||||
* If no operation completes without an error, waits for completion of all
|
||||
* operations.
|
||||
*/
|
||||
class wait_for_one_success
|
||||
{
|
||||
public:
|
||||
constexpr explicit wait_for_one_success(
|
||||
cancellation_type_t cancel_type = cancellation_type::all)
|
||||
: cancel_type_(cancel_type)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr cancellation_type_t
|
||||
operator()() const noexcept
|
||||
{
|
||||
return cancel_type_;
|
||||
}
|
||||
|
||||
template <typename E, typename... Args>
|
||||
constexpr constraint_t<!is_disposition<E>::value, cancellation_type_t>
|
||||
operator()(const E&, Args&&...) const noexcept
|
||||
{
|
||||
return cancel_type_;
|
||||
}
|
||||
|
||||
template <typename E, typename... Args>
|
||||
constexpr constraint_t<is_disposition<E>::value, cancellation_type_t>
|
||||
operator()(const E& e, Args&&...) const noexcept
|
||||
{
|
||||
return e != no_error ? cancellation_type::none : cancel_type_;
|
||||
}
|
||||
|
||||
private:
|
||||
cancellation_type_t cancel_type_;
|
||||
};
|
||||
|
||||
/// Wait until an operation completes with an error, then cancel the others.
|
||||
/**
|
||||
* If no operation completes with an error, waits for completion of all
|
||||
* operations.
|
||||
*/
|
||||
class wait_for_one_error
|
||||
{
|
||||
public:
|
||||
constexpr explicit wait_for_one_error(
|
||||
cancellation_type_t cancel_type = cancellation_type::all)
|
||||
: cancel_type_(cancel_type)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr cancellation_type_t operator()() const noexcept
|
||||
{
|
||||
return cancellation_type::none;
|
||||
}
|
||||
|
||||
template <typename E, typename... Args>
|
||||
constexpr constraint_t<!is_disposition<E>::value, cancellation_type_t>
|
||||
operator()(const E&, Args&&...) const noexcept
|
||||
{
|
||||
return cancellation_type::none;
|
||||
}
|
||||
|
||||
template <typename E, typename... Args>
|
||||
constexpr constraint_t<is_disposition<E>::value, cancellation_type_t>
|
||||
operator()(const E& e, Args&&...) const noexcept
|
||||
{
|
||||
return e != no_error ? cancel_type_ : cancellation_type::none;
|
||||
}
|
||||
|
||||
private:
|
||||
cancellation_type_t cancel_type_;
|
||||
};
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_CANCELLATION_CONDITION_HPP
|
||||
76
include/asio/experimental/channel.hpp
Normal file
76
include/asio/experimental/channel.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// experimental/channel.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_CHANNEL_HPP
|
||||
#define ASIO_EXPERIMENTAL_CHANNEL_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/any_io_executor.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/execution/executor.hpp"
|
||||
#include "asio/is_executor.hpp"
|
||||
#include "asio/experimental/basic_channel.hpp"
|
||||
#include "asio/experimental/channel_traits.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename ExecutorOrSignature, typename = void>
|
||||
struct channel_type
|
||||
{
|
||||
template <typename... Signatures>
|
||||
struct inner
|
||||
{
|
||||
typedef basic_channel<any_io_executor, channel_traits<>,
|
||||
ExecutorOrSignature, Signatures...> type;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename ExecutorOrSignature>
|
||||
struct channel_type<ExecutorOrSignature,
|
||||
enable_if_t<
|
||||
is_executor<ExecutorOrSignature>::value
|
||||
|| execution::is_executor<ExecutorOrSignature>::value
|
||||
>>
|
||||
{
|
||||
template <typename... Signatures>
|
||||
struct inner
|
||||
{
|
||||
typedef basic_channel<ExecutorOrSignature,
|
||||
channel_traits<>, Signatures...> type;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Template type alias for common use of channel.
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
template <typename ExecutorOrSignature, typename... Signatures>
|
||||
using channel = basic_channel<
|
||||
specified_executor_or_any_io_executor, channel_traits<>, signatures...>;
|
||||
#else // defined(GENERATING_DOCUMENTATION)
|
||||
template <typename ExecutorOrSignature, typename... Signatures>
|
||||
using channel = typename detail::channel_type<
|
||||
ExecutorOrSignature>::template inner<Signatures...>::type;
|
||||
#endif // defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_CHANNEL_HPP
|
||||
82
include/asio/experimental/channel_error.hpp
Normal file
82
include/asio/experimental/channel_error.hpp
Normal file
@@ -0,0 +1,82 @@
|
||||
//
|
||||
// experimental/channel_error.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_CHANNEL_ERROR_HPP
|
||||
#define ASIO_EXPERIMENTAL_CHANNEL_ERROR_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/error_code.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace error {
|
||||
|
||||
enum channel_errors
|
||||
{
|
||||
/// The channel was closed.
|
||||
channel_closed = 1,
|
||||
|
||||
/// The channel was cancelled.
|
||||
channel_cancelled = 2
|
||||
};
|
||||
|
||||
extern ASIO_DECL
|
||||
const asio::error_category& get_channel_category();
|
||||
|
||||
static const asio::error_category&
|
||||
channel_category ASIO_UNUSED_VARIABLE
|
||||
= asio::experimental::error::get_channel_category();
|
||||
|
||||
} // namespace error
|
||||
namespace channel_errc {
|
||||
// Simulates a scoped enum.
|
||||
using error::channel_closed;
|
||||
using error::channel_cancelled;
|
||||
} // namespace channel_errc
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
namespace std {
|
||||
|
||||
template<> struct is_error_code_enum<
|
||||
asio::experimental::error::channel_errors>
|
||||
{
|
||||
static const bool value = true;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace error {
|
||||
|
||||
inline asio::error_code make_error_code(channel_errors e)
|
||||
{
|
||||
return asio::error_code(
|
||||
static_cast<int>(e), get_channel_category());
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#if defined(ASIO_HEADER_ONLY)
|
||||
# include "asio/experimental/impl/channel_error.ipp"
|
||||
#endif // defined(ASIO_HEADER_ONLY)
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_CHANNEL_ERROR_HPP
|
||||
301
include/asio/experimental/channel_traits.hpp
Normal file
301
include/asio/experimental/channel_traits.hpp
Normal file
@@ -0,0 +1,301 @@
|
||||
//
|
||||
// experimental/channel_traits.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_CHANNEL_TRAITS_HPP
|
||||
#define ASIO_EXPERIMENTAL_CHANNEL_TRAITS_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <deque>
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/error.hpp"
|
||||
#include "asio/experimental/channel_error.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
template <typename... Signatures>
|
||||
struct channel_traits
|
||||
{
|
||||
/// Rebind the traits to a new set of signatures.
|
||||
/**
|
||||
* This nested structure must have a single nested type @c other that
|
||||
* aliases a traits type with the specified set of signatures.
|
||||
*/
|
||||
template <typename... NewSignatures>
|
||||
struct rebind
|
||||
{
|
||||
typedef user_defined other;
|
||||
};
|
||||
|
||||
/// Determine the container for the specified elements.
|
||||
/**
|
||||
* This nested structure must have a single nested type @c other that
|
||||
* aliases a container type for the specified element type.
|
||||
*/
|
||||
template <typename Element>
|
||||
struct container
|
||||
{
|
||||
typedef user_defined type;
|
||||
};
|
||||
|
||||
/// The signature of a channel cancellation notification.
|
||||
typedef void receive_cancelled_signature(...);
|
||||
|
||||
/// Invoke the specified handler with a cancellation notification.
|
||||
template <typename F>
|
||||
static void invoke_receive_cancelled(F f);
|
||||
|
||||
/// The signature of a channel closed notification.
|
||||
typedef void receive_closed_signature(...);
|
||||
|
||||
/// Invoke the specified handler with a closed notification.
|
||||
template <typename F>
|
||||
static void invoke_receive_closed(F f);
|
||||
};
|
||||
|
||||
#else // defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
/// Traits used for customising channel behaviour.
|
||||
template <typename... Signatures>
|
||||
struct channel_traits
|
||||
{
|
||||
template <typename... NewSignatures>
|
||||
struct rebind
|
||||
{
|
||||
typedef channel_traits<NewSignatures...> other;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename R>
|
||||
struct channel_traits<R(asio::error_code)>
|
||||
{
|
||||
template <typename... NewSignatures>
|
||||
struct rebind
|
||||
{
|
||||
typedef channel_traits<NewSignatures...> other;
|
||||
};
|
||||
|
||||
template <typename Element>
|
||||
struct container
|
||||
{
|
||||
typedef std::deque<Element> type;
|
||||
};
|
||||
|
||||
typedef R receive_cancelled_signature(asio::error_code);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_cancelled(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_cancelled;
|
||||
static_cast<F&&>(f)(e);
|
||||
}
|
||||
|
||||
typedef R receive_closed_signature(asio::error_code);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_closed(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_closed;
|
||||
static_cast<F&&>(f)(e);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename R, typename... Args, typename... Signatures>
|
||||
struct channel_traits<R(asio::error_code, Args...), Signatures...>
|
||||
{
|
||||
template <typename... NewSignatures>
|
||||
struct rebind
|
||||
{
|
||||
typedef channel_traits<NewSignatures...> other;
|
||||
};
|
||||
|
||||
template <typename Element>
|
||||
struct container
|
||||
{
|
||||
typedef std::deque<Element> type;
|
||||
};
|
||||
|
||||
typedef R receive_cancelled_signature(asio::error_code, Args...);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_cancelled(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_cancelled;
|
||||
static_cast<F&&>(f)(e, decay_t<Args>()...);
|
||||
}
|
||||
|
||||
typedef R receive_closed_signature(asio::error_code, Args...);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_closed(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_closed;
|
||||
static_cast<F&&>(f)(e, decay_t<Args>()...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename R>
|
||||
struct channel_traits<R(std::exception_ptr)>
|
||||
{
|
||||
template <typename... NewSignatures>
|
||||
struct rebind
|
||||
{
|
||||
typedef channel_traits<NewSignatures...> other;
|
||||
};
|
||||
|
||||
template <typename Element>
|
||||
struct container
|
||||
{
|
||||
typedef std::deque<Element> type;
|
||||
};
|
||||
|
||||
typedef R receive_cancelled_signature(std::exception_ptr);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_cancelled(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_cancelled;
|
||||
static_cast<F&&>(f)(
|
||||
std::make_exception_ptr(asio::system_error(e)));
|
||||
}
|
||||
|
||||
typedef R receive_closed_signature(std::exception_ptr);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_closed(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_closed;
|
||||
static_cast<F&&>(f)(
|
||||
std::make_exception_ptr(asio::system_error(e)));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename R, typename... Args, typename... Signatures>
|
||||
struct channel_traits<R(std::exception_ptr, Args...), Signatures...>
|
||||
{
|
||||
template <typename... NewSignatures>
|
||||
struct rebind
|
||||
{
|
||||
typedef channel_traits<NewSignatures...> other;
|
||||
};
|
||||
|
||||
template <typename Element>
|
||||
struct container
|
||||
{
|
||||
typedef std::deque<Element> type;
|
||||
};
|
||||
|
||||
typedef R receive_cancelled_signature(std::exception_ptr, Args...);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_cancelled(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_cancelled;
|
||||
static_cast<F&&>(f)(
|
||||
std::make_exception_ptr(asio::system_error(e)),
|
||||
decay_t<Args>()...);
|
||||
}
|
||||
|
||||
typedef R receive_closed_signature(std::exception_ptr, Args...);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_closed(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_closed;
|
||||
static_cast<F&&>(f)(
|
||||
std::make_exception_ptr(asio::system_error(e)),
|
||||
decay_t<Args>()...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename R>
|
||||
struct channel_traits<R()>
|
||||
{
|
||||
template <typename... NewSignatures>
|
||||
struct rebind
|
||||
{
|
||||
typedef channel_traits<NewSignatures...> other;
|
||||
};
|
||||
|
||||
template <typename Element>
|
||||
struct container
|
||||
{
|
||||
typedef std::deque<Element> type;
|
||||
};
|
||||
|
||||
typedef R receive_cancelled_signature(asio::error_code);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_cancelled(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_cancelled;
|
||||
static_cast<F&&>(f)(e);
|
||||
}
|
||||
|
||||
typedef R receive_closed_signature(asio::error_code);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_closed(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_closed;
|
||||
static_cast<F&&>(f)(e);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename R, typename T>
|
||||
struct channel_traits<R(T)>
|
||||
{
|
||||
template <typename... NewSignatures>
|
||||
struct rebind
|
||||
{
|
||||
typedef channel_traits<NewSignatures...> other;
|
||||
};
|
||||
|
||||
template <typename Element>
|
||||
struct container
|
||||
{
|
||||
typedef std::deque<Element> type;
|
||||
};
|
||||
|
||||
typedef R receive_cancelled_signature(asio::error_code);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_cancelled(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_cancelled;
|
||||
static_cast<F&&>(f)(e);
|
||||
}
|
||||
|
||||
typedef R receive_closed_signature(asio::error_code);
|
||||
|
||||
template <typename F>
|
||||
static void invoke_receive_closed(F f)
|
||||
{
|
||||
const asio::error_code e = error::channel_closed;
|
||||
static_cast<F&&>(f)(e);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_CHANNEL_TRAITS_HPP
|
||||
33
include/asio/experimental/co_composed.hpp
Normal file
33
include/asio/experimental/co_composed.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// experimental/co_composed.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_CO_COMPOSED_HPP
|
||||
#define ASIO_EXPERIMENTAL_CO_COMPOSED_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/co_composed.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
|
||||
using asio::co_composed;
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_CO_COMPOSED_HPP
|
||||
187
include/asio/experimental/co_spawn.hpp
Normal file
187
include/asio/experimental/co_spawn.hpp
Normal file
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// experimental/co_spawn.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#ifndef ASIO_EXPERIMENTAL_CO_SPAWN_HPP
|
||||
#define ASIO_EXPERIMENTAL_CO_SPAWN_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <utility>
|
||||
#include "asio/compose.hpp"
|
||||
#include "asio/deferred.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/experimental/coro.hpp"
|
||||
#include "asio/prepend.hpp"
|
||||
#include "asio/redirect_error.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename U, typename Executor>
|
||||
struct coro_spawn_op
|
||||
{
|
||||
coro<T, U, Executor> c;
|
||||
|
||||
void operator()(auto& self)
|
||||
{
|
||||
auto op = c.async_resume(deferred);
|
||||
std::move(op)((prepend)(std::move(self), 0));
|
||||
}
|
||||
|
||||
void operator()(auto& self, int, auto... res)
|
||||
{
|
||||
self.complete(std::move(res)...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Spawn a resumable coroutine.
|
||||
/**
|
||||
* This function spawns the coroutine for execution on its executor. It binds
|
||||
* the lifetime of the coroutine to the executor.
|
||||
*
|
||||
* @param c The coroutine
|
||||
*
|
||||
* @param token The completion token
|
||||
*
|
||||
* @returns Implementation defined
|
||||
*/
|
||||
template <typename T, typename Executor, typename CompletionToken>
|
||||
ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken, void(std::exception_ptr, T))
|
||||
co_spawn(coro<void, T, Executor> c, CompletionToken&& token)
|
||||
{
|
||||
auto exec = c.get_executor();
|
||||
return async_compose<CompletionToken, void(std::exception_ptr, T)>(
|
||||
detail::coro_spawn_op<void, T, Executor>{std::move(c)},
|
||||
token, exec);
|
||||
}
|
||||
|
||||
/// Spawn a resumable coroutine.
|
||||
/**
|
||||
* This function spawns the coroutine for execution on its executor. It binds
|
||||
* the lifetime of the coroutine to the executor.
|
||||
*
|
||||
* @param c The coroutine
|
||||
*
|
||||
* @param token The completion token
|
||||
*
|
||||
* @returns Implementation defined
|
||||
*/
|
||||
template <typename T, typename Executor, typename CompletionToken>
|
||||
ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken, void(std::exception_ptr, T))
|
||||
co_spawn(coro<void(), T, Executor> c, CompletionToken&& token)
|
||||
{
|
||||
auto exec = c.get_executor();
|
||||
return async_compose<CompletionToken, void(std::exception_ptr, T)>(
|
||||
detail::coro_spawn_op<void(), T, Executor>{std::move(c)},
|
||||
token, exec);
|
||||
}
|
||||
|
||||
/// Spawn a resumable coroutine.
|
||||
/**
|
||||
* This function spawns the coroutine for execution on its executor. It binds
|
||||
* the lifetime of the coroutine to the executor.
|
||||
*
|
||||
* @param c The coroutine
|
||||
*
|
||||
* @param token The completion token
|
||||
*
|
||||
* @returns Implementation defined
|
||||
*/
|
||||
template <typename T, typename Executor, typename CompletionToken>
|
||||
ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(T))
|
||||
co_spawn(coro<void() noexcept, T, Executor> c, CompletionToken&& token)
|
||||
{
|
||||
auto exec = c.get_executor();
|
||||
return async_compose<CompletionToken, void(T)>(
|
||||
detail::coro_spawn_op<void() noexcept, T, Executor>{std::move(c)},
|
||||
token, exec);
|
||||
}
|
||||
|
||||
/// Spawn a resumable coroutine.
|
||||
/**
|
||||
* This function spawns the coroutine for execution on its executor. It binds
|
||||
* the lifetime of the coroutine to the executor.
|
||||
*
|
||||
* @param c The coroutine
|
||||
*
|
||||
* @param token The completion token
|
||||
*
|
||||
* @returns Implementation defined
|
||||
*/
|
||||
template <typename Executor, typename CompletionToken>
|
||||
ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken, void(std::exception_ptr))
|
||||
co_spawn(coro<void, void, Executor> c, CompletionToken&& token)
|
||||
{
|
||||
auto exec = c.get_executor();
|
||||
return async_compose<CompletionToken, void(std::exception_ptr)>(
|
||||
detail::coro_spawn_op<void, void, Executor>{std::move(c)},
|
||||
token, exec);
|
||||
}
|
||||
|
||||
/// Spawn a resumable coroutine.
|
||||
/**
|
||||
* This function spawns the coroutine for execution on its executor. It binds
|
||||
* the lifetime of the coroutine to the executor.
|
||||
*
|
||||
* @param c The coroutine
|
||||
*
|
||||
* @param token The completion token
|
||||
*
|
||||
* @returns Implementation defined
|
||||
*/
|
||||
template <typename Executor, typename CompletionToken>
|
||||
ASIO_INITFN_AUTO_RESULT_TYPE(
|
||||
CompletionToken, void(std::exception_ptr))
|
||||
co_spawn(coro<void(), void, Executor> c, CompletionToken&& token)
|
||||
{
|
||||
auto exec = c.get_executor();
|
||||
return async_compose<CompletionToken, void(std::exception_ptr)>(
|
||||
detail::coro_spawn_op<void(), void, Executor>{std::move(c)},
|
||||
token, exec);
|
||||
}
|
||||
|
||||
/// Spawn a resumable coroutine.
|
||||
/**
|
||||
* This function spawns the coroutine for execution on its executor. It binds
|
||||
* the lifetime of the coroutine to the executor.
|
||||
*
|
||||
* @param c The coroutine
|
||||
*
|
||||
* @param token The completion token
|
||||
*
|
||||
* @returns Implementation defined
|
||||
*/
|
||||
template <typename Executor, typename CompletionToken>
|
||||
ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void())
|
||||
co_spawn(coro<void() noexcept, void, Executor> c, CompletionToken&& token)
|
||||
{
|
||||
auto exec = c.get_executor();
|
||||
return async_compose<CompletionToken, void()>(
|
||||
detail::coro_spawn_op<void() noexcept, void, Executor>{std::move(c)},
|
||||
token, exec);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif //ASIO_EXPERIMENTAL_CO_SPAWN_HPP
|
||||
76
include/asio/experimental/concurrent_channel.hpp
Normal file
76
include/asio/experimental/concurrent_channel.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// experimental/concurrent_channel.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_CONCURRENT_CHANNEL_HPP
|
||||
#define ASIO_EXPERIMENTAL_CONCURRENT_CHANNEL_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/any_io_executor.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/execution/executor.hpp"
|
||||
#include "asio/is_executor.hpp"
|
||||
#include "asio/experimental/basic_concurrent_channel.hpp"
|
||||
#include "asio/experimental/channel_traits.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename ExecutorOrSignature, typename = void>
|
||||
struct concurrent_channel_type
|
||||
{
|
||||
template <typename... Signatures>
|
||||
struct inner
|
||||
{
|
||||
typedef basic_concurrent_channel<any_io_executor, channel_traits<>,
|
||||
ExecutorOrSignature, Signatures...> type;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename ExecutorOrSignature>
|
||||
struct concurrent_channel_type<ExecutorOrSignature,
|
||||
enable_if_t<
|
||||
is_executor<ExecutorOrSignature>::value
|
||||
|| execution::is_executor<ExecutorOrSignature>::value
|
||||
>>
|
||||
{
|
||||
template <typename... Signatures>
|
||||
struct inner
|
||||
{
|
||||
typedef basic_concurrent_channel<ExecutorOrSignature,
|
||||
channel_traits<>, Signatures...> type;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Template type alias for common use of channel.
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
template <typename ExecutorOrSignature, typename... Signatures>
|
||||
using concurrent_channel = basic_concurrent_channel<
|
||||
specified_executor_or_any_io_executor, channel_traits<>, signatures...>;
|
||||
#else // defined(GENERATING_DOCUMENTATION)
|
||||
template <typename ExecutorOrSignature, typename... Signatures>
|
||||
using concurrent_channel = typename detail::concurrent_channel_type<
|
||||
ExecutorOrSignature>::template inner<Signatures...>::type;
|
||||
#endif // defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_CONCURRENT_CHANNEL_HPP
|
||||
293
include/asio/experimental/coro.hpp
Normal file
293
include/asio/experimental/coro.hpp
Normal file
@@ -0,0 +1,293 @@
|
||||
//
|
||||
// experimental/coro.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_CORO_HPP
|
||||
#define ASIO_EXPERIMENTAL_CORO_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/dispatch.hpp"
|
||||
#include "asio/error.hpp"
|
||||
#include "asio/error_code.hpp"
|
||||
#include "asio/experimental/coro_traits.hpp"
|
||||
#include "asio/experimental/detail/coro_promise_allocator.hpp"
|
||||
#include "asio/experimental/detail/partial_promise.hpp"
|
||||
#include "asio/post.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename Signature, typename Return,
|
||||
typename Executor, typename Allocator>
|
||||
struct coro_promise;
|
||||
|
||||
template <typename T, typename Coroutine>
|
||||
struct coro_with_arg;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// The main type of a resumable coroutine.
|
||||
/**
|
||||
* Template parameter @c Yield specifies type or signature used by co_yield,
|
||||
* @c Return specifies the type used for co_return, and @c Executor specifies
|
||||
* the underlying executor type.
|
||||
*/
|
||||
template <typename Yield = void, typename Return = void,
|
||||
typename Executor = any_io_executor,
|
||||
typename Allocator = std::allocator<void>>
|
||||
struct coro
|
||||
{
|
||||
/// The traits of the coroutine. See asio::experimental::coro_traits
|
||||
/// for details.
|
||||
using traits = coro_traits<Yield, Return, Executor>;
|
||||
|
||||
/// The value that can be passed into a symmetrical cororoutine. @c void if
|
||||
/// asymmetrical.
|
||||
using input_type = typename traits::input_type;
|
||||
|
||||
/// The type that can be passed out through a co_yield.
|
||||
using yield_type = typename traits::yield_type;
|
||||
|
||||
/// The type that can be passed out through a co_return.
|
||||
using return_type = typename traits::return_type;
|
||||
|
||||
/// The type received by a co_await or async_resume. Its a combination of
|
||||
/// yield and return.
|
||||
using result_type = typename traits::result_type;
|
||||
|
||||
/// The signature used by the async_resume.
|
||||
using signature_type = typename traits::signature_type;
|
||||
|
||||
/// Whether or not the coroutine is noexcept.
|
||||
constexpr static bool is_noexcept = traits::is_noexcept;
|
||||
|
||||
/// The error type of the coroutine. Void for noexcept
|
||||
using error_type = typename traits::error_type;
|
||||
|
||||
/// Completion handler type used by async_resume.
|
||||
using completion_handler = typename traits::completion_handler;
|
||||
|
||||
/// The internal promise-type of the coroutine.
|
||||
using promise_type = detail::coro_promise<Yield, Return, Executor, Allocator>;
|
||||
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
template <typename T, typename Coroutine>
|
||||
friend struct detail::coro_with_arg;
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
/// The executor type.
|
||||
using executor_type = Executor;
|
||||
|
||||
/// The allocator type.
|
||||
using allocator_type = Allocator;
|
||||
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
friend struct detail::coro_promise<Yield, Return, Executor, Allocator>;
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
/// The default constructor, gives an invalid coroutine.
|
||||
coro() = default;
|
||||
|
||||
/// Move constructor.
|
||||
coro(coro&& lhs) noexcept
|
||||
: coro_(std::exchange(lhs.coro_, nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
coro(const coro&) = delete;
|
||||
|
||||
/// Move assignment.
|
||||
coro& operator=(coro&& lhs) noexcept
|
||||
{
|
||||
std::swap(coro_, lhs.coro_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
coro& operator=(const coro&) = delete;
|
||||
|
||||
/// Destructor. Destroys the coroutine, if it holds a valid one.
|
||||
/**
|
||||
* @note This does not cancel an active coroutine. Destructing a resumable
|
||||
* coroutine, i.e. one with a call to async_resume that has not completed, is
|
||||
* undefined behaviour.
|
||||
*/
|
||||
~coro()
|
||||
{
|
||||
if (coro_ != nullptr)
|
||||
{
|
||||
struct destroyer
|
||||
{
|
||||
detail::coroutine_handle<promise_type> handle;
|
||||
|
||||
destroyer(const detail::coroutine_handle<promise_type>& handle)
|
||||
: handle(handle)
|
||||
{ }
|
||||
|
||||
destroyer(destroyer&& lhs)
|
||||
: handle(std::exchange(lhs.handle, nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
destroyer(const destroyer&) = delete;
|
||||
|
||||
void operator()() {}
|
||||
|
||||
~destroyer()
|
||||
{
|
||||
if (handle)
|
||||
handle.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
auto handle =
|
||||
detail::coroutine_handle<promise_type>::from_promise(*coro_);
|
||||
if (handle)
|
||||
asio::dispatch(coro_->get_executor(), destroyer{handle});
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the used executor.
|
||||
executor_type get_executor() const
|
||||
{
|
||||
if (coro_)
|
||||
return coro_->get_executor();
|
||||
|
||||
if constexpr (std::is_default_constructible_v<Executor>)
|
||||
return Executor{};
|
||||
else
|
||||
throw std::logic_error("Coroutine has no executor");
|
||||
}
|
||||
|
||||
/// Get the used allocator.
|
||||
allocator_type get_allocator() const
|
||||
{
|
||||
if (coro_)
|
||||
return coro_->get_allocator();
|
||||
|
||||
if constexpr (std::is_default_constructible_v<Allocator>)
|
||||
return Allocator{};
|
||||
else
|
||||
throw std::logic_error(
|
||||
"Coroutine has no available allocator without a constructed promise");
|
||||
}
|
||||
|
||||
/// Resume the coroutine.
|
||||
/**
|
||||
* @param token The completion token of the async resume.
|
||||
*
|
||||
* @attention Calling an invalid coroutine with a noexcept signature is
|
||||
* undefined behaviour.
|
||||
*
|
||||
* @note This overload is only available for coroutines without an input
|
||||
* value.
|
||||
*/
|
||||
template <typename CompletionToken>
|
||||
requires std::is_void_v<input_type>
|
||||
auto async_resume(CompletionToken&& token) &
|
||||
{
|
||||
return async_initiate<CompletionToken,
|
||||
typename traits::completion_handler>(
|
||||
initiate_async_resume(this), token);
|
||||
}
|
||||
|
||||
/// Resume the coroutine.
|
||||
/**
|
||||
* @param token The completion token of the async resume.
|
||||
*
|
||||
* @attention Calling an invalid coroutine with a noexcept signature is
|
||||
* undefined behaviour.
|
||||
*
|
||||
* @note This overload is only available for coroutines with an input value.
|
||||
*/
|
||||
template <typename CompletionToken, detail::convertible_to<input_type> T>
|
||||
auto async_resume(T&& ip, CompletionToken&& token) &
|
||||
{
|
||||
return async_initiate<CompletionToken,
|
||||
typename traits::completion_handler>(
|
||||
initiate_async_resume(this), token, std::forward<T>(ip));
|
||||
}
|
||||
|
||||
/// Operator used for coroutines without input value.
|
||||
auto operator co_await() requires (std::is_void_v<input_type>)
|
||||
{
|
||||
return awaitable_t{*this};
|
||||
}
|
||||
|
||||
/// Operator used for coroutines with input value.
|
||||
/**
|
||||
* @param ip The input value
|
||||
*
|
||||
* @returns An awaitable handle.
|
||||
*
|
||||
* @code
|
||||
* coro<void> push_values(coro<double(int)> c)
|
||||
* {
|
||||
* std::optional<double> res = co_await c(42);
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
template <detail::convertible_to<input_type> T>
|
||||
auto operator()(T&& ip)
|
||||
{
|
||||
return detail::coro_with_arg<std::decay_t<T>, coro>{
|
||||
std::forward<T>(ip), *this};
|
||||
}
|
||||
|
||||
/// Check whether the coroutine is open, i.e. can be resumed.
|
||||
bool is_open() const
|
||||
{
|
||||
if (coro_)
|
||||
{
|
||||
auto handle =
|
||||
detail::coroutine_handle<promise_type>::from_promise(*coro_);
|
||||
return handle && !handle.done();
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Check whether the coroutine is open, i.e. can be resumed.
|
||||
explicit operator bool() const { return is_open(); }
|
||||
|
||||
private:
|
||||
struct awaitable_t;
|
||||
|
||||
struct initiate_async_resume;
|
||||
|
||||
explicit coro(promise_type* const cr) : coro_(cr) {}
|
||||
|
||||
promise_type* coro_{nullptr};
|
||||
};
|
||||
|
||||
/// A generator is a coro that returns void and yields value.
|
||||
template<typename T, typename Executor = asio::any_io_executor,
|
||||
typename Allocator = std::allocator<void>>
|
||||
using generator = coro<T, void, Executor, Allocator>;
|
||||
|
||||
/// A task is a coro that does not yield values
|
||||
template<typename T, typename Executor = asio::any_io_executor,
|
||||
typename Allocator = std::allocator<void>>
|
||||
using task = coro<void(), T, Executor, Allocator>;
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#include "asio/experimental/impl/coro.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_CORO_HPP
|
||||
228
include/asio/experimental/coro_traits.hpp
Normal file
228
include/asio/experimental/coro_traits.hpp
Normal file
@@ -0,0 +1,228 @@
|
||||
//
|
||||
// experimental/detail/coro_traits.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_CORO_TRAITS_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_CORO_TRAITS_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
#include "asio/any_io_executor.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <class From, class To>
|
||||
concept convertible_to = std::is_convertible_v<From, To>;
|
||||
|
||||
template <typename T>
|
||||
concept decays_to_executor = execution::executor<std::decay_t<T>>;
|
||||
|
||||
template <typename T, typename Executor = any_io_executor>
|
||||
concept execution_context = requires (T& t)
|
||||
{
|
||||
{t.get_executor()} -> convertible_to<Executor>;
|
||||
};
|
||||
|
||||
template <typename Yield, typename Return>
|
||||
struct coro_result
|
||||
{
|
||||
using type = std::variant<Yield, Return>;
|
||||
};
|
||||
|
||||
template <typename Yield>
|
||||
struct coro_result<Yield, void>
|
||||
{
|
||||
using type = std::optional<Yield>;
|
||||
};
|
||||
|
||||
template <typename Return>
|
||||
struct coro_result<void, Return>
|
||||
{
|
||||
using type = Return;
|
||||
};
|
||||
|
||||
template <typename YieldReturn>
|
||||
struct coro_result<YieldReturn, YieldReturn>
|
||||
{
|
||||
using type = YieldReturn;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct coro_result<void, void>
|
||||
{
|
||||
using type = void;
|
||||
};
|
||||
|
||||
template <typename Yield, typename Return>
|
||||
using coro_result_t = typename coro_result<Yield, Return>::type;
|
||||
|
||||
template <typename Result, bool IsNoexcept>
|
||||
struct coro_handler;
|
||||
|
||||
template <>
|
||||
struct coro_handler<void, false>
|
||||
{
|
||||
using type = void(std::exception_ptr);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct coro_handler<void, true>
|
||||
{
|
||||
using type = void();
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct coro_handler<T, false>
|
||||
{
|
||||
using type = void(std::exception_ptr, T);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct coro_handler<T, true>
|
||||
{
|
||||
using type = void(T);
|
||||
};
|
||||
|
||||
template <typename Result, bool IsNoexcept>
|
||||
using coro_handler_t = typename coro_handler<Result, IsNoexcept>::type;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
/// The traits describing the resumable coroutine behaviour.
|
||||
/**
|
||||
* Template parameter @c Yield specifies type or signature used by co_yield,
|
||||
* @c Return specifies the type used for co_return, and @c Executor specifies
|
||||
* the underlying executor type.
|
||||
*/
|
||||
template <typename Yield, typename Return, typename Executor>
|
||||
struct coro_traits
|
||||
{
|
||||
/// The value that can be passed into a symmetrical cororoutine. @c void if
|
||||
/// asymmetrical.
|
||||
using input_type = argument_dependent;
|
||||
|
||||
/// The type that can be passed out through a co_yield.
|
||||
using yield_type = argument_dependent;
|
||||
|
||||
/// The type that can be passed out through a co_return.
|
||||
using return_type = argument_dependent;
|
||||
|
||||
/// The type received by a co_await or async_resume. It's a combination of
|
||||
/// yield and return.
|
||||
using result_type = argument_dependent;
|
||||
|
||||
/// The signature used by the async_resume.
|
||||
using signature_type = argument_dependent;
|
||||
|
||||
/// Whether or not the coroutine is noexcept.
|
||||
constexpr static bool is_noexcept = argument_dependent;
|
||||
|
||||
/// The error type of the coroutine. @c void for noexcept.
|
||||
using error_type = argument_dependent;
|
||||
|
||||
/// Completion handler type used by async_resume.
|
||||
using completion_handler = argument_dependent;
|
||||
};
|
||||
|
||||
#else // defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
template <typename Yield, typename Return, typename Executor>
|
||||
struct coro_traits
|
||||
{
|
||||
using input_type = void;
|
||||
using yield_type = Yield;
|
||||
using return_type = Return;
|
||||
using result_type = detail::coro_result_t<yield_type, return_type>;
|
||||
using signature_type = result_type();
|
||||
constexpr static bool is_noexcept = false;
|
||||
using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
|
||||
using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
|
||||
};
|
||||
|
||||
template <typename T, typename Return, typename Executor>
|
||||
struct coro_traits<T(), Return, Executor>
|
||||
{
|
||||
using input_type = void;
|
||||
using yield_type = T;
|
||||
using return_type = Return;
|
||||
using result_type = detail::coro_result_t<yield_type, return_type>;
|
||||
using signature_type = result_type();
|
||||
constexpr static bool is_noexcept = false;
|
||||
using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
|
||||
using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
|
||||
};
|
||||
|
||||
template <typename T, typename Return, typename Executor>
|
||||
struct coro_traits<T() noexcept, Return, Executor>
|
||||
{
|
||||
using input_type = void;
|
||||
using yield_type = T;
|
||||
using return_type = Return;
|
||||
using result_type = detail::coro_result_t<yield_type, return_type>;
|
||||
using signature_type = result_type();
|
||||
constexpr static bool is_noexcept = true;
|
||||
using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
|
||||
using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
|
||||
};
|
||||
|
||||
template <typename T, typename U, typename Return, typename Executor>
|
||||
struct coro_traits<T(U), Return, Executor>
|
||||
{
|
||||
using input_type = U;
|
||||
using yield_type = T;
|
||||
using return_type = Return;
|
||||
using result_type = detail::coro_result_t<yield_type, return_type>;
|
||||
using signature_type = result_type(input_type);
|
||||
constexpr static bool is_noexcept = false;
|
||||
using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
|
||||
using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
|
||||
};
|
||||
|
||||
template <typename T, typename U, typename Return, typename Executor>
|
||||
struct coro_traits<T(U) noexcept, Return, Executor>
|
||||
{
|
||||
using input_type = U;
|
||||
using yield_type = T;
|
||||
using return_type = Return;
|
||||
using result_type = detail::coro_result_t<yield_type, return_type>;
|
||||
using signature_type = result_type(input_type);
|
||||
constexpr static bool is_noexcept = true;
|
||||
using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
|
||||
using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
|
||||
};
|
||||
|
||||
template <typename Executor>
|
||||
struct coro_traits<void() noexcept, void, Executor>
|
||||
{
|
||||
using input_type = void;
|
||||
using yield_type = void;
|
||||
using return_type = void;
|
||||
using result_type = detail::coro_result_t<yield_type, return_type>;
|
||||
using signature_type = result_type(input_type);
|
||||
constexpr static bool is_noexcept = true;
|
||||
using error_type = std::conditional_t<is_noexcept, void, std::exception_ptr>;
|
||||
using completion_handler = detail::coro_handler_t<result_type, is_noexcept>;
|
||||
};
|
||||
|
||||
#endif // defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_CORO_TRAITS_HPP
|
||||
361
include/asio/experimental/detail/channel_operation.hpp
Normal file
361
include/asio/experimental/detail/channel_operation.hpp
Normal file
@@ -0,0 +1,361 @@
|
||||
//
|
||||
// experimental/detail/channel_operation.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_CHANNEL_OPERATION_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_CHANNEL_OPERATION_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/associated_allocator.hpp"
|
||||
#include "asio/associated_executor.hpp"
|
||||
#include "asio/associated_immediate_executor.hpp"
|
||||
#include "asio/detail/initiate_post.hpp"
|
||||
#include "asio/detail/initiate_dispatch.hpp"
|
||||
#include "asio/detail/op_queue.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/execution/executor.hpp"
|
||||
#include "asio/execution/outstanding_work.hpp"
|
||||
#include "asio/executor_work_guard.hpp"
|
||||
#include "asio/prefer.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
// Base class for all channel operations. A function pointer is used instead of
|
||||
// virtual functions to avoid the associated overhead.
|
||||
class channel_operation ASIO_INHERIT_TRACKED_HANDLER
|
||||
{
|
||||
public:
|
||||
template <typename Executor, typename = void, typename = void>
|
||||
class handler_work_base;
|
||||
|
||||
template <typename Handler, typename IoExecutor, typename = void>
|
||||
class handler_work;
|
||||
|
||||
void destroy()
|
||||
{
|
||||
func_(this, destroy_op, 0);
|
||||
}
|
||||
|
||||
protected:
|
||||
enum action
|
||||
{
|
||||
destroy_op = 0,
|
||||
immediate_op = 1,
|
||||
post_op = 2,
|
||||
dispatch_op = 3,
|
||||
cancel_op = 4,
|
||||
close_op = 5
|
||||
};
|
||||
|
||||
typedef void (*func_type)(channel_operation*, action, void*);
|
||||
|
||||
channel_operation(func_type func)
|
||||
: next_(0),
|
||||
func_(func),
|
||||
cancellation_key_(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Prevents deletion through this type.
|
||||
~channel_operation()
|
||||
{
|
||||
}
|
||||
|
||||
friend class asio::detail::op_queue_access;
|
||||
channel_operation* next_;
|
||||
func_type func_;
|
||||
|
||||
public:
|
||||
// The operation key used for targeted cancellation.
|
||||
void* cancellation_key_;
|
||||
};
|
||||
|
||||
template <typename Executor, typename, typename>
|
||||
class channel_operation::handler_work_base
|
||||
{
|
||||
public:
|
||||
typedef decay_t<
|
||||
prefer_result_t<Executor,
|
||||
execution::outstanding_work_t::tracked_t
|
||||
>
|
||||
> executor_type;
|
||||
|
||||
handler_work_base(int, const Executor& ex)
|
||||
: executor_(asio::prefer(ex, execution::outstanding_work.tracked))
|
||||
{
|
||||
}
|
||||
|
||||
const executor_type& get_executor() const noexcept
|
||||
{
|
||||
return executor_;
|
||||
}
|
||||
|
||||
template <typename IoExecutor, typename Function, typename Handler>
|
||||
void post(const IoExecutor& io_exec, Function& function, Handler&)
|
||||
{
|
||||
(asio::detail::initiate_post_with_executor<IoExecutor>(io_exec))(
|
||||
static_cast<Function&&>(function));
|
||||
}
|
||||
|
||||
template <typename Function, typename Handler>
|
||||
void dispatch(Function& function, Handler& handler)
|
||||
{
|
||||
associated_allocator_t<Handler> allocator =
|
||||
(get_associated_allocator)(handler);
|
||||
|
||||
asio::prefer(executor_,
|
||||
execution::allocator(allocator)
|
||||
).execute(static_cast<Function&&>(function));
|
||||
}
|
||||
|
||||
private:
|
||||
executor_type executor_;
|
||||
};
|
||||
|
||||
template <typename Executor>
|
||||
class channel_operation::handler_work_base<Executor,
|
||||
enable_if_t<
|
||||
execution::is_executor<Executor>::value
|
||||
>,
|
||||
enable_if_t<
|
||||
can_require<Executor, execution::blocking_t::never_t>::value
|
||||
>
|
||||
>
|
||||
{
|
||||
public:
|
||||
typedef decay_t<
|
||||
prefer_result_t<Executor,
|
||||
execution::outstanding_work_t::tracked_t
|
||||
>
|
||||
> executor_type;
|
||||
|
||||
handler_work_base(int, const Executor& ex)
|
||||
: executor_(asio::prefer(ex, execution::outstanding_work.tracked))
|
||||
{
|
||||
}
|
||||
|
||||
const executor_type& get_executor() const noexcept
|
||||
{
|
||||
return executor_;
|
||||
}
|
||||
|
||||
template <typename IoExecutor, typename Function, typename Handler>
|
||||
void post(const IoExecutor&, Function& function, Handler& handler)
|
||||
{
|
||||
associated_allocator_t<Handler> allocator =
|
||||
(get_associated_allocator)(handler);
|
||||
|
||||
asio::prefer(
|
||||
asio::require(executor_, execution::blocking.never),
|
||||
execution::allocator(allocator)
|
||||
).execute(static_cast<Function&&>(function));
|
||||
}
|
||||
|
||||
template <typename Function, typename Handler>
|
||||
void dispatch(Function& function, Handler& handler)
|
||||
{
|
||||
associated_allocator_t<Handler> allocator =
|
||||
(get_associated_allocator)(handler);
|
||||
|
||||
asio::prefer(executor_,
|
||||
execution::allocator(allocator)
|
||||
).execute(static_cast<Function&&>(function));
|
||||
}
|
||||
|
||||
private:
|
||||
executor_type executor_;
|
||||
};
|
||||
|
||||
#if !defined(ASIO_NO_TS_EXECUTORS)
|
||||
|
||||
template <typename Executor>
|
||||
class channel_operation::handler_work_base<Executor,
|
||||
enable_if_t<
|
||||
!execution::is_executor<Executor>::value
|
||||
>
|
||||
>
|
||||
{
|
||||
public:
|
||||
typedef Executor executor_type;
|
||||
|
||||
handler_work_base(int, const Executor& ex)
|
||||
: work_(ex)
|
||||
{
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept
|
||||
{
|
||||
return work_.get_executor();
|
||||
}
|
||||
|
||||
template <typename IoExecutor, typename Function, typename Handler>
|
||||
void post(const IoExecutor&, Function& function, Handler& handler)
|
||||
{
|
||||
associated_allocator_t<Handler> allocator =
|
||||
(get_associated_allocator)(handler);
|
||||
|
||||
work_.get_executor().post(
|
||||
static_cast<Function&&>(function), allocator);
|
||||
}
|
||||
|
||||
template <typename Function, typename Handler>
|
||||
void dispatch(Function& function, Handler& handler)
|
||||
{
|
||||
associated_allocator_t<Handler> allocator =
|
||||
(get_associated_allocator)(handler);
|
||||
|
||||
work_.get_executor().dispatch(
|
||||
static_cast<Function&&>(function), allocator);
|
||||
}
|
||||
|
||||
private:
|
||||
executor_work_guard<Executor> work_;
|
||||
};
|
||||
|
||||
#endif // !defined(ASIO_NO_TS_EXECUTORS)
|
||||
|
||||
template <typename Handler, typename IoExecutor, typename>
|
||||
class channel_operation::handler_work :
|
||||
channel_operation::handler_work_base<IoExecutor>,
|
||||
channel_operation::handler_work_base<
|
||||
associated_executor_t<Handler, IoExecutor>, IoExecutor>
|
||||
{
|
||||
public:
|
||||
typedef channel_operation::handler_work_base<IoExecutor> base1_type;
|
||||
|
||||
typedef channel_operation::handler_work_base<
|
||||
associated_executor_t<Handler, IoExecutor>, IoExecutor>
|
||||
base2_type;
|
||||
|
||||
handler_work(Handler& handler, const IoExecutor& io_ex) noexcept
|
||||
: base1_type(0, io_ex),
|
||||
base2_type(0, (get_associated_executor)(handler, io_ex))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void post(Function& function, Handler& handler)
|
||||
{
|
||||
base2_type::post(base1_type::get_executor(), function, handler);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void dispatch(Function& function, Handler& handler)
|
||||
{
|
||||
base2_type::dispatch(function, handler);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void immediate(Function& function, Handler& handler, ...)
|
||||
{
|
||||
typedef associated_immediate_executor_t<Handler,
|
||||
typename base1_type::executor_type> immediate_ex_type;
|
||||
|
||||
immediate_ex_type immediate_ex = (get_associated_immediate_executor)(
|
||||
handler, base1_type::get_executor());
|
||||
|
||||
(asio::detail::initiate_dispatch_with_executor<immediate_ex_type>(
|
||||
immediate_ex))(static_cast<Function&&>(function));
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void immediate(Function& function, Handler&,
|
||||
enable_if_t<
|
||||
is_same<
|
||||
typename associated_immediate_executor<
|
||||
conditional_t<false, Function, Handler>,
|
||||
typename base1_type::executor_type>::
|
||||
asio_associated_immediate_executor_is_unspecialised,
|
||||
void
|
||||
>::value
|
||||
>*)
|
||||
{
|
||||
(asio::detail::initiate_post_with_executor<
|
||||
typename base1_type::executor_type>(
|
||||
base1_type::get_executor()))(
|
||||
static_cast<Function&&>(function));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Handler, typename IoExecutor>
|
||||
class channel_operation::handler_work<
|
||||
Handler, IoExecutor,
|
||||
enable_if_t<
|
||||
is_same<
|
||||
typename associated_executor<Handler,
|
||||
IoExecutor>::asio_associated_executor_is_unspecialised,
|
||||
void
|
||||
>::value
|
||||
>
|
||||
> : handler_work_base<IoExecutor>
|
||||
{
|
||||
public:
|
||||
typedef channel_operation::handler_work_base<IoExecutor> base1_type;
|
||||
|
||||
handler_work(Handler&, const IoExecutor& io_ex) noexcept
|
||||
: base1_type(0, io_ex)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void post(Function& function, Handler& handler)
|
||||
{
|
||||
base1_type::post(base1_type::get_executor(), function, handler);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void dispatch(Function& function, Handler& handler)
|
||||
{
|
||||
base1_type::dispatch(function, handler);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void immediate(Function& function, Handler& handler, ...)
|
||||
{
|
||||
typedef associated_immediate_executor_t<Handler,
|
||||
typename base1_type::executor_type> immediate_ex_type;
|
||||
|
||||
immediate_ex_type immediate_ex = (get_associated_immediate_executor)(
|
||||
handler, base1_type::get_executor());
|
||||
|
||||
(asio::detail::initiate_dispatch_with_executor<immediate_ex_type>(
|
||||
immediate_ex))(static_cast<Function&&>(function));
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void immediate(Function& function, Handler& handler,
|
||||
enable_if_t<
|
||||
is_same<
|
||||
typename associated_immediate_executor<
|
||||
conditional_t<false, Function, Handler>,
|
||||
typename base1_type::executor_type>::
|
||||
asio_associated_immediate_executor_is_unspecialised,
|
||||
void
|
||||
>::value
|
||||
>*)
|
||||
{
|
||||
base1_type::post(base1_type::get_executor(), function, handler);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_CHANNEL_OPERATION_HPP
|
||||
126
include/asio/experimental/detail/channel_receive_op.hpp
Normal file
126
include/asio/experimental/detail/channel_receive_op.hpp
Normal file
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// experimental/detail/channel_receive_op.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_CHANNEL_RECEIVE_OP_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_CHANNEL_RECEIVE_OP_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/detail/bind_handler.hpp"
|
||||
#include "asio/detail/completion_handler.hpp"
|
||||
#include "asio/detail/handler_alloc_helpers.hpp"
|
||||
#include "asio/error.hpp"
|
||||
#include "asio/experimental/detail/channel_operation.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename Payload>
|
||||
class channel_receive : public channel_operation
|
||||
{
|
||||
public:
|
||||
void immediate(Payload payload)
|
||||
{
|
||||
func_(this, immediate_op, &payload);
|
||||
}
|
||||
|
||||
void post(Payload payload)
|
||||
{
|
||||
func_(this, post_op, &payload);
|
||||
}
|
||||
|
||||
void dispatch(Payload payload)
|
||||
{
|
||||
func_(this, dispatch_op, &payload);
|
||||
}
|
||||
|
||||
protected:
|
||||
channel_receive(func_type func)
|
||||
: channel_operation(func)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Payload, typename Handler, typename IoExecutor>
|
||||
class channel_receive_op : public channel_receive<Payload>
|
||||
{
|
||||
public:
|
||||
ASIO_DEFINE_HANDLER_PTR(channel_receive_op);
|
||||
|
||||
template <typename... Args>
|
||||
channel_receive_op(Handler& handler, const IoExecutor& io_ex)
|
||||
: channel_receive<Payload>(&channel_receive_op::do_action),
|
||||
handler_(static_cast<Handler&&>(handler)),
|
||||
work_(handler_, io_ex)
|
||||
{
|
||||
}
|
||||
|
||||
static void do_action(channel_operation* base,
|
||||
channel_operation::action a, void* v)
|
||||
{
|
||||
// Take ownership of the operation object.
|
||||
channel_receive_op* o(static_cast<channel_receive_op*>(base));
|
||||
ptr p = { asio::detail::addressof(o->handler_), o, o };
|
||||
|
||||
ASIO_HANDLER_COMPLETION((*o));
|
||||
|
||||
// Take ownership of the operation's outstanding work.
|
||||
channel_operation::handler_work<Handler, IoExecutor> w(
|
||||
static_cast<channel_operation::handler_work<Handler, IoExecutor>&&>(
|
||||
o->work_));
|
||||
|
||||
// Make a copy of the handler so that the memory can be deallocated before
|
||||
// the handler is posted. Even if we're not about to post the handler, a
|
||||
// sub-object of the handler may be the true owner of the memory associated
|
||||
// with the handler. Consequently, a local copy of the handler is required
|
||||
// to ensure that any owning sub-object remains valid until after we have
|
||||
// deallocated the memory here.
|
||||
if (a != channel_operation::destroy_op)
|
||||
{
|
||||
Payload* payload = static_cast<Payload*>(v);
|
||||
asio::detail::completion_payload_handler<Payload, Handler> handler(
|
||||
static_cast<Payload&&>(*payload), o->handler_);
|
||||
p.h = asio::detail::addressof(handler.handler_);
|
||||
p.reset();
|
||||
ASIO_HANDLER_INVOCATION_BEGIN(());
|
||||
if (a == channel_operation::immediate_op)
|
||||
w.immediate(handler, handler.handler_, 0);
|
||||
else if (a == channel_operation::dispatch_op)
|
||||
w.dispatch(handler, handler.handler_);
|
||||
else
|
||||
w.post(handler, handler.handler_);
|
||||
ASIO_HANDLER_INVOCATION_END;
|
||||
}
|
||||
else
|
||||
{
|
||||
asio::detail::binder0<Handler> handler(o->handler_);
|
||||
p.h = asio::detail::addressof(handler.handler_);
|
||||
p.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Handler handler_;
|
||||
channel_operation::handler_work<Handler, IoExecutor> work_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_CHANNEL_RECEIVE_OP_HPP
|
||||
200
include/asio/experimental/detail/channel_send_functions.hpp
Normal file
200
include/asio/experimental/detail/channel_send_functions.hpp
Normal file
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// experimental/detail/channel_send_functions.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_CHANNEL_SEND_FUNCTIONS_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_CHANNEL_SEND_FUNCTIONS_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/async_result.hpp"
|
||||
#include "asio/detail/completion_message.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/error_code.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename Derived, typename Executor, typename... Signatures>
|
||||
class channel_send_functions;
|
||||
|
||||
template <typename Derived, typename Executor, typename R, typename... Args>
|
||||
class channel_send_functions<Derived, Executor, R(Args...)>
|
||||
{
|
||||
public:
|
||||
template <typename... Args2>
|
||||
enable_if_t<
|
||||
is_constructible<asio::detail::completion_message<R(Args...)>,
|
||||
int, Args2...>::value,
|
||||
bool
|
||||
> try_send(Args2&&... args)
|
||||
{
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return self->service_->template try_send<message_type>(
|
||||
self->impl_, false, static_cast<Args2&&>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args2>
|
||||
enable_if_t<
|
||||
is_constructible<asio::detail::completion_message<R(Args...)>,
|
||||
int, Args2...>::value,
|
||||
bool
|
||||
> try_send_via_dispatch(Args2&&... args)
|
||||
{
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return self->service_->template try_send<message_type>(
|
||||
self->impl_, true, static_cast<Args2&&>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args2>
|
||||
enable_if_t<
|
||||
is_constructible<asio::detail::completion_message<R(Args...)>,
|
||||
int, Args2...>::value,
|
||||
std::size_t
|
||||
> try_send_n(std::size_t count, Args2&&... args)
|
||||
{
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return self->service_->template try_send_n<message_type>(
|
||||
self->impl_, count, false, static_cast<Args2&&>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args2>
|
||||
enable_if_t<
|
||||
is_constructible<asio::detail::completion_message<R(Args...)>,
|
||||
int, Args2...>::value,
|
||||
std::size_t
|
||||
> try_send_n_via_dispatch(std::size_t count, Args2&&... args)
|
||||
{
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return self->service_->template try_send_n<message_type>(
|
||||
self->impl_, count, true, static_cast<Args2&&>(args)...);
|
||||
}
|
||||
|
||||
template <
|
||||
ASIO_COMPLETION_TOKEN_FOR(void (asio::error_code))
|
||||
CompletionToken ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(Executor)>
|
||||
auto async_send(Args... args,
|
||||
CompletionToken&& token
|
||||
ASIO_DEFAULT_COMPLETION_TOKEN(Executor))
|
||||
-> decltype(
|
||||
async_initiate<CompletionToken, void (asio::error_code)>(
|
||||
declval<typename conditional_t<false, CompletionToken,
|
||||
Derived>::initiate_async_send>(), token,
|
||||
declval<typename conditional_t<false, CompletionToken,
|
||||
Derived>::payload_type>()))
|
||||
{
|
||||
typedef typename Derived::payload_type payload_type;
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return async_initiate<CompletionToken, void (asio::error_code)>(
|
||||
typename Derived::initiate_async_send(self), token,
|
||||
payload_type(message_type(0, static_cast<Args&&>(args)...)));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Derived, typename Executor,
|
||||
typename R, typename... Args, typename... Signatures>
|
||||
class channel_send_functions<Derived, Executor, R(Args...), Signatures...> :
|
||||
public channel_send_functions<Derived, Executor, Signatures...>
|
||||
{
|
||||
public:
|
||||
using channel_send_functions<Derived, Executor, Signatures...>::try_send;
|
||||
using channel_send_functions<Derived, Executor, Signatures...>::async_send;
|
||||
|
||||
template <typename... Args2>
|
||||
enable_if_t<
|
||||
is_constructible<asio::detail::completion_message<R(Args...)>,
|
||||
int, Args2...>::value,
|
||||
bool
|
||||
> try_send(Args2&&... args)
|
||||
{
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return self->service_->template try_send<message_type>(
|
||||
self->impl_, false, static_cast<Args2&&>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args2>
|
||||
enable_if_t<
|
||||
is_constructible<asio::detail::completion_message<R(Args...)>,
|
||||
int, Args2...>::value,
|
||||
bool
|
||||
> try_send_via_dispatch(Args2&&... args)
|
||||
{
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return self->service_->template try_send<message_type>(
|
||||
self->impl_, true, static_cast<Args2&&>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args2>
|
||||
enable_if_t<
|
||||
is_constructible<asio::detail::completion_message<R(Args...)>,
|
||||
int, Args2...>::value,
|
||||
std::size_t
|
||||
> try_send_n(std::size_t count, Args2&&... args)
|
||||
{
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return self->service_->template try_send_n<message_type>(
|
||||
self->impl_, count, false, static_cast<Args2&&>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args2>
|
||||
enable_if_t<
|
||||
is_constructible<asio::detail::completion_message<R(Args...)>,
|
||||
int, Args2...>::value,
|
||||
std::size_t
|
||||
> try_send_n_via_dispatch(std::size_t count, Args2&&... args)
|
||||
{
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return self->service_->template try_send_n<message_type>(
|
||||
self->impl_, count, true, static_cast<Args2&&>(args)...);
|
||||
}
|
||||
|
||||
template <
|
||||
ASIO_COMPLETION_TOKEN_FOR(void (asio::error_code))
|
||||
CompletionToken ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(Executor)>
|
||||
auto async_send(Args... args,
|
||||
CompletionToken&& token
|
||||
ASIO_DEFAULT_COMPLETION_TOKEN(Executor))
|
||||
-> decltype(
|
||||
async_initiate<CompletionToken, void (asio::error_code)>(
|
||||
declval<typename conditional_t<false, CompletionToken,
|
||||
Derived>::initiate_async_send>(), token,
|
||||
declval<typename conditional_t<false, CompletionToken,
|
||||
Derived>::payload_type>()))
|
||||
{
|
||||
typedef typename Derived::payload_type payload_type;
|
||||
typedef asio::detail::completion_message<R(Args...)> message_type;
|
||||
Derived* self = static_cast<Derived*>(this);
|
||||
return async_initiate<CompletionToken, void (asio::error_code)>(
|
||||
typename Derived::initiate_async_send(self), token,
|
||||
payload_type(message_type(0, static_cast<Args&&>(args)...)));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_CHANNEL_SEND_FUNCTIONS_HPP
|
||||
147
include/asio/experimental/detail/channel_send_op.hpp
Normal file
147
include/asio/experimental/detail/channel_send_op.hpp
Normal file
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// experimental/detail/channel_send_op.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_CHANNEL_SEND_OP_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_CHANNEL_SEND_OP_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/detail/bind_handler.hpp"
|
||||
#include "asio/detail/handler_alloc_helpers.hpp"
|
||||
#include "asio/error.hpp"
|
||||
#include "asio/experimental/channel_error.hpp"
|
||||
#include "asio/experimental/detail/channel_operation.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename Payload>
|
||||
class channel_send : public channel_operation
|
||||
{
|
||||
public:
|
||||
Payload get_payload()
|
||||
{
|
||||
return static_cast<Payload&&>(payload_);
|
||||
}
|
||||
|
||||
void immediate()
|
||||
{
|
||||
func_(this, immediate_op, 0);
|
||||
}
|
||||
|
||||
void post()
|
||||
{
|
||||
func_(this, post_op, 0);
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
func_(this, cancel_op, 0);
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
func_(this, close_op, 0);
|
||||
}
|
||||
|
||||
protected:
|
||||
channel_send(func_type func, Payload&& payload)
|
||||
: channel_operation(func),
|
||||
payload_(static_cast<Payload&&>(payload))
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
Payload payload_;
|
||||
};
|
||||
|
||||
template <typename Payload, typename Handler, typename IoExecutor>
|
||||
class channel_send_op : public channel_send<Payload>
|
||||
{
|
||||
public:
|
||||
ASIO_DEFINE_HANDLER_PTR(channel_send_op);
|
||||
|
||||
channel_send_op(Payload&& payload,
|
||||
Handler& handler, const IoExecutor& io_ex)
|
||||
: channel_send<Payload>(&channel_send_op::do_action,
|
||||
static_cast<Payload&&>(payload)),
|
||||
handler_(static_cast<Handler&&>(handler)),
|
||||
work_(handler_, io_ex)
|
||||
{
|
||||
}
|
||||
|
||||
static void do_action(channel_operation* base,
|
||||
channel_operation::action a, void*)
|
||||
{
|
||||
// Take ownership of the operation object.
|
||||
channel_send_op* o(static_cast<channel_send_op*>(base));
|
||||
ptr p = { asio::detail::addressof(o->handler_), o, o };
|
||||
|
||||
ASIO_HANDLER_COMPLETION((*o));
|
||||
|
||||
// Take ownership of the operation's outstanding work.
|
||||
channel_operation::handler_work<Handler, IoExecutor> w(
|
||||
static_cast<channel_operation::handler_work<Handler, IoExecutor>&&>(
|
||||
o->work_));
|
||||
|
||||
asio::error_code ec;
|
||||
switch (a)
|
||||
{
|
||||
case channel_operation::cancel_op:
|
||||
ec = error::channel_cancelled;
|
||||
break;
|
||||
case channel_operation::close_op:
|
||||
ec = error::channel_closed;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Make a copy of the handler so that the memory can be deallocated before
|
||||
// the handler is posted. Even if we're not about to post the handler, a
|
||||
// sub-object of the handler may be the true owner of the memory associated
|
||||
// with the handler. Consequently, a local copy of the handler is required
|
||||
// to ensure that any owning sub-object remains valid until after we have
|
||||
// deallocated the memory here.
|
||||
asio::detail::binder1<Handler, asio::error_code>
|
||||
handler(o->handler_, ec);
|
||||
p.h = asio::detail::addressof(handler.handler_);
|
||||
p.reset();
|
||||
|
||||
// Post the completion if required.
|
||||
if (a != channel_operation::destroy_op)
|
||||
{
|
||||
ASIO_HANDLER_INVOCATION_BEGIN((handler.arg1_));
|
||||
if (a == channel_operation::immediate_op)
|
||||
w.immediate(handler, handler.handler_, 0);
|
||||
else
|
||||
w.post(handler, handler.handler_);
|
||||
ASIO_HANDLER_INVOCATION_END;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Handler handler_;
|
||||
channel_operation::handler_work<Handler, IoExecutor> work_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_CHANNEL_SEND_OP_HPP
|
||||
679
include/asio/experimental/detail/channel_service.hpp
Normal file
679
include/asio/experimental/detail/channel_service.hpp
Normal file
@@ -0,0 +1,679 @@
|
||||
//
|
||||
// experimental/detail/channel_service.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_CHANNEL_SERVICE_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_CHANNEL_SERVICE_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/associated_cancellation_slot.hpp"
|
||||
#include "asio/cancellation_type.hpp"
|
||||
#include "asio/detail/completion_message.hpp"
|
||||
#include "asio/detail/completion_payload.hpp"
|
||||
#include "asio/detail/completion_payload_handler.hpp"
|
||||
#include "asio/detail/mutex.hpp"
|
||||
#include "asio/detail/op_queue.hpp"
|
||||
#include "asio/execution_context.hpp"
|
||||
#include "asio/experimental/detail/channel_receive_op.hpp"
|
||||
#include "asio/experimental/detail/channel_send_op.hpp"
|
||||
#include "asio/experimental/detail/has_signature.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename Mutex>
|
||||
class channel_service
|
||||
: public asio::detail::execution_context_service_base<
|
||||
channel_service<Mutex>>
|
||||
{
|
||||
public:
|
||||
// Possible states for a channel end.
|
||||
enum state
|
||||
{
|
||||
buffer = 0,
|
||||
waiter = 1,
|
||||
block = 2,
|
||||
closed = 3
|
||||
};
|
||||
|
||||
// The base implementation type of all channels.
|
||||
struct base_implementation_type
|
||||
{
|
||||
// Default constructor.
|
||||
base_implementation_type()
|
||||
: receive_state_(block),
|
||||
send_state_(block),
|
||||
max_buffer_size_(0),
|
||||
next_(0),
|
||||
prev_(0)
|
||||
{
|
||||
}
|
||||
|
||||
// The current state of the channel.
|
||||
state receive_state_ : 16;
|
||||
state send_state_ : 16;
|
||||
|
||||
// The maximum number of elements that may be buffered in the channel.
|
||||
std::size_t max_buffer_size_;
|
||||
|
||||
// The operations that are waiting on the channel.
|
||||
asio::detail::op_queue<channel_operation> waiters_;
|
||||
|
||||
// Pointers to adjacent channel implementations in linked list.
|
||||
base_implementation_type* next_;
|
||||
base_implementation_type* prev_;
|
||||
|
||||
// The mutex type to protect the internal implementation.
|
||||
mutable Mutex mutex_;
|
||||
};
|
||||
|
||||
// The implementation for a specific value type.
|
||||
template <typename Traits, typename... Signatures>
|
||||
struct implementation_type;
|
||||
|
||||
// Constructor.
|
||||
channel_service(asio::execution_context& ctx);
|
||||
|
||||
// Destroy all user-defined handler objects owned by the service.
|
||||
void shutdown();
|
||||
|
||||
// Construct a new channel implementation.
|
||||
void construct(base_implementation_type& impl, std::size_t max_buffer_size);
|
||||
|
||||
// Destroy a channel implementation.
|
||||
template <typename Traits, typename... Signatures>
|
||||
void destroy(implementation_type<Traits, Signatures...>& impl);
|
||||
|
||||
// Move-construct a new channel implementation.
|
||||
template <typename Traits, typename... Signatures>
|
||||
void move_construct(implementation_type<Traits, Signatures...>& impl,
|
||||
implementation_type<Traits, Signatures...>& other_impl);
|
||||
|
||||
// Move-assign from another channel implementation.
|
||||
template <typename Traits, typename... Signatures>
|
||||
void move_assign(implementation_type<Traits, Signatures...>& impl,
|
||||
channel_service& other_service,
|
||||
implementation_type<Traits, Signatures...>& other_impl);
|
||||
|
||||
// Get the capacity of the channel.
|
||||
std::size_t capacity(
|
||||
const base_implementation_type& impl) const noexcept;
|
||||
|
||||
// Determine whether the channel is open.
|
||||
bool is_open(const base_implementation_type& impl) const noexcept;
|
||||
|
||||
// Reset the channel to its initial state.
|
||||
template <typename Traits, typename... Signatures>
|
||||
void reset(implementation_type<Traits, Signatures...>& impl);
|
||||
|
||||
// Close the channel.
|
||||
template <typename Traits, typename... Signatures>
|
||||
void close(implementation_type<Traits, Signatures...>& impl);
|
||||
|
||||
// Cancel all operations associated with the channel.
|
||||
template <typename Traits, typename... Signatures>
|
||||
void cancel(implementation_type<Traits, Signatures...>& impl);
|
||||
|
||||
// Cancel the operation associated with the channel that has the given key.
|
||||
template <typename Traits, typename... Signatures>
|
||||
void cancel_by_key(implementation_type<Traits, Signatures...>& impl,
|
||||
void* cancellation_key);
|
||||
|
||||
// Determine whether a value can be read from the channel without blocking.
|
||||
bool ready(const base_implementation_type& impl) const noexcept;
|
||||
|
||||
// Synchronously send a new value into the channel.
|
||||
template <typename Message, typename Traits,
|
||||
typename... Signatures, typename... Args>
|
||||
bool try_send(implementation_type<Traits, Signatures...>& impl,
|
||||
bool via_dispatch, Args&&... args);
|
||||
|
||||
// Synchronously send a number of new values into the channel.
|
||||
template <typename Message, typename Traits,
|
||||
typename... Signatures, typename... Args>
|
||||
std::size_t try_send_n(implementation_type<Traits, Signatures...>& impl,
|
||||
std::size_t count, bool via_dispatch, Args&&... args);
|
||||
|
||||
// Asynchronously send a new value into the channel.
|
||||
template <typename Traits, typename... Signatures,
|
||||
typename Handler, typename IoExecutor>
|
||||
void async_send(implementation_type<Traits, Signatures...>& impl,
|
||||
typename implementation_type<Traits,
|
||||
Signatures...>::payload_type&& payload,
|
||||
Handler& handler, const IoExecutor& io_ex)
|
||||
{
|
||||
associated_cancellation_slot_t<Handler> slot
|
||||
= asio::get_associated_cancellation_slot(handler);
|
||||
|
||||
// Allocate and construct an operation to wrap the handler.
|
||||
typedef channel_send_op<
|
||||
typename implementation_type<Traits, Signatures...>::payload_type,
|
||||
Handler, IoExecutor> op;
|
||||
typename op::ptr p = { asio::detail::addressof(handler),
|
||||
op::ptr::allocate(handler), 0 };
|
||||
p.p = new (p.v) op(static_cast<typename implementation_type<
|
||||
Traits, Signatures...>::payload_type&&>(payload), handler, io_ex);
|
||||
|
||||
// Optionally register for per-operation cancellation.
|
||||
if (slot.is_connected())
|
||||
{
|
||||
p.p->cancellation_key_ =
|
||||
&slot.template emplace<op_cancellation<Traits, Signatures...>>(
|
||||
this, &impl);
|
||||
}
|
||||
|
||||
ASIO_HANDLER_CREATION((this->context(), *p.p,
|
||||
"channel", &impl, 0, "async_send"));
|
||||
|
||||
start_send_op(impl, p.p);
|
||||
p.v = p.p = 0;
|
||||
}
|
||||
|
||||
// Synchronously receive a value from the channel.
|
||||
template <typename Traits, typename... Signatures, typename Handler>
|
||||
bool try_receive(implementation_type<Traits, Signatures...>& impl,
|
||||
Handler&& handler);
|
||||
|
||||
// Asynchronously receive a value from the channel.
|
||||
template <typename Traits, typename... Signatures,
|
||||
typename Handler, typename IoExecutor>
|
||||
void async_receive(implementation_type<Traits, Signatures...>& impl,
|
||||
Handler& handler, const IoExecutor& io_ex)
|
||||
{
|
||||
associated_cancellation_slot_t<Handler> slot
|
||||
= asio::get_associated_cancellation_slot(handler);
|
||||
|
||||
// Allocate and construct an operation to wrap the handler.
|
||||
typedef channel_receive_op<
|
||||
typename implementation_type<Traits, Signatures...>::payload_type,
|
||||
Handler, IoExecutor> op;
|
||||
typename op::ptr p = { asio::detail::addressof(handler),
|
||||
op::ptr::allocate(handler), 0 };
|
||||
p.p = new (p.v) op(handler, io_ex);
|
||||
|
||||
// Optionally register for per-operation cancellation.
|
||||
if (slot.is_connected())
|
||||
{
|
||||
p.p->cancellation_key_ =
|
||||
&slot.template emplace<op_cancellation<Traits, Signatures...>>(
|
||||
this, &impl);
|
||||
}
|
||||
|
||||
ASIO_HANDLER_CREATION((this->context(), *p.p,
|
||||
"channel", &impl, 0, "async_receive"));
|
||||
|
||||
start_receive_op(impl, p.p);
|
||||
p.v = p.p = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Helper function object to handle a closed notification.
|
||||
template <typename Payload, typename Signature>
|
||||
struct post_receive
|
||||
{
|
||||
explicit post_receive(channel_receive<Payload>* op)
|
||||
: op_(op)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void operator()(Args&&... args)
|
||||
{
|
||||
op_->post(
|
||||
asio::detail::completion_message<Signature>(0,
|
||||
static_cast<Args&&>(args)...));
|
||||
}
|
||||
|
||||
channel_receive<Payload>* op_;
|
||||
};
|
||||
|
||||
// Destroy a base channel implementation.
|
||||
void base_destroy(base_implementation_type& impl);
|
||||
|
||||
// Helper function to start an asynchronous put operation.
|
||||
template <typename Traits, typename... Signatures>
|
||||
void start_send_op(implementation_type<Traits, Signatures...>& impl,
|
||||
channel_send<typename implementation_type<
|
||||
Traits, Signatures...>::payload_type>* send_op);
|
||||
|
||||
// Helper function to start an asynchronous get operation.
|
||||
template <typename Traits, typename... Signatures>
|
||||
void start_receive_op(implementation_type<Traits, Signatures...>& impl,
|
||||
channel_receive<typename implementation_type<
|
||||
Traits, Signatures...>::payload_type>* receive_op);
|
||||
|
||||
// Helper class used to implement per-operation cancellation.
|
||||
template <typename Traits, typename... Signatures>
|
||||
class op_cancellation
|
||||
{
|
||||
public:
|
||||
op_cancellation(channel_service* s,
|
||||
implementation_type<Traits, Signatures...>* impl)
|
||||
: service_(s),
|
||||
impl_(impl)
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(cancellation_type_t type)
|
||||
{
|
||||
if (!!(type &
|
||||
(cancellation_type::terminal
|
||||
| cancellation_type::partial
|
||||
| cancellation_type::total)))
|
||||
{
|
||||
service_->cancel_by_key(*impl_, this);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
channel_service* service_;
|
||||
implementation_type<Traits, Signatures...>* impl_;
|
||||
};
|
||||
|
||||
// Mutex to protect access to the linked list of implementations.
|
||||
asio::detail::mutex mutex_;
|
||||
|
||||
// The head of a linked list of all implementations.
|
||||
base_implementation_type* impl_list_;
|
||||
};
|
||||
|
||||
// The implementation for a specific value type.
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
struct channel_service<Mutex>::implementation_type : base_implementation_type
|
||||
{
|
||||
// The traits type associated with the channel.
|
||||
typedef typename Traits::template rebind<Signatures...>::other traits_type;
|
||||
|
||||
// Type of an element stored in the buffer.
|
||||
typedef conditional_t<
|
||||
has_signature<
|
||||
typename traits_type::receive_cancelled_signature,
|
||||
Signatures...
|
||||
>::value,
|
||||
conditional_t<
|
||||
has_signature<
|
||||
typename traits_type::receive_closed_signature,
|
||||
Signatures...
|
||||
>::value,
|
||||
asio::detail::completion_payload<Signatures...>,
|
||||
asio::detail::completion_payload<
|
||||
Signatures...,
|
||||
typename traits_type::receive_closed_signature
|
||||
>
|
||||
>,
|
||||
conditional_t<
|
||||
has_signature<
|
||||
typename traits_type::receive_closed_signature,
|
||||
Signatures...,
|
||||
typename traits_type::receive_cancelled_signature
|
||||
>::value,
|
||||
asio::detail::completion_payload<
|
||||
Signatures...,
|
||||
typename traits_type::receive_cancelled_signature
|
||||
>,
|
||||
asio::detail::completion_payload<
|
||||
Signatures...,
|
||||
typename traits_type::receive_cancelled_signature,
|
||||
typename traits_type::receive_closed_signature
|
||||
>
|
||||
>
|
||||
> payload_type;
|
||||
|
||||
// Move from another buffer.
|
||||
void buffer_move_from(implementation_type& other)
|
||||
{
|
||||
buffer_ = static_cast<
|
||||
typename traits_type::template container<payload_type>::type&&>(
|
||||
other.buffer_);
|
||||
other.buffer_clear();
|
||||
}
|
||||
|
||||
// Get number of buffered elements.
|
||||
std::size_t buffer_size() const
|
||||
{
|
||||
return buffer_.size();
|
||||
}
|
||||
|
||||
// Push a new value to the back of the buffer.
|
||||
void buffer_push(payload_type payload)
|
||||
{
|
||||
buffer_.push_back(static_cast<payload_type&&>(payload));
|
||||
}
|
||||
|
||||
// Push new values to the back of the buffer.
|
||||
std::size_t buffer_push_n(std::size_t count, payload_type payload)
|
||||
{
|
||||
std::size_t i = 0;
|
||||
for (; i < count && buffer_.size() < this->max_buffer_size_; ++i)
|
||||
buffer_.push_back(payload);
|
||||
return i;
|
||||
}
|
||||
|
||||
// Get the element at the front of the buffer.
|
||||
payload_type buffer_front()
|
||||
{
|
||||
return static_cast<payload_type&&>(buffer_.front());
|
||||
}
|
||||
|
||||
// Pop a value from the front of the buffer.
|
||||
void buffer_pop()
|
||||
{
|
||||
buffer_.pop_front();
|
||||
}
|
||||
|
||||
// Clear all buffered values.
|
||||
void buffer_clear()
|
||||
{
|
||||
buffer_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
// Buffered values.
|
||||
typename traits_type::template container<payload_type>::type buffer_;
|
||||
};
|
||||
|
||||
// The implementation for a void value type.
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename R>
|
||||
struct channel_service<Mutex>::implementation_type<Traits, R()>
|
||||
: channel_service::base_implementation_type
|
||||
{
|
||||
// The traits type associated with the channel.
|
||||
typedef typename Traits::template rebind<R()>::other traits_type;
|
||||
|
||||
// Type of an element stored in the buffer.
|
||||
typedef conditional_t<
|
||||
has_signature<
|
||||
typename traits_type::receive_cancelled_signature,
|
||||
R()
|
||||
>::value,
|
||||
conditional_t<
|
||||
has_signature<
|
||||
typename traits_type::receive_closed_signature,
|
||||
R()
|
||||
>::value,
|
||||
asio::detail::completion_payload<R()>,
|
||||
asio::detail::completion_payload<
|
||||
R(),
|
||||
typename traits_type::receive_closed_signature
|
||||
>
|
||||
>,
|
||||
conditional_t<
|
||||
has_signature<
|
||||
typename traits_type::receive_closed_signature,
|
||||
R(),
|
||||
typename traits_type::receive_cancelled_signature
|
||||
>::value,
|
||||
asio::detail::completion_payload<
|
||||
R(),
|
||||
typename traits_type::receive_cancelled_signature
|
||||
>,
|
||||
asio::detail::completion_payload<
|
||||
R(),
|
||||
typename traits_type::receive_cancelled_signature,
|
||||
typename traits_type::receive_closed_signature
|
||||
>
|
||||
>
|
||||
> payload_type;
|
||||
|
||||
// Construct with empty buffer.
|
||||
implementation_type()
|
||||
: buffer_(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Move from another buffer.
|
||||
void buffer_move_from(implementation_type& other)
|
||||
{
|
||||
buffer_ = other.buffer_;
|
||||
other.buffer_ = 0;
|
||||
}
|
||||
|
||||
// Get number of buffered elements.
|
||||
std::size_t buffer_size() const
|
||||
{
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
// Push a new value to the back of the buffer.
|
||||
void buffer_push(payload_type)
|
||||
{
|
||||
++buffer_;
|
||||
}
|
||||
|
||||
// Push new values to the back of the buffer.
|
||||
std::size_t buffer_push_n(std::size_t count, payload_type)
|
||||
{
|
||||
std::size_t available = this->max_buffer_size_ - buffer_;
|
||||
count = (count < available) ? count : available;
|
||||
buffer_ += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
// Get the element at the front of the buffer.
|
||||
payload_type buffer_front()
|
||||
{
|
||||
return payload_type(asio::detail::completion_message<R()>(0));
|
||||
}
|
||||
|
||||
// Pop a value from the front of the buffer.
|
||||
void buffer_pop()
|
||||
{
|
||||
--buffer_;
|
||||
}
|
||||
|
||||
// Clear all values from the buffer.
|
||||
void buffer_clear()
|
||||
{
|
||||
buffer_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Number of buffered "values".
|
||||
std::size_t buffer_;
|
||||
};
|
||||
|
||||
// The implementation for an error_code signature.
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename R>
|
||||
struct channel_service<Mutex>::implementation_type<
|
||||
Traits, R(asio::error_code)>
|
||||
: channel_service::base_implementation_type
|
||||
{
|
||||
// The traits type associated with the channel.
|
||||
typedef typename Traits::template rebind<R(asio::error_code)>::other
|
||||
traits_type;
|
||||
|
||||
// Type of an element stored in the buffer.
|
||||
typedef conditional_t<
|
||||
has_signature<
|
||||
typename traits_type::receive_cancelled_signature,
|
||||
R(asio::error_code)
|
||||
>::value,
|
||||
conditional_t<
|
||||
has_signature<
|
||||
typename traits_type::receive_closed_signature,
|
||||
R(asio::error_code)
|
||||
>::value,
|
||||
asio::detail::completion_payload<R(asio::error_code)>,
|
||||
asio::detail::completion_payload<
|
||||
R(asio::error_code),
|
||||
typename traits_type::receive_closed_signature
|
||||
>
|
||||
>,
|
||||
conditional_t<
|
||||
has_signature<
|
||||
typename traits_type::receive_closed_signature,
|
||||
R(asio::error_code),
|
||||
typename traits_type::receive_cancelled_signature
|
||||
>::value,
|
||||
asio::detail::completion_payload<
|
||||
R(asio::error_code),
|
||||
typename traits_type::receive_cancelled_signature
|
||||
>,
|
||||
asio::detail::completion_payload<
|
||||
R(asio::error_code),
|
||||
typename traits_type::receive_cancelled_signature,
|
||||
typename traits_type::receive_closed_signature
|
||||
>
|
||||
>
|
||||
> payload_type;
|
||||
|
||||
// Construct with empty buffer.
|
||||
implementation_type()
|
||||
: size_(0)
|
||||
{
|
||||
first_.count_ = 0;
|
||||
}
|
||||
|
||||
// Move from another buffer.
|
||||
void buffer_move_from(implementation_type& other)
|
||||
{
|
||||
size_ = other.size_;
|
||||
other.size_ = 0;
|
||||
first_ = other.first_;
|
||||
other.first_.count_ = 0;
|
||||
rest_ = static_cast<
|
||||
typename traits_type::template container<buffered_value>::type&&>(
|
||||
other.rest_);
|
||||
other.buffer_clear();
|
||||
}
|
||||
|
||||
// Get number of buffered elements.
|
||||
std::size_t buffer_size() const
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
// Push a new value to the back of the buffer.
|
||||
void buffer_push(payload_type payload)
|
||||
{
|
||||
buffered_value& last = rest_.empty() ? first_ : rest_.back();
|
||||
if (last.count_ == 0)
|
||||
{
|
||||
value_handler handler{last.value_};
|
||||
payload.receive(handler);
|
||||
last.count_ = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
asio::error_code value{last.value_};
|
||||
value_handler handler{value};
|
||||
payload.receive(handler);
|
||||
if (last.value_ == value)
|
||||
++last.count_;
|
||||
else
|
||||
rest_.push_back({value, 1});
|
||||
}
|
||||
++size_;
|
||||
}
|
||||
|
||||
// Push new values to the back of the buffer.
|
||||
std::size_t buffer_push_n(std::size_t count, payload_type payload)
|
||||
{
|
||||
std::size_t available = this->max_buffer_size_ - size_;
|
||||
count = (count < available) ? count : available;
|
||||
if (count > 0)
|
||||
{
|
||||
buffered_value& last = rest_.empty() ? first_ : rest_.back();
|
||||
if (last.count_ == 0)
|
||||
{
|
||||
payload.receive(value_handler{last.value_});
|
||||
last.count_ = count;
|
||||
}
|
||||
else
|
||||
{
|
||||
asio::error_code value{last.value_};
|
||||
payload.receive(value_handler{value});
|
||||
if (last.value_ == value)
|
||||
last.count_ += count;
|
||||
else
|
||||
rest_.push_back({value, count});
|
||||
}
|
||||
size_ += count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Get the element at the front of the buffer.
|
||||
payload_type buffer_front()
|
||||
{
|
||||
return payload_type({0, first_.value_});
|
||||
}
|
||||
|
||||
// Pop a value from the front of the buffer.
|
||||
void buffer_pop()
|
||||
{
|
||||
--size_;
|
||||
if (--first_.count_ == 0 && !rest_.empty())
|
||||
{
|
||||
first_ = rest_.front();
|
||||
rest_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all values from the buffer.
|
||||
void buffer_clear()
|
||||
{
|
||||
size_ = 0;
|
||||
first_.count_ = 0;
|
||||
rest_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
struct buffered_value
|
||||
{
|
||||
asio::error_code value_;
|
||||
std::size_t count_;
|
||||
};
|
||||
|
||||
struct value_handler
|
||||
{
|
||||
asio::error_code& target_;
|
||||
|
||||
template <typename... Args>
|
||||
void operator()(const asio::error_code& value, Args&&...)
|
||||
{
|
||||
target_ = value;
|
||||
}
|
||||
};
|
||||
|
||||
buffered_value& last_value()
|
||||
{
|
||||
return rest_.empty() ? first_ : rest_.back();
|
||||
}
|
||||
|
||||
// Total number of buffered values.
|
||||
std::size_t size_;
|
||||
|
||||
// The first buffered value is maintained as a separate data member to avoid
|
||||
// allocating space in the container in the common case.
|
||||
buffered_value first_;
|
||||
|
||||
// The rest of the buffered values.
|
||||
typename traits_type::template container<buffered_value>::type rest_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#include "asio/experimental/detail/impl/channel_service.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_CHANNEL_SERVICE_HPP
|
||||
169
include/asio/experimental/detail/coro_completion_handler.hpp
Normal file
169
include/asio/experimental/detail/coro_completion_handler.hpp
Normal file
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// experimental/detail/coro_completion_handler.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_CORO_COMPLETION_HANDLER_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_CORO_COMPLETION_HANDLER_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/deferred.hpp"
|
||||
#include "asio/experimental/coro.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename Promise, typename... Args>
|
||||
struct coro_completion_handler
|
||||
{
|
||||
coro_completion_handler(coroutine_handle<Promise> h,
|
||||
std::optional<std::tuple<Args...>>& result)
|
||||
: self(h),
|
||||
result(result)
|
||||
{
|
||||
}
|
||||
|
||||
coro_completion_handler(coro_completion_handler&&) = default;
|
||||
|
||||
coroutine_handle<Promise> self;
|
||||
|
||||
std::optional<std::tuple<Args...>>& result;
|
||||
|
||||
using promise_type = Promise;
|
||||
|
||||
void operator()(Args... args)
|
||||
{
|
||||
result.emplace(std::move(args)...);
|
||||
self.resume();
|
||||
}
|
||||
|
||||
using allocator_type = typename promise_type::allocator_type;
|
||||
allocator_type get_allocator() const noexcept
|
||||
{
|
||||
return self.promise().get_allocator();
|
||||
}
|
||||
|
||||
using executor_type = typename promise_type::executor_type;
|
||||
executor_type get_executor() const noexcept
|
||||
{
|
||||
return self.promise().get_executor();
|
||||
}
|
||||
|
||||
using cancellation_slot_type = typename promise_type::cancellation_slot_type;
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept
|
||||
{
|
||||
return self.promise().get_cancellation_slot();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Signature>
|
||||
struct coro_completion_handler_type;
|
||||
|
||||
template <typename... Args>
|
||||
struct coro_completion_handler_type<void(Args...)>
|
||||
{
|
||||
using type = std::tuple<Args...>;
|
||||
|
||||
template <typename Promise>
|
||||
using completion_handler = coro_completion_handler<Promise, Args...>;
|
||||
};
|
||||
|
||||
template <typename Signature>
|
||||
using coro_completion_handler_type_t =
|
||||
typename coro_completion_handler_type<Signature>::type;
|
||||
|
||||
inline void coro_interpret_result(std::tuple<>&&)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline auto coro_interpret_result(std::tuple<Args...>&& args)
|
||||
{
|
||||
return std::move(args);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
auto coro_interpret_result(std::tuple<std::exception_ptr, Args...>&& args)
|
||||
{
|
||||
if (std::get<0>(args))
|
||||
std::rethrow_exception(std::get<0>(args));
|
||||
|
||||
return std::apply(
|
||||
[](auto, auto&&... rest)
|
||||
{
|
||||
return std::make_tuple(std::move(rest)...);
|
||||
}, std::move(args));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
auto coro_interpret_result(
|
||||
std::tuple<asio::error_code, Args...>&& args)
|
||||
{
|
||||
if (std::get<0>(args))
|
||||
asio::detail::throw_exception(
|
||||
asio::system_error(std::get<0>(args)));
|
||||
|
||||
return std::apply(
|
||||
[](auto, auto&&... rest)
|
||||
{
|
||||
return std::make_tuple(std::move(rest)...);
|
||||
}, std::move(args));
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
inline auto coro_interpret_result(std::tuple<Arg>&& args)
|
||||
{
|
||||
return std::get<0>(std::move(args));
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
auto coro_interpret_result(std::tuple<std::exception_ptr, Arg>&& args)
|
||||
{
|
||||
if (std::get<0>(args))
|
||||
std::rethrow_exception(std::get<0>(args));
|
||||
return std::get<1>(std::move(args));
|
||||
}
|
||||
|
||||
inline auto coro_interpret_result(
|
||||
std::tuple<asio::error_code>&& args)
|
||||
{
|
||||
if (std::get<0>(args))
|
||||
asio::detail::throw_exception(
|
||||
asio::system_error(std::get<0>(args)));
|
||||
}
|
||||
|
||||
inline auto coro_interpret_result(std::tuple<std::exception_ptr>&& args)
|
||||
{
|
||||
if (std::get<0>(args))
|
||||
std::rethrow_exception(std::get<0>(args));
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
auto coro_interpret_result(std::tuple<asio::error_code, Arg>&& args)
|
||||
{
|
||||
if (std::get<0>(args))
|
||||
asio::detail::throw_exception(
|
||||
asio::system_error(std::get<0>(args)));
|
||||
return std::get<1>(std::move(args));
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_CORO_COMPLETION_HANDLER_HPP
|
||||
141
include/asio/experimental/detail/coro_promise_allocator.hpp
Normal file
141
include/asio/experimental/detail/coro_promise_allocator.hpp
Normal file
@@ -0,0 +1,141 @@
|
||||
//
|
||||
// experimental/detail/coro_promise_allocator.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_CORO_PROMISE_ALLOCATOR_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_CORO_PROMISE_ALLOCATOR_HPP
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <limits>
|
||||
#include "asio/experimental/coro_traits.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
/// Allocate the memory and put the allocator behind the coro memory
|
||||
template <typename AllocatorType>
|
||||
void* allocate_coroutine(const std::size_t size, AllocatorType alloc_)
|
||||
{
|
||||
using alloc_type = typename std::allocator_traits<AllocatorType>::template
|
||||
rebind_alloc<unsigned char>;
|
||||
alloc_type alloc{alloc_};
|
||||
|
||||
const auto align_needed = size % alignof(alloc_type);
|
||||
const auto align_offset = align_needed != 0
|
||||
? alignof(alloc_type) - align_needed : 0ull;
|
||||
const auto alloc_size = size + sizeof(alloc_type) + align_offset;
|
||||
const auto raw =
|
||||
std::allocator_traits<alloc_type>::allocate(alloc, alloc_size);
|
||||
new(raw + size + align_offset) alloc_type(std::move(alloc));
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
/// Deallocate the memory and destroy the allocator in the coro memory.
|
||||
template <typename AllocatorType>
|
||||
void deallocate_coroutine(void* raw_, const std::size_t size)
|
||||
{
|
||||
using alloc_type = typename std::allocator_traits<AllocatorType>::template
|
||||
rebind_alloc<unsigned char>;
|
||||
|
||||
const auto raw = static_cast<unsigned char *>(raw_);
|
||||
|
||||
const auto align_needed = size % alignof(alloc_type);
|
||||
const auto align_offset = align_needed != 0
|
||||
? alignof(alloc_type) - align_needed : 0ull;
|
||||
const auto alloc_size = size + sizeof(alloc_type) + align_offset;
|
||||
|
||||
auto alloc_p = reinterpret_cast<alloc_type *>(raw + size + align_offset);
|
||||
auto alloc = std::move(*alloc_p);
|
||||
alloc_p->~alloc_type();
|
||||
std::allocator_traits<alloc_type>::deallocate(alloc, raw, alloc_size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr std::size_t variadic_first(std::size_t = 0u)
|
||||
{
|
||||
return std::numeric_limits<std::size_t>::max();
|
||||
}
|
||||
|
||||
template <typename T, typename First, typename... Args>
|
||||
constexpr std::size_t variadic_first(std::size_t pos = 0u)
|
||||
{
|
||||
if constexpr (std::is_same_v<std::decay_t<First>, T>)
|
||||
return pos;
|
||||
else
|
||||
return variadic_first<T, Args...>(pos+1);
|
||||
}
|
||||
|
||||
template <std::size_t Idx, typename First, typename... Args>
|
||||
requires (Idx <= sizeof...(Args))
|
||||
constexpr decltype(auto) get_variadic(First&& first, Args&&... args)
|
||||
{
|
||||
if constexpr (Idx == 0u)
|
||||
return static_cast<First>(first);
|
||||
else
|
||||
return get_variadic<Idx-1u>(static_cast<Args>(args)...);
|
||||
}
|
||||
|
||||
template <std::size_t Idx>
|
||||
constexpr decltype(auto) get_variadic();
|
||||
|
||||
template <typename Allocator>
|
||||
struct coro_promise_allocator
|
||||
{
|
||||
using allocator_type = Allocator;
|
||||
allocator_type get_allocator() const {return alloc_;}
|
||||
|
||||
template <typename... Args>
|
||||
void* operator new(std::size_t size, Args & ... args)
|
||||
{
|
||||
return allocate_coroutine(size,
|
||||
get_variadic<variadic_first<std::allocator_arg_t,
|
||||
std::decay_t<Args>...>() + 1u>(args...));
|
||||
}
|
||||
|
||||
void operator delete(void* raw, std::size_t size)
|
||||
{
|
||||
deallocate_coroutine<allocator_type>(raw, size);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
coro_promise_allocator(Args&& ... args)
|
||||
: alloc_(
|
||||
get_variadic<variadic_first<std::allocator_arg_t,
|
||||
std::decay_t<Args>...>() + 1u>(args...))
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
allocator_type alloc_;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct coro_promise_allocator<std::allocator<void>>
|
||||
{
|
||||
using allocator_type = std::allocator<void>;
|
||||
|
||||
template <typename... Args>
|
||||
coro_promise_allocator(Args&&...)
|
||||
{
|
||||
}
|
||||
|
||||
allocator_type get_allocator() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_CORO_PROMISE_ALLOCATOR_HPP
|
||||
54
include/asio/experimental/detail/has_signature.hpp
Normal file
54
include/asio/experimental/detail/has_signature.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// experimental/detail/has_signature.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_HAS_SIGNATURE_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_HAS_SIGNATURE_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename S, typename... Signatures>
|
||||
struct has_signature;
|
||||
|
||||
template <typename S, typename... Signatures>
|
||||
struct has_signature;
|
||||
|
||||
template <typename S>
|
||||
struct has_signature<S> : false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename S, typename... Signatures>
|
||||
struct has_signature<S, S, Signatures...> : true_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename S, typename Head, typename... Tail>
|
||||
struct has_signature<S, Head, Tail...> : has_signature<S, Tail...>
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_HAS_SIGNATURE_HPP
|
||||
623
include/asio/experimental/detail/impl/channel_service.hpp
Normal file
623
include/asio/experimental/detail/impl/channel_service.hpp
Normal file
@@ -0,0 +1,623 @@
|
||||
//
|
||||
// experimental/detail/impl/channel_service.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_IMPL_CHANNEL_SERVICE_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_IMPL_CHANNEL_SERVICE_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <typename Mutex>
|
||||
inline channel_service<Mutex>::channel_service(
|
||||
asio::execution_context& ctx)
|
||||
: asio::detail::execution_context_service_base<channel_service>(ctx),
|
||||
mutex_(),
|
||||
impl_list_(0)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
inline void channel_service<Mutex>::shutdown()
|
||||
{
|
||||
// Abandon all pending operations.
|
||||
asio::detail::op_queue<channel_operation> ops;
|
||||
asio::detail::mutex::scoped_lock lock(mutex_);
|
||||
base_implementation_type* impl = impl_list_;
|
||||
while (impl)
|
||||
{
|
||||
ops.push(impl->waiters_);
|
||||
impl = impl->next_;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
inline void channel_service<Mutex>::construct(
|
||||
channel_service<Mutex>::base_implementation_type& impl,
|
||||
std::size_t max_buffer_size)
|
||||
{
|
||||
impl.max_buffer_size_ = max_buffer_size;
|
||||
impl.receive_state_ = block;
|
||||
impl.send_state_ = max_buffer_size ? buffer : block;
|
||||
|
||||
// Insert implementation into linked list of all implementations.
|
||||
asio::detail::mutex::scoped_lock lock(mutex_);
|
||||
impl.next_ = impl_list_;
|
||||
impl.prev_ = 0;
|
||||
if (impl_list_)
|
||||
impl_list_->prev_ = &impl;
|
||||
impl_list_ = &impl;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
void channel_service<Mutex>::destroy(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl)
|
||||
{
|
||||
cancel(impl);
|
||||
base_destroy(impl);
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
void channel_service<Mutex>::move_construct(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl,
|
||||
channel_service<Mutex>::implementation_type<
|
||||
Traits, Signatures...>& other_impl)
|
||||
{
|
||||
impl.max_buffer_size_ = other_impl.max_buffer_size_;
|
||||
impl.receive_state_ = other_impl.receive_state_;
|
||||
other_impl.receive_state_ = block;
|
||||
impl.send_state_ = other_impl.send_state_;
|
||||
other_impl.send_state_ = other_impl.max_buffer_size_ ? buffer : block;
|
||||
impl.buffer_move_from(other_impl);
|
||||
|
||||
// Insert implementation into linked list of all implementations.
|
||||
asio::detail::mutex::scoped_lock lock(mutex_);
|
||||
impl.next_ = impl_list_;
|
||||
impl.prev_ = 0;
|
||||
if (impl_list_)
|
||||
impl_list_->prev_ = &impl;
|
||||
impl_list_ = &impl;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
void channel_service<Mutex>::move_assign(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl,
|
||||
channel_service& other_service,
|
||||
channel_service<Mutex>::implementation_type<
|
||||
Traits, Signatures...>& other_impl)
|
||||
{
|
||||
cancel(impl);
|
||||
|
||||
if (this != &other_service)
|
||||
{
|
||||
// Remove implementation from linked list of all implementations.
|
||||
asio::detail::mutex::scoped_lock lock(mutex_);
|
||||
if (impl_list_ == &impl)
|
||||
impl_list_ = impl.next_;
|
||||
if (impl.prev_)
|
||||
impl.prev_->next_ = impl.next_;
|
||||
if (impl.next_)
|
||||
impl.next_->prev_= impl.prev_;
|
||||
impl.next_ = 0;
|
||||
impl.prev_ = 0;
|
||||
}
|
||||
|
||||
impl.max_buffer_size_ = other_impl.max_buffer_size_;
|
||||
impl.receive_state_ = other_impl.receive_state_;
|
||||
other_impl.receive_state_ = block;
|
||||
impl.send_state_ = other_impl.send_state_;
|
||||
other_impl.send_state_ = other_impl.max_buffer_size_ ? buffer : block;
|
||||
impl.buffer_move_from(other_impl);
|
||||
|
||||
if (this != &other_service)
|
||||
{
|
||||
// Insert implementation into linked list of all implementations.
|
||||
asio::detail::mutex::scoped_lock lock(other_service.mutex_);
|
||||
impl.next_ = other_service.impl_list_;
|
||||
impl.prev_ = 0;
|
||||
if (other_service.impl_list_)
|
||||
other_service.impl_list_->prev_ = &impl;
|
||||
other_service.impl_list_ = &impl;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
inline void channel_service<Mutex>::base_destroy(
|
||||
channel_service<Mutex>::base_implementation_type& impl)
|
||||
{
|
||||
// Remove implementation from linked list of all implementations.
|
||||
asio::detail::mutex::scoped_lock lock(mutex_);
|
||||
if (impl_list_ == &impl)
|
||||
impl_list_ = impl.next_;
|
||||
if (impl.prev_)
|
||||
impl.prev_->next_ = impl.next_;
|
||||
if (impl.next_)
|
||||
impl.next_->prev_= impl.prev_;
|
||||
impl.next_ = 0;
|
||||
impl.prev_ = 0;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
inline std::size_t channel_service<Mutex>::capacity(
|
||||
const channel_service<Mutex>::base_implementation_type& impl)
|
||||
const noexcept
|
||||
{
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
return impl.max_buffer_size_;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
inline bool channel_service<Mutex>::is_open(
|
||||
const channel_service<Mutex>::base_implementation_type& impl)
|
||||
const noexcept
|
||||
{
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
return impl.send_state_ != closed;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
void channel_service<Mutex>::reset(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl)
|
||||
{
|
||||
cancel(impl);
|
||||
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
impl.receive_state_ = block;
|
||||
impl.send_state_ = impl.max_buffer_size_ ? buffer : block;
|
||||
impl.buffer_clear();
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
void channel_service<Mutex>::close(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl)
|
||||
{
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::traits_type traits_type;
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::payload_type payload_type;
|
||||
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
if (impl.receive_state_ == block)
|
||||
{
|
||||
while (channel_operation* op = impl.waiters_.front())
|
||||
{
|
||||
impl.waiters_.pop();
|
||||
traits_type::invoke_receive_closed(
|
||||
post_receive<payload_type,
|
||||
typename traits_type::receive_closed_signature>(
|
||||
static_cast<channel_receive<payload_type>*>(op)));
|
||||
}
|
||||
}
|
||||
|
||||
impl.send_state_ = closed;
|
||||
if (impl.receive_state_ != buffer)
|
||||
impl.receive_state_ = closed;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
void channel_service<Mutex>::cancel(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl)
|
||||
{
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::traits_type traits_type;
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::payload_type payload_type;
|
||||
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
while (channel_operation* op = impl.waiters_.front())
|
||||
{
|
||||
if (impl.send_state_ == block)
|
||||
{
|
||||
impl.waiters_.pop();
|
||||
static_cast<channel_send<payload_type>*>(op)->cancel();
|
||||
}
|
||||
else
|
||||
{
|
||||
impl.waiters_.pop();
|
||||
traits_type::invoke_receive_cancelled(
|
||||
post_receive<payload_type,
|
||||
typename traits_type::receive_cancelled_signature>(
|
||||
static_cast<channel_receive<payload_type>*>(op)));
|
||||
}
|
||||
}
|
||||
|
||||
if (impl.receive_state_ == waiter)
|
||||
impl.receive_state_ = block;
|
||||
if (impl.send_state_ == waiter)
|
||||
impl.send_state_ = impl.max_buffer_size_ ? buffer : block;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
void channel_service<Mutex>::cancel_by_key(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl,
|
||||
void* cancellation_key)
|
||||
{
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::traits_type traits_type;
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::payload_type payload_type;
|
||||
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
asio::detail::op_queue<channel_operation> other_ops;
|
||||
while (channel_operation* op = impl.waiters_.front())
|
||||
{
|
||||
if (op->cancellation_key_ == cancellation_key)
|
||||
{
|
||||
if (impl.send_state_ == block)
|
||||
{
|
||||
impl.waiters_.pop();
|
||||
static_cast<channel_send<payload_type>*>(op)->cancel();
|
||||
}
|
||||
else
|
||||
{
|
||||
impl.waiters_.pop();
|
||||
traits_type::invoke_receive_cancelled(
|
||||
post_receive<payload_type,
|
||||
typename traits_type::receive_cancelled_signature>(
|
||||
static_cast<channel_receive<payload_type>*>(op)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
impl.waiters_.pop();
|
||||
other_ops.push(op);
|
||||
}
|
||||
}
|
||||
impl.waiters_.push(other_ops);
|
||||
|
||||
if (impl.waiters_.empty())
|
||||
{
|
||||
if (impl.receive_state_ == waiter)
|
||||
impl.receive_state_ = block;
|
||||
if (impl.send_state_ == waiter)
|
||||
impl.send_state_ = impl.max_buffer_size_ ? buffer : block;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
inline bool channel_service<Mutex>::ready(
|
||||
const channel_service<Mutex>::base_implementation_type& impl)
|
||||
const noexcept
|
||||
{
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
return impl.receive_state_ != block;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Message, typename Traits,
|
||||
typename... Signatures, typename... Args>
|
||||
bool channel_service<Mutex>::try_send(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl,
|
||||
bool via_dispatch, Args&&... args)
|
||||
{
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::payload_type payload_type;
|
||||
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
switch (impl.send_state_)
|
||||
{
|
||||
case block:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case buffer:
|
||||
{
|
||||
impl.buffer_push(Message(0, static_cast<Args&&>(args)...));
|
||||
impl.receive_state_ = buffer;
|
||||
if (impl.buffer_size() == impl.max_buffer_size_)
|
||||
impl.send_state_ = block;
|
||||
return true;
|
||||
}
|
||||
case waiter:
|
||||
{
|
||||
payload_type payload(Message(0, static_cast<Args&&>(args)...));
|
||||
channel_receive<payload_type>* receive_op =
|
||||
static_cast<channel_receive<payload_type>*>(impl.waiters_.front());
|
||||
impl.waiters_.pop();
|
||||
if (impl.waiters_.empty())
|
||||
impl.send_state_ = impl.max_buffer_size_ ? buffer : block;
|
||||
lock.unlock();
|
||||
if (via_dispatch)
|
||||
receive_op->dispatch(static_cast<payload_type&&>(payload));
|
||||
else
|
||||
receive_op->post(static_cast<payload_type&&>(payload));
|
||||
return true;
|
||||
}
|
||||
case closed:
|
||||
default:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Message, typename Traits,
|
||||
typename... Signatures, typename... Args>
|
||||
std::size_t channel_service<Mutex>::try_send_n(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl,
|
||||
std::size_t count, bool via_dispatch, Args&&... args)
|
||||
{
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::payload_type payload_type;
|
||||
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
switch (impl.send_state_)
|
||||
{
|
||||
case block:
|
||||
return 0;
|
||||
case buffer:
|
||||
case waiter:
|
||||
break;
|
||||
case closed:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
payload_type payload(Message(0, static_cast<Args&&>(args)...));
|
||||
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
switch (impl.send_state_)
|
||||
{
|
||||
case block:
|
||||
{
|
||||
return i;
|
||||
}
|
||||
case buffer:
|
||||
{
|
||||
i += impl.buffer_push_n(count - i,
|
||||
static_cast<payload_type&&>(payload));
|
||||
impl.receive_state_ = buffer;
|
||||
if (impl.buffer_size() == impl.max_buffer_size_)
|
||||
impl.send_state_ = block;
|
||||
return i;
|
||||
}
|
||||
case waiter:
|
||||
{
|
||||
channel_receive<payload_type>* receive_op =
|
||||
static_cast<channel_receive<payload_type>*>(impl.waiters_.front());
|
||||
impl.waiters_.pop();
|
||||
if (impl.waiters_.empty())
|
||||
impl.send_state_ = impl.max_buffer_size_ ? buffer : block;
|
||||
lock.unlock();
|
||||
if (via_dispatch)
|
||||
receive_op->dispatch(payload);
|
||||
else
|
||||
receive_op->post(payload);
|
||||
break;
|
||||
}
|
||||
case closed:
|
||||
default:
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
void channel_service<Mutex>::start_send_op(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl,
|
||||
channel_send<typename implementation_type<
|
||||
Traits, Signatures...>::payload_type>* send_op)
|
||||
{
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::payload_type payload_type;
|
||||
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
switch (impl.send_state_)
|
||||
{
|
||||
case block:
|
||||
{
|
||||
impl.waiters_.push(send_op);
|
||||
if (impl.receive_state_ == block)
|
||||
impl.receive_state_ = waiter;
|
||||
return;
|
||||
}
|
||||
case buffer:
|
||||
{
|
||||
impl.buffer_push(send_op->get_payload());
|
||||
impl.receive_state_ = buffer;
|
||||
if (impl.buffer_size() == impl.max_buffer_size_)
|
||||
impl.send_state_ = block;
|
||||
send_op->immediate();
|
||||
break;
|
||||
}
|
||||
case waiter:
|
||||
{
|
||||
channel_receive<payload_type>* receive_op =
|
||||
static_cast<channel_receive<payload_type>*>(impl.waiters_.front());
|
||||
impl.waiters_.pop();
|
||||
if (impl.waiters_.empty())
|
||||
impl.send_state_ = impl.max_buffer_size_ ? buffer : block;
|
||||
receive_op->post(send_op->get_payload());
|
||||
send_op->immediate();
|
||||
break;
|
||||
}
|
||||
case closed:
|
||||
default:
|
||||
{
|
||||
send_op->close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures, typename Handler>
|
||||
bool channel_service<Mutex>::try_receive(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl,
|
||||
Handler&& handler)
|
||||
{
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::payload_type payload_type;
|
||||
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
switch (impl.receive_state_)
|
||||
{
|
||||
case block:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case buffer:
|
||||
{
|
||||
payload_type payload(impl.buffer_front());
|
||||
if (channel_send<payload_type>* send_op =
|
||||
static_cast<channel_send<payload_type>*>(impl.waiters_.front()))
|
||||
{
|
||||
impl.buffer_pop();
|
||||
impl.buffer_push(send_op->get_payload());
|
||||
impl.waiters_.pop();
|
||||
send_op->post();
|
||||
}
|
||||
else
|
||||
{
|
||||
impl.buffer_pop();
|
||||
if (impl.buffer_size() == 0)
|
||||
impl.receive_state_ = (impl.send_state_ == closed) ? closed : block;
|
||||
impl.send_state_ = (impl.send_state_ == closed) ? closed : buffer;
|
||||
}
|
||||
lock.unlock();
|
||||
asio::detail::non_const_lvalue<Handler> handler2(handler);
|
||||
asio::detail::completion_payload_handler<
|
||||
payload_type, decay_t<Handler>>(
|
||||
static_cast<payload_type&&>(payload), handler2.value)();
|
||||
return true;
|
||||
}
|
||||
case waiter:
|
||||
{
|
||||
channel_send<payload_type>* send_op =
|
||||
static_cast<channel_send<payload_type>*>(impl.waiters_.front());
|
||||
payload_type payload = send_op->get_payload();
|
||||
impl.waiters_.pop();
|
||||
if (impl.waiters_.front() == 0)
|
||||
impl.receive_state_ = (impl.send_state_ == closed) ? closed : block;
|
||||
send_op->post();
|
||||
lock.unlock();
|
||||
asio::detail::non_const_lvalue<Handler> handler2(handler);
|
||||
asio::detail::completion_payload_handler<
|
||||
payload_type, decay_t<Handler>>(
|
||||
static_cast<payload_type&&>(payload), handler2.value)();
|
||||
return true;
|
||||
}
|
||||
case closed:
|
||||
default:
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Mutex>
|
||||
template <typename Traits, typename... Signatures>
|
||||
void channel_service<Mutex>::start_receive_op(
|
||||
channel_service<Mutex>::implementation_type<Traits, Signatures...>& impl,
|
||||
channel_receive<typename implementation_type<
|
||||
Traits, Signatures...>::payload_type>* receive_op)
|
||||
{
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::traits_type traits_type;
|
||||
typedef typename implementation_type<Traits,
|
||||
Signatures...>::payload_type payload_type;
|
||||
|
||||
typename Mutex::scoped_lock lock(impl.mutex_);
|
||||
|
||||
switch (impl.receive_state_)
|
||||
{
|
||||
case block:
|
||||
{
|
||||
impl.waiters_.push(receive_op);
|
||||
if (impl.send_state_ != closed)
|
||||
impl.send_state_ = waiter;
|
||||
return;
|
||||
}
|
||||
case buffer:
|
||||
{
|
||||
payload_type payload(
|
||||
static_cast<payload_type&&>(impl.buffer_front()));
|
||||
if (channel_send<payload_type>* send_op =
|
||||
static_cast<channel_send<payload_type>*>(impl.waiters_.front()))
|
||||
{
|
||||
impl.buffer_pop();
|
||||
impl.buffer_push(send_op->get_payload());
|
||||
impl.waiters_.pop();
|
||||
send_op->post();
|
||||
}
|
||||
else
|
||||
{
|
||||
impl.buffer_pop();
|
||||
if (impl.buffer_size() == 0)
|
||||
impl.receive_state_ = (impl.send_state_ == closed) ? closed : block;
|
||||
impl.send_state_ = (impl.send_state_ == closed) ? closed : buffer;
|
||||
}
|
||||
receive_op->immediate(static_cast<payload_type&&>(payload));
|
||||
break;
|
||||
}
|
||||
case waiter:
|
||||
{
|
||||
channel_send<payload_type>* send_op =
|
||||
static_cast<channel_send<payload_type>*>(impl.waiters_.front());
|
||||
payload_type payload = send_op->get_payload();
|
||||
impl.waiters_.pop();
|
||||
if (impl.waiters_.front() == 0)
|
||||
impl.receive_state_ = (impl.send_state_ == closed) ? closed : block;
|
||||
send_op->post();
|
||||
receive_op->immediate(static_cast<payload_type&&>(payload));
|
||||
break;
|
||||
}
|
||||
case closed:
|
||||
default:
|
||||
{
|
||||
traits_type::invoke_receive_closed(
|
||||
post_receive<payload_type,
|
||||
typename traits_type::receive_closed_signature>(receive_op));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_IMPL_CHANNEL_SERVICE_HPP
|
||||
195
include/asio/experimental/detail/partial_promise.hpp
Normal file
195
include/asio/experimental/detail/partial_promise.hpp
Normal file
@@ -0,0 +1,195 @@
|
||||
//
|
||||
// experimental/detail/partial_promise.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_DETAIL_PARTIAL_PROMISE_HPP
|
||||
#define ASIO_EXPERIMENTAL_DETAIL_PARTIAL_PROMISE_HPP
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/append.hpp"
|
||||
#include "asio/awaitable.hpp"
|
||||
#include "asio/experimental/coro_traits.hpp"
|
||||
|
||||
#if defined(ASIO_HAS_STD_COROUTINE)
|
||||
# include <coroutine>
|
||||
#else // defined(ASIO_HAS_STD_COROUTINE)
|
||||
# include <experimental/coroutine>
|
||||
#endif // defined(ASIO_HAS_STD_COROUTINE)
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
#if defined(ASIO_HAS_STD_COROUTINE)
|
||||
|
||||
using std::coroutine_handle;
|
||||
using std::coroutine_traits;
|
||||
using std::suspend_never;
|
||||
using std::suspend_always;
|
||||
using std::noop_coroutine;
|
||||
|
||||
#else // defined(ASIO_HAS_STD_COROUTINE)
|
||||
|
||||
using std::experimental::coroutine_handle;
|
||||
using std::experimental::coroutine_traits;
|
||||
using std::experimental::suspend_never;
|
||||
using std::experimental::suspend_always;
|
||||
using std::experimental::noop_coroutine;
|
||||
|
||||
#endif // defined(ASIO_HAS_STD_COROUTINE)
|
||||
|
||||
struct partial_coro
|
||||
{
|
||||
coroutine_handle<void> handle{nullptr};
|
||||
};
|
||||
|
||||
template <typename Allocator>
|
||||
struct partial_promise_base
|
||||
{
|
||||
template <typename Executor, typename Token, typename... Args>
|
||||
void* operator new(std::size_t size, Executor&, Token& tk, Args&...)
|
||||
{
|
||||
return allocate_coroutine<Allocator>(size, get_associated_allocator(tk));
|
||||
}
|
||||
|
||||
void operator delete(void* raw, std::size_t size)
|
||||
{
|
||||
deallocate_coroutine<Allocator>(raw, size);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct partial_promise_base<std::allocator<void>>
|
||||
{
|
||||
};
|
||||
|
||||
template <typename Allocator>
|
||||
struct partial_promise : partial_promise_base<Allocator>
|
||||
{
|
||||
auto initial_suspend() noexcept
|
||||
{
|
||||
return asio::detail::suspend_always{};
|
||||
}
|
||||
|
||||
auto final_suspend() noexcept
|
||||
{
|
||||
struct awaitable_t
|
||||
{
|
||||
partial_promise *p;
|
||||
|
||||
constexpr bool await_ready() noexcept { return true; }
|
||||
|
||||
auto await_suspend(asio::detail::coroutine_handle<>) noexcept
|
||||
{
|
||||
p->get_return_object().handle.destroy();
|
||||
}
|
||||
|
||||
constexpr void await_resume() noexcept {}
|
||||
};
|
||||
|
||||
return awaitable_t{this};
|
||||
}
|
||||
|
||||
void return_void() {}
|
||||
|
||||
partial_coro get_return_object()
|
||||
{
|
||||
return partial_coro{coroutine_handle<partial_promise>::from_promise(*this)};
|
||||
}
|
||||
|
||||
void unhandled_exception()
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#if defined(ASIO_HAS_STD_COROUTINE)
|
||||
|
||||
namespace std {
|
||||
|
||||
template <typename Executor, typename Completion, typename... Args>
|
||||
struct coroutine_traits<
|
||||
asio::experimental::detail::partial_coro,
|
||||
Executor, Completion, Args...>
|
||||
{
|
||||
using promise_type =
|
||||
asio::experimental::detail::partial_promise<
|
||||
asio::associated_allocator_t<Completion>>;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
#else // defined(ASIO_HAS_STD_COROUTINE)
|
||||
|
||||
namespace std { namespace experimental {
|
||||
|
||||
template <typename Executor, typename Completion, typename... Args>
|
||||
struct coroutine_traits<
|
||||
asio::experimental::detail::partial_coro,
|
||||
Executor, Completion, Args...>
|
||||
{
|
||||
using promise_type =
|
||||
asio::experimental::detail::partial_promise<
|
||||
asio::associated_allocator_t<Completion>>;
|
||||
};
|
||||
|
||||
}} // namespace std::experimental
|
||||
|
||||
#endif // defined(ASIO_HAS_STD_COROUTINE)
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
template <execution::executor Executor,
|
||||
typename CompletionToken, typename... Args>
|
||||
partial_coro post_coroutine(Executor exec,
|
||||
CompletionToken token, Args&&... args) noexcept
|
||||
{
|
||||
post(exec, asio::append(std::move(token), std::move(args)...));
|
||||
co_return;
|
||||
}
|
||||
|
||||
template <detail::execution_context Context,
|
||||
typename CompletionToken, typename... Args>
|
||||
partial_coro post_coroutine(Context& ctx,
|
||||
CompletionToken token, Args&&... args) noexcept
|
||||
{
|
||||
post(ctx, asio::append(std::move(token), std::move(args)...));
|
||||
co_return;
|
||||
}
|
||||
|
||||
template <execution::executor Executor,
|
||||
typename CompletionToken, typename... Args>
|
||||
partial_coro dispatch_coroutine(Executor exec,
|
||||
CompletionToken token, Args&&... args) noexcept
|
||||
{
|
||||
dispatch(exec, asio::append(std::move(token), std::move(args)...));
|
||||
co_return;
|
||||
}
|
||||
|
||||
template <detail::execution_context Context,
|
||||
typename CompletionToken, typename... Args>
|
||||
partial_coro dispatch_coroutine(Context& ctx,
|
||||
CompletionToken token, Args &&... args) noexcept
|
||||
{
|
||||
dispatch(ctx, asio::append(std::move(token), std::move(args)...));
|
||||
co_return;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_DETAIL_PARTIAL_PROMISE_HPP
|
||||
181
include/asio/experimental/impl/as_single.hpp
Normal file
181
include/asio/experimental/impl/as_single.hpp
Normal file
@@ -0,0 +1,181 @@
|
||||
//
|
||||
// experimental/impl/as_single.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_IMPL_EXPERIMENTAL_AS_SINGLE_HPP
|
||||
#define ASIO_IMPL_EXPERIMENTAL_AS_SINGLE_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <tuple>
|
||||
#include "asio/associator.hpp"
|
||||
#include "asio/async_result.hpp"
|
||||
#include "asio/detail/handler_cont_helpers.hpp"
|
||||
#include "asio/detail/initiation_base.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
// Class to adapt a as_single_t as a completion handler.
|
||||
template <typename Handler>
|
||||
class as_single_handler
|
||||
{
|
||||
public:
|
||||
typedef void result_type;
|
||||
|
||||
template <typename CompletionToken>
|
||||
as_single_handler(as_single_t<CompletionToken> e)
|
||||
: handler_(static_cast<CompletionToken&&>(e.token_))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename RedirectedHandler>
|
||||
as_single_handler(RedirectedHandler&& h)
|
||||
: handler_(static_cast<RedirectedHandler&&>(h))
|
||||
{
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
static_cast<Handler&&>(handler_)();
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
void operator()(Arg&& arg)
|
||||
{
|
||||
static_cast<Handler&&>(handler_)(static_cast<Arg&&>(arg));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void operator()(Args&&... args)
|
||||
{
|
||||
static_cast<Handler&&>(handler_)(
|
||||
std::make_tuple(static_cast<Args&&>(args)...));
|
||||
}
|
||||
|
||||
//private:
|
||||
Handler handler_;
|
||||
};
|
||||
|
||||
template <typename Handler>
|
||||
inline bool asio_handler_is_continuation(
|
||||
as_single_handler<Handler>* this_handler)
|
||||
{
|
||||
return asio_handler_cont_helpers::is_continuation(
|
||||
this_handler->handler_);
|
||||
}
|
||||
|
||||
template <typename Signature>
|
||||
struct as_single_signature
|
||||
{
|
||||
typedef Signature type;
|
||||
};
|
||||
|
||||
template <typename R>
|
||||
struct as_single_signature<R()>
|
||||
{
|
||||
typedef R type();
|
||||
};
|
||||
|
||||
template <typename R, typename Arg>
|
||||
struct as_single_signature<R(Arg)>
|
||||
{
|
||||
typedef R type(Arg);
|
||||
};
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct as_single_signature<R(Args...)>
|
||||
{
|
||||
typedef R type(std::tuple<decay_t<Args>...>);
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
template <typename CompletionToken, typename Signature>
|
||||
struct async_result<experimental::as_single_t<CompletionToken>, Signature>
|
||||
{
|
||||
template <typename Initiation>
|
||||
struct init_wrapper : detail::initiation_base<Initiation>
|
||||
{
|
||||
using detail::initiation_base<Initiation>::initiation_base;
|
||||
|
||||
template <typename Handler, typename... Args>
|
||||
void operator()(Handler&& handler, Args&&... args) &&
|
||||
{
|
||||
static_cast<Initiation&&>(*this)(
|
||||
experimental::detail::as_single_handler<decay_t<Handler>>(
|
||||
static_cast<Handler&&>(handler)),
|
||||
static_cast<Args&&>(args)...);
|
||||
}
|
||||
|
||||
template <typename Handler, typename... Args>
|
||||
void operator()(Handler&& handler, Args&&... args) const &
|
||||
{
|
||||
static_cast<const Initiation&>(*this)(
|
||||
experimental::detail::as_single_handler<decay_t<Handler>>(
|
||||
static_cast<Handler&&>(handler)),
|
||||
static_cast<Args&&>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Initiation, typename RawCompletionToken, typename... Args>
|
||||
static auto initiate(Initiation&& initiation,
|
||||
RawCompletionToken&& token, Args&&... args)
|
||||
-> decltype(
|
||||
async_initiate<CompletionToken,
|
||||
typename experimental::detail::as_single_signature<Signature>::type>(
|
||||
init_wrapper<decay_t<Initiation>>(
|
||||
static_cast<Initiation&&>(initiation)),
|
||||
token.token_, static_cast<Args&&>(args)...))
|
||||
{
|
||||
return async_initiate<CompletionToken,
|
||||
typename experimental::detail::as_single_signature<Signature>::type>(
|
||||
init_wrapper<decay_t<Initiation>>(
|
||||
static_cast<Initiation&&>(initiation)),
|
||||
token.token_, static_cast<Args&&>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <template <typename, typename> class Associator,
|
||||
typename Handler, typename DefaultCandidate>
|
||||
struct associator<Associator,
|
||||
experimental::detail::as_single_handler<Handler>, DefaultCandidate>
|
||||
: Associator<Handler, DefaultCandidate>
|
||||
{
|
||||
static typename Associator<Handler, DefaultCandidate>::type get(
|
||||
const experimental::detail::as_single_handler<Handler>& h) noexcept
|
||||
{
|
||||
return Associator<Handler, DefaultCandidate>::get(h.handler_);
|
||||
}
|
||||
|
||||
static auto get(const experimental::detail::as_single_handler<Handler>& h,
|
||||
const DefaultCandidate& c) noexcept
|
||||
-> decltype(Associator<Handler, DefaultCandidate>::get(h.handler_, c))
|
||||
{
|
||||
return Associator<Handler, DefaultCandidate>::get(h.handler_, c);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_IMPL_EXPERIMENTAL_AS_SINGLE_HPP
|
||||
61
include/asio/experimental/impl/channel_error.ipp
Normal file
61
include/asio/experimental/impl/channel_error.ipp
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// experimental/impl/channel_error.ipp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_IMPL_CHANNEL_ERROR_IPP
|
||||
#define ASIO_EXPERIMENTAL_IMPL_CHANNEL_ERROR_IPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/experimental/channel_error.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace error {
|
||||
namespace detail {
|
||||
|
||||
class channel_category : public asio::error_category
|
||||
{
|
||||
public:
|
||||
const char* name() const noexcept
|
||||
{
|
||||
return "asio.channel";
|
||||
}
|
||||
|
||||
std::string message(int value) const
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case channel_closed: return "Channel closed";
|
||||
case channel_cancelled: return "Channel cancelled";
|
||||
default: return "asio.channel error";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
const asio::error_category& get_channel_category()
|
||||
{
|
||||
static detail::channel_category instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace error
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_IMPL_CHANNEL_ERROR_IPP
|
||||
1222
include/asio/experimental/impl/coro.hpp
Normal file
1222
include/asio/experimental/impl/coro.hpp
Normal file
File diff suppressed because it is too large
Load Diff
789
include/asio/experimental/impl/parallel_group.hpp
Normal file
789
include/asio/experimental/impl/parallel_group.hpp
Normal file
@@ -0,0 +1,789 @@
|
||||
//
|
||||
// experimental/impl/parallel_group.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_IMPL_EXPERIMENTAL_PARALLEL_GROUP_HPP
|
||||
#define ASIO_IMPL_EXPERIMENTAL_PARALLEL_GROUP_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <tuple>
|
||||
#include "asio/associated_cancellation_slot.hpp"
|
||||
#include "asio/detail/recycling_allocator.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/dispatch.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
// Stores the result from an individual asynchronous operation.
|
||||
template <typename T, typename = void>
|
||||
struct parallel_group_op_result
|
||||
{
|
||||
public:
|
||||
parallel_group_op_result()
|
||||
: has_value_(false)
|
||||
{
|
||||
}
|
||||
|
||||
parallel_group_op_result(parallel_group_op_result&& other)
|
||||
: has_value_(other.has_value_)
|
||||
{
|
||||
if (has_value_)
|
||||
new (&u_.value_) T(std::move(other.get()));
|
||||
}
|
||||
|
||||
~parallel_group_op_result()
|
||||
{
|
||||
if (has_value_)
|
||||
u_.value_.~T();
|
||||
}
|
||||
|
||||
T& get() noexcept
|
||||
{
|
||||
return u_.value_;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void emplace(Args&&... args)
|
||||
{
|
||||
new (&u_.value_) T(std::forward<Args>(args)...);
|
||||
has_value_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
union u
|
||||
{
|
||||
u() {}
|
||||
~u() {}
|
||||
char c_;
|
||||
T value_;
|
||||
} u_;
|
||||
bool has_value_;
|
||||
};
|
||||
|
||||
// Proxy completion handler for the group of parallel operations. Unpacks and
|
||||
// concatenates the individual operations' results, and invokes the user's
|
||||
// completion handler.
|
||||
template <typename Handler, typename... Ops>
|
||||
struct parallel_group_completion_handler
|
||||
{
|
||||
typedef decay_t<
|
||||
prefer_result_t<
|
||||
associated_executor_t<Handler>,
|
||||
execution::outstanding_work_t::tracked_t
|
||||
>
|
||||
> executor_type;
|
||||
|
||||
parallel_group_completion_handler(Handler&& h)
|
||||
: handler_(std::move(h)),
|
||||
executor_(
|
||||
asio::prefer(
|
||||
asio::get_associated_executor(handler_),
|
||||
execution::outstanding_work.tracked))
|
||||
{
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept
|
||||
{
|
||||
return executor_;
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
this->invoke(asio::detail::make_index_sequence<sizeof...(Ops)>());
|
||||
}
|
||||
|
||||
template <std::size_t... I>
|
||||
void invoke(asio::detail::index_sequence<I...>)
|
||||
{
|
||||
this->invoke(std::tuple_cat(std::move(std::get<I>(args_).get())...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void invoke(std::tuple<Args...>&& args)
|
||||
{
|
||||
this->invoke(std::move(args),
|
||||
asio::detail::index_sequence_for<Args...>());
|
||||
}
|
||||
|
||||
template <typename... Args, std::size_t... I>
|
||||
void invoke(std::tuple<Args...>&& args,
|
||||
asio::detail::index_sequence<I...>)
|
||||
{
|
||||
std::move(handler_)(completion_order_, std::move(std::get<I>(args))...);
|
||||
}
|
||||
|
||||
Handler handler_;
|
||||
executor_type executor_;
|
||||
std::array<std::size_t, sizeof...(Ops)> completion_order_{};
|
||||
std::tuple<
|
||||
parallel_group_op_result<
|
||||
typename parallel_op_signature_as_tuple<
|
||||
completion_signature_of_t<Ops>
|
||||
>::type
|
||||
>...
|
||||
> args_{};
|
||||
};
|
||||
|
||||
// Shared state for the parallel group.
|
||||
template <typename Condition, typename Handler, typename... Ops>
|
||||
struct parallel_group_state
|
||||
{
|
||||
parallel_group_state(Condition&& c, Handler&& h)
|
||||
: cancellation_condition_(std::move(c)),
|
||||
handler_(std::move(h))
|
||||
{
|
||||
}
|
||||
|
||||
// The number of operations that have completed so far. Used to determine the
|
||||
// order of completion.
|
||||
std::atomic<unsigned int> completed_{0};
|
||||
|
||||
// The non-none cancellation type that resulted from a cancellation condition.
|
||||
// Stored here for use by the group's initiating function.
|
||||
std::atomic<cancellation_type_t> cancel_type_{cancellation_type::none};
|
||||
|
||||
// The number of cancellations that have been requested, either on completion
|
||||
// of the operations within the group, or via the cancellation slot for the
|
||||
// group operation. Initially set to the number of operations to prevent
|
||||
// cancellation signals from being emitted until after all of the group's
|
||||
// operations' initiating functions have completed.
|
||||
std::atomic<unsigned int> cancellations_requested_{sizeof...(Ops)};
|
||||
|
||||
// The number of operations that are yet to complete. Used to determine when
|
||||
// it is safe to invoke the user's completion handler.
|
||||
std::atomic<unsigned int> outstanding_{sizeof...(Ops)};
|
||||
|
||||
// The cancellation signals for each operation in the group.
|
||||
asio::cancellation_signal cancellation_signals_[sizeof...(Ops)];
|
||||
|
||||
// The cancellation condition is used to determine whether the results from an
|
||||
// individual operation warrant a cancellation request for the whole group.
|
||||
Condition cancellation_condition_;
|
||||
|
||||
// The proxy handler to be invoked once all operations in the group complete.
|
||||
parallel_group_completion_handler<Handler, Ops...> handler_;
|
||||
};
|
||||
|
||||
// Handler for an individual operation within the parallel group.
|
||||
template <std::size_t I, typename Condition, typename Handler, typename... Ops>
|
||||
struct parallel_group_op_handler
|
||||
{
|
||||
typedef asio::cancellation_slot cancellation_slot_type;
|
||||
|
||||
parallel_group_op_handler(
|
||||
std::shared_ptr<parallel_group_state<Condition, Handler, Ops...>> state)
|
||||
: state_(std::move(state))
|
||||
{
|
||||
}
|
||||
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept
|
||||
{
|
||||
return state_->cancellation_signals_[I].slot();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void operator()(Args... args)
|
||||
{
|
||||
// Capture this operation into the completion order.
|
||||
state_->handler_.completion_order_[state_->completed_++] = I;
|
||||
|
||||
// Determine whether the results of this operation require cancellation of
|
||||
// the whole group.
|
||||
cancellation_type_t cancel_type = state_->cancellation_condition_(args...);
|
||||
|
||||
// Capture the result of the operation into the proxy completion handler.
|
||||
std::get<I>(state_->handler_.args_).emplace(std::move(args)...);
|
||||
|
||||
if (cancel_type != cancellation_type::none)
|
||||
{
|
||||
// Save the type for potential use by the group's initiating function.
|
||||
state_->cancel_type_ = cancel_type;
|
||||
|
||||
// If we are the first operation to request cancellation, emit a signal
|
||||
// for each operation in the group.
|
||||
if (state_->cancellations_requested_++ == 0)
|
||||
for (std::size_t i = 0; i < sizeof...(Ops); ++i)
|
||||
if (i != I)
|
||||
state_->cancellation_signals_[i].emit(cancel_type);
|
||||
}
|
||||
|
||||
// If this is the last outstanding operation, invoke the user's handler.
|
||||
if (--state_->outstanding_ == 0)
|
||||
asio::dispatch(std::move(state_->handler_));
|
||||
}
|
||||
|
||||
std::shared_ptr<parallel_group_state<Condition, Handler, Ops...>> state_;
|
||||
};
|
||||
|
||||
// Handler for an individual operation within the parallel group that has an
|
||||
// explicitly specified executor.
|
||||
template <typename Executor, std::size_t I,
|
||||
typename Condition, typename Handler, typename... Ops>
|
||||
struct parallel_group_op_handler_with_executor :
|
||||
parallel_group_op_handler<I, Condition, Handler, Ops...>
|
||||
{
|
||||
typedef parallel_group_op_handler<I, Condition, Handler, Ops...> base_type;
|
||||
typedef asio::cancellation_slot cancellation_slot_type;
|
||||
typedef Executor executor_type;
|
||||
|
||||
parallel_group_op_handler_with_executor(
|
||||
std::shared_ptr<parallel_group_state<Condition, Handler, Ops...>> state,
|
||||
executor_type ex)
|
||||
: parallel_group_op_handler<I, Condition, Handler, Ops...>(std::move(state))
|
||||
{
|
||||
cancel_proxy_ =
|
||||
&this->state_->cancellation_signals_[I].slot().template
|
||||
emplace<cancel_proxy>(this->state_, std::move(ex));
|
||||
}
|
||||
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept
|
||||
{
|
||||
return cancel_proxy_->signal_.slot();
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept
|
||||
{
|
||||
return cancel_proxy_->executor_;
|
||||
}
|
||||
|
||||
// Proxy handler that forwards the emitted signal to the correct executor.
|
||||
struct cancel_proxy
|
||||
{
|
||||
cancel_proxy(
|
||||
std::shared_ptr<parallel_group_state<
|
||||
Condition, Handler, Ops...>> state,
|
||||
executor_type ex)
|
||||
: state_(std::move(state)),
|
||||
executor_(std::move(ex))
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(cancellation_type_t type)
|
||||
{
|
||||
if (auto state = state_.lock())
|
||||
{
|
||||
asio::cancellation_signal* sig = &signal_;
|
||||
asio::dispatch(executor_,
|
||||
[state, sig, type]{ sig->emit(type); });
|
||||
}
|
||||
}
|
||||
|
||||
std::weak_ptr<parallel_group_state<Condition, Handler, Ops...>> state_;
|
||||
asio::cancellation_signal signal_;
|
||||
executor_type executor_;
|
||||
};
|
||||
|
||||
cancel_proxy* cancel_proxy_;
|
||||
};
|
||||
|
||||
// Helper to launch an operation using the correct executor, if any.
|
||||
template <std::size_t I, typename Op, typename = void>
|
||||
struct parallel_group_op_launcher
|
||||
{
|
||||
template <typename Condition, typename Handler, typename... Ops>
|
||||
static void launch(Op& op,
|
||||
const std::shared_ptr<parallel_group_state<
|
||||
Condition, Handler, Ops...>>& state)
|
||||
{
|
||||
typedef associated_executor_t<Op> ex_type;
|
||||
ex_type ex = asio::get_associated_executor(op);
|
||||
std::move(op)(
|
||||
parallel_group_op_handler_with_executor<ex_type, I,
|
||||
Condition, Handler, Ops...>(state, std::move(ex)));
|
||||
}
|
||||
};
|
||||
|
||||
// Specialised launcher for operations that specify no executor.
|
||||
template <std::size_t I, typename Op>
|
||||
struct parallel_group_op_launcher<I, Op,
|
||||
enable_if_t<
|
||||
is_same<
|
||||
typename associated_executor<
|
||||
Op>::asio_associated_executor_is_unspecialised,
|
||||
void
|
||||
>::value
|
||||
>>
|
||||
{
|
||||
template <typename Condition, typename Handler, typename... Ops>
|
||||
static void launch(Op& op,
|
||||
const std::shared_ptr<parallel_group_state<
|
||||
Condition, Handler, Ops...>>& state)
|
||||
{
|
||||
std::move(op)(
|
||||
parallel_group_op_handler<I, Condition, Handler, Ops...>(state));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Condition, typename Handler, typename... Ops>
|
||||
struct parallel_group_cancellation_handler
|
||||
{
|
||||
parallel_group_cancellation_handler(
|
||||
std::shared_ptr<parallel_group_state<Condition, Handler, Ops...>> state)
|
||||
: state_(std::move(state))
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(cancellation_type_t cancel_type)
|
||||
{
|
||||
// If we are the first place to request cancellation, i.e. no operation has
|
||||
// yet completed and requested cancellation, emit a signal for each
|
||||
// operation in the group.
|
||||
if (cancel_type != cancellation_type::none)
|
||||
if (auto state = state_.lock())
|
||||
if (state->cancellations_requested_++ == 0)
|
||||
for (std::size_t i = 0; i < sizeof...(Ops); ++i)
|
||||
state->cancellation_signals_[i].emit(cancel_type);
|
||||
}
|
||||
|
||||
std::weak_ptr<parallel_group_state<Condition, Handler, Ops...>> state_;
|
||||
};
|
||||
|
||||
template <typename Condition, typename Handler,
|
||||
typename... Ops, std::size_t... I>
|
||||
void parallel_group_launch(Condition cancellation_condition, Handler handler,
|
||||
std::tuple<Ops...>& ops, asio::detail::index_sequence<I...>)
|
||||
{
|
||||
// Get the user's completion handler's cancellation slot, so that we can allow
|
||||
// cancellation of the entire group.
|
||||
associated_cancellation_slot_t<Handler> slot
|
||||
= asio::get_associated_cancellation_slot(handler);
|
||||
|
||||
// Create the shared state for the operation.
|
||||
typedef parallel_group_state<Condition, Handler, Ops...> state_type;
|
||||
std::shared_ptr<state_type> state = std::allocate_shared<state_type>(
|
||||
asio::detail::recycling_allocator<state_type,
|
||||
asio::detail::thread_info_base::parallel_group_tag>(),
|
||||
std::move(cancellation_condition), std::move(handler));
|
||||
|
||||
// Initiate each individual operation in the group.
|
||||
int fold[] = { 0,
|
||||
( parallel_group_op_launcher<I, Ops>::launch(std::get<I>(ops), state),
|
||||
0 )...
|
||||
};
|
||||
(void)fold;
|
||||
|
||||
// Check if any of the operations has already requested cancellation, and if
|
||||
// so, emit a signal for each operation in the group.
|
||||
if ((state->cancellations_requested_ -= sizeof...(Ops)) > 0)
|
||||
for (auto& signal : state->cancellation_signals_)
|
||||
signal.emit(state->cancel_type_);
|
||||
|
||||
// Register a handler with the user's completion handler's cancellation slot.
|
||||
if (slot.is_connected())
|
||||
slot.template emplace<
|
||||
parallel_group_cancellation_handler<
|
||||
Condition, Handler, Ops...>>(state);
|
||||
}
|
||||
|
||||
// Proxy completion handler for the ranged group of parallel operations.
|
||||
// Unpacks and recombines the individual operations' results, and invokes the
|
||||
// user's completion handler.
|
||||
template <typename Handler, typename Op, typename Allocator>
|
||||
struct ranged_parallel_group_completion_handler
|
||||
{
|
||||
typedef decay_t<
|
||||
prefer_result_t<
|
||||
associated_executor_t<Handler>,
|
||||
execution::outstanding_work_t::tracked_t
|
||||
>
|
||||
> executor_type;
|
||||
|
||||
typedef typename parallel_op_signature_as_tuple<
|
||||
completion_signature_of_t<Op>
|
||||
>::type op_tuple_type;
|
||||
|
||||
typedef parallel_group_op_result<op_tuple_type> op_result_type;
|
||||
|
||||
ranged_parallel_group_completion_handler(Handler&& h,
|
||||
std::size_t size, const Allocator& allocator)
|
||||
: handler_(std::move(h)),
|
||||
executor_(
|
||||
asio::prefer(
|
||||
asio::get_associated_executor(handler_),
|
||||
execution::outstanding_work.tracked)),
|
||||
allocator_(allocator),
|
||||
completion_order_(size, 0,
|
||||
ASIO_REBIND_ALLOC(Allocator, std::size_t)(allocator)),
|
||||
args_(ASIO_REBIND_ALLOC(Allocator, op_result_type)(allocator))
|
||||
{
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
args_.emplace_back();
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept
|
||||
{
|
||||
return executor_;
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
this->invoke(
|
||||
asio::detail::make_index_sequence<
|
||||
std::tuple_size<op_tuple_type>::value>());
|
||||
}
|
||||
|
||||
template <std::size_t... I>
|
||||
void invoke(asio::detail::index_sequence<I...>)
|
||||
{
|
||||
typedef typename parallel_op_signature_as_tuple<
|
||||
typename ranged_parallel_group_signature<
|
||||
completion_signature_of_t<Op>,
|
||||
Allocator
|
||||
>::raw_type
|
||||
>::type vectors_type;
|
||||
|
||||
// Construct all result vectors using the supplied allocator.
|
||||
vectors_type vectors{
|
||||
typename std::tuple_element<I, vectors_type>::type(
|
||||
ASIO_REBIND_ALLOC(Allocator, int)(allocator_))...};
|
||||
|
||||
// Reserve sufficient space in each of the result vectors.
|
||||
int reserve_fold[] = { 0,
|
||||
( std::get<I>(vectors).reserve(completion_order_.size()),
|
||||
0 )...
|
||||
};
|
||||
(void)reserve_fold;
|
||||
|
||||
// Copy the results from all operations into the result vectors.
|
||||
for (std::size_t idx = 0; idx < completion_order_.size(); ++idx)
|
||||
{
|
||||
int pushback_fold[] = { 0,
|
||||
( std::get<I>(vectors).push_back(
|
||||
std::move(std::get<I>(args_[idx].get()))),
|
||||
0 )...
|
||||
};
|
||||
(void)pushback_fold;
|
||||
}
|
||||
|
||||
std::move(handler_)(std::move(completion_order_),
|
||||
std::move(std::get<I>(vectors))...);
|
||||
}
|
||||
|
||||
Handler handler_;
|
||||
executor_type executor_;
|
||||
Allocator allocator_;
|
||||
std::vector<std::size_t,
|
||||
ASIO_REBIND_ALLOC(Allocator, std::size_t)> completion_order_;
|
||||
std::deque<op_result_type,
|
||||
ASIO_REBIND_ALLOC(Allocator, op_result_type)> args_;
|
||||
};
|
||||
|
||||
// Shared state for the parallel group.
|
||||
template <typename Condition, typename Handler, typename Op, typename Allocator>
|
||||
struct ranged_parallel_group_state
|
||||
{
|
||||
ranged_parallel_group_state(Condition&& c, Handler&& h,
|
||||
std::size_t size, const Allocator& allocator)
|
||||
: cancellations_requested_(size),
|
||||
outstanding_(size),
|
||||
cancellation_signals_(
|
||||
ASIO_REBIND_ALLOC(Allocator,
|
||||
asio::cancellation_signal)(allocator)),
|
||||
cancellation_condition_(std::move(c)),
|
||||
handler_(std::move(h), size, allocator)
|
||||
{
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
cancellation_signals_.emplace_back();
|
||||
}
|
||||
|
||||
// The number of operations that have completed so far. Used to determine the
|
||||
// order of completion.
|
||||
std::atomic<unsigned int> completed_{0};
|
||||
|
||||
// The non-none cancellation type that resulted from a cancellation condition.
|
||||
// Stored here for use by the group's initiating function.
|
||||
std::atomic<cancellation_type_t> cancel_type_{cancellation_type::none};
|
||||
|
||||
// The number of cancellations that have been requested, either on completion
|
||||
// of the operations within the group, or via the cancellation slot for the
|
||||
// group operation. Initially set to the number of operations to prevent
|
||||
// cancellation signals from being emitted until after all of the group's
|
||||
// operations' initiating functions have completed.
|
||||
std::atomic<unsigned int> cancellations_requested_;
|
||||
|
||||
// The number of operations that are yet to complete. Used to determine when
|
||||
// it is safe to invoke the user's completion handler.
|
||||
std::atomic<unsigned int> outstanding_;
|
||||
|
||||
// The cancellation signals for each operation in the group.
|
||||
std::deque<asio::cancellation_signal,
|
||||
ASIO_REBIND_ALLOC(Allocator, asio::cancellation_signal)>
|
||||
cancellation_signals_;
|
||||
|
||||
// The cancellation condition is used to determine whether the results from an
|
||||
// individual operation warrant a cancellation request for the whole group.
|
||||
Condition cancellation_condition_;
|
||||
|
||||
// The proxy handler to be invoked once all operations in the group complete.
|
||||
ranged_parallel_group_completion_handler<Handler, Op, Allocator> handler_;
|
||||
};
|
||||
|
||||
// Handler for an individual operation within the parallel group.
|
||||
template <typename Condition, typename Handler, typename Op, typename Allocator>
|
||||
struct ranged_parallel_group_op_handler
|
||||
{
|
||||
typedef asio::cancellation_slot cancellation_slot_type;
|
||||
|
||||
ranged_parallel_group_op_handler(
|
||||
std::shared_ptr<ranged_parallel_group_state<
|
||||
Condition, Handler, Op, Allocator>> state,
|
||||
std::size_t idx)
|
||||
: state_(std::move(state)),
|
||||
idx_(idx)
|
||||
{
|
||||
}
|
||||
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept
|
||||
{
|
||||
return state_->cancellation_signals_[idx_].slot();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void operator()(Args... args)
|
||||
{
|
||||
// Capture this operation into the completion order.
|
||||
state_->handler_.completion_order_[state_->completed_++] = idx_;
|
||||
|
||||
// Determine whether the results of this operation require cancellation of
|
||||
// the whole group.
|
||||
cancellation_type_t cancel_type = state_->cancellation_condition_(args...);
|
||||
|
||||
// Capture the result of the operation into the proxy completion handler.
|
||||
state_->handler_.args_[idx_].emplace(std::move(args)...);
|
||||
|
||||
if (cancel_type != cancellation_type::none)
|
||||
{
|
||||
// Save the type for potential use by the group's initiating function.
|
||||
state_->cancel_type_ = cancel_type;
|
||||
|
||||
// If we are the first operation to request cancellation, emit a signal
|
||||
// for each operation in the group.
|
||||
if (state_->cancellations_requested_++ == 0)
|
||||
for (std::size_t i = 0; i < state_->cancellation_signals_.size(); ++i)
|
||||
if (i != idx_)
|
||||
state_->cancellation_signals_[i].emit(cancel_type);
|
||||
}
|
||||
|
||||
// If this is the last outstanding operation, invoke the user's handler.
|
||||
if (--state_->outstanding_ == 0)
|
||||
asio::dispatch(std::move(state_->handler_));
|
||||
}
|
||||
|
||||
std::shared_ptr<ranged_parallel_group_state<
|
||||
Condition, Handler, Op, Allocator>> state_;
|
||||
std::size_t idx_;
|
||||
};
|
||||
|
||||
// Handler for an individual operation within the parallel group that has an
|
||||
// explicitly specified executor.
|
||||
template <typename Executor, typename Condition,
|
||||
typename Handler, typename Op, typename Allocator>
|
||||
struct ranged_parallel_group_op_handler_with_executor :
|
||||
ranged_parallel_group_op_handler<Condition, Handler, Op, Allocator>
|
||||
{
|
||||
typedef ranged_parallel_group_op_handler<
|
||||
Condition, Handler, Op, Allocator> base_type;
|
||||
typedef asio::cancellation_slot cancellation_slot_type;
|
||||
typedef Executor executor_type;
|
||||
|
||||
ranged_parallel_group_op_handler_with_executor(
|
||||
std::shared_ptr<ranged_parallel_group_state<
|
||||
Condition, Handler, Op, Allocator>> state,
|
||||
executor_type ex, std::size_t idx)
|
||||
: ranged_parallel_group_op_handler<Condition, Handler, Op, Allocator>(
|
||||
std::move(state), idx)
|
||||
{
|
||||
cancel_proxy_ =
|
||||
&this->state_->cancellation_signals_[idx].slot().template
|
||||
emplace<cancel_proxy>(this->state_, std::move(ex));
|
||||
}
|
||||
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept
|
||||
{
|
||||
return cancel_proxy_->signal_.slot();
|
||||
}
|
||||
|
||||
executor_type get_executor() const noexcept
|
||||
{
|
||||
return cancel_proxy_->executor_;
|
||||
}
|
||||
|
||||
// Proxy handler that forwards the emitted signal to the correct executor.
|
||||
struct cancel_proxy
|
||||
{
|
||||
cancel_proxy(
|
||||
std::shared_ptr<ranged_parallel_group_state<
|
||||
Condition, Handler, Op, Allocator>> state,
|
||||
executor_type ex)
|
||||
: state_(std::move(state)),
|
||||
executor_(std::move(ex))
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(cancellation_type_t type)
|
||||
{
|
||||
if (auto state = state_.lock())
|
||||
{
|
||||
asio::cancellation_signal* sig = &signal_;
|
||||
asio::dispatch(executor_,
|
||||
[state, sig, type]{ sig->emit(type); });
|
||||
}
|
||||
}
|
||||
|
||||
std::weak_ptr<ranged_parallel_group_state<
|
||||
Condition, Handler, Op, Allocator>> state_;
|
||||
asio::cancellation_signal signal_;
|
||||
executor_type executor_;
|
||||
};
|
||||
|
||||
cancel_proxy* cancel_proxy_;
|
||||
};
|
||||
|
||||
template <typename Condition, typename Handler, typename Op, typename Allocator>
|
||||
struct ranged_parallel_group_cancellation_handler
|
||||
{
|
||||
ranged_parallel_group_cancellation_handler(
|
||||
std::shared_ptr<ranged_parallel_group_state<
|
||||
Condition, Handler, Op, Allocator>> state)
|
||||
: state_(std::move(state))
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(cancellation_type_t cancel_type)
|
||||
{
|
||||
// If we are the first place to request cancellation, i.e. no operation has
|
||||
// yet completed and requested cancellation, emit a signal for each
|
||||
// operation in the group.
|
||||
if (cancel_type != cancellation_type::none)
|
||||
if (auto state = state_.lock())
|
||||
if (state->cancellations_requested_++ == 0)
|
||||
for (std::size_t i = 0; i < state->cancellation_signals_.size(); ++i)
|
||||
state->cancellation_signals_[i].emit(cancel_type);
|
||||
}
|
||||
|
||||
std::weak_ptr<ranged_parallel_group_state<
|
||||
Condition, Handler, Op, Allocator>> state_;
|
||||
};
|
||||
|
||||
template <typename Condition, typename Handler,
|
||||
typename Range, typename Allocator>
|
||||
void ranged_parallel_group_launch(Condition cancellation_condition,
|
||||
Handler handler, Range&& range, const Allocator& allocator)
|
||||
{
|
||||
// Get the user's completion handler's cancellation slot, so that we can allow
|
||||
// cancellation of the entire group.
|
||||
associated_cancellation_slot_t<Handler> slot
|
||||
= asio::get_associated_cancellation_slot(handler);
|
||||
|
||||
// The type of the asynchronous operation.
|
||||
typedef decay_t<decltype(*declval<typename Range::iterator>())> op_type;
|
||||
|
||||
// Create the shared state for the operation.
|
||||
typedef ranged_parallel_group_state<Condition,
|
||||
Handler, op_type, Allocator> state_type;
|
||||
std::shared_ptr<state_type> state = std::allocate_shared<state_type>(
|
||||
asio::detail::recycling_allocator<state_type,
|
||||
asio::detail::thread_info_base::parallel_group_tag>(),
|
||||
std::move(cancellation_condition),
|
||||
std::move(handler), range.size(), allocator);
|
||||
|
||||
std::size_t idx = 0;
|
||||
std::size_t range_size = range.size();
|
||||
for (auto&& op : std::forward<Range>(range))
|
||||
{
|
||||
typedef associated_executor_t<op_type> ex_type;
|
||||
ex_type ex = asio::get_associated_executor(op);
|
||||
std::move(op)(
|
||||
ranged_parallel_group_op_handler_with_executor<
|
||||
ex_type, Condition, Handler, op_type, Allocator>(
|
||||
state, std::move(ex), idx++));
|
||||
}
|
||||
|
||||
// Check if any of the operations has already requested cancellation, and if
|
||||
// so, emit a signal for each operation in the group.
|
||||
if ((state->cancellations_requested_ -= range_size) > 0)
|
||||
for (auto& signal : state->cancellation_signals_)
|
||||
signal.emit(state->cancel_type_);
|
||||
|
||||
// Register a handler with the user's completion handler's cancellation slot.
|
||||
if (slot.is_connected())
|
||||
slot.template emplace<
|
||||
ranged_parallel_group_cancellation_handler<
|
||||
Condition, Handler, op_type, Allocator>>(state);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
|
||||
template <template <typename, typename> class Associator,
|
||||
typename Handler, typename... Ops, typename DefaultCandidate>
|
||||
struct associator<Associator,
|
||||
experimental::detail::parallel_group_completion_handler<Handler, Ops...>,
|
||||
DefaultCandidate>
|
||||
: Associator<Handler, DefaultCandidate>
|
||||
{
|
||||
static typename Associator<Handler, DefaultCandidate>::type get(
|
||||
const experimental::detail::parallel_group_completion_handler<
|
||||
Handler, Ops...>& h) noexcept
|
||||
{
|
||||
return Associator<Handler, DefaultCandidate>::get(h.handler_);
|
||||
}
|
||||
|
||||
static auto get(
|
||||
const experimental::detail::parallel_group_completion_handler<
|
||||
Handler, Ops...>& h,
|
||||
const DefaultCandidate& c) noexcept
|
||||
-> decltype(Associator<Handler, DefaultCandidate>::get(h.handler_, c))
|
||||
{
|
||||
return Associator<Handler, DefaultCandidate>::get(h.handler_, c);
|
||||
}
|
||||
};
|
||||
|
||||
template <template <typename, typename> class Associator, typename Handler,
|
||||
typename Op, typename Allocator, typename DefaultCandidate>
|
||||
struct associator<Associator,
|
||||
experimental::detail::ranged_parallel_group_completion_handler<
|
||||
Handler, Op, Allocator>,
|
||||
DefaultCandidate>
|
||||
: Associator<Handler, DefaultCandidate>
|
||||
{
|
||||
static typename Associator<Handler, DefaultCandidate>::type get(
|
||||
const experimental::detail::ranged_parallel_group_completion_handler<
|
||||
Handler, Op, Allocator>& h) noexcept
|
||||
{
|
||||
return Associator<Handler, DefaultCandidate>::get(h.handler_);
|
||||
}
|
||||
|
||||
static auto get(
|
||||
const experimental::detail::ranged_parallel_group_completion_handler<
|
||||
Handler, Op, Allocator>& h,
|
||||
const DefaultCandidate& c) noexcept
|
||||
-> decltype(Associator<Handler, DefaultCandidate>::get(h.handler_, c))
|
||||
{
|
||||
return Associator<Handler, DefaultCandidate>::get(h.handler_, c);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_IMPL_EXPERIMENTAL_PARALLEL_GROUP_HPP
|
||||
255
include/asio/experimental/impl/promise.hpp
Normal file
255
include/asio/experimental/impl/promise.hpp
Normal file
@@ -0,0 +1,255 @@
|
||||
//
|
||||
// experimental/impl/promise.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#ifndef ASIO_EXPERIMENTAL_IMPL_PROMISE_HPP
|
||||
#define ASIO_EXPERIMENTAL_IMPL_PROMISE_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/cancellation_signal.hpp"
|
||||
#include "asio/detail/utility.hpp"
|
||||
#include "asio/error.hpp"
|
||||
#include "asio/system_error.hpp"
|
||||
#include <tuple>
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
|
||||
template<typename Signature = void(),
|
||||
typename Executor = asio::any_io_executor,
|
||||
typename Allocator = std::allocator<void>>
|
||||
struct promise;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename Signature, typename Executor, typename Allocator>
|
||||
struct promise_impl;
|
||||
|
||||
template<typename... Ts, typename Executor, typename Allocator>
|
||||
struct promise_impl<void(Ts...), Executor, Allocator>
|
||||
{
|
||||
using result_type = std::tuple<Ts...>;
|
||||
|
||||
promise_impl(Allocator allocator, Executor executor)
|
||||
: allocator(std::move(allocator)), executor(std::move(executor))
|
||||
{
|
||||
}
|
||||
|
||||
promise_impl(const promise_impl&) = delete;
|
||||
|
||||
~promise_impl()
|
||||
{
|
||||
if (completion)
|
||||
this->cancel_();
|
||||
|
||||
if (done)
|
||||
reinterpret_cast<result_type*>(&result)->~result_type();
|
||||
}
|
||||
|
||||
aligned_storage_t<sizeof(result_type), alignof(result_type)> result;
|
||||
std::atomic<bool> done{false};
|
||||
cancellation_signal cancel;
|
||||
Allocator allocator;
|
||||
Executor executor;
|
||||
|
||||
template<typename Func, std::size_t... Idx>
|
||||
void apply_impl(Func f, asio::detail::index_sequence<Idx...>)
|
||||
{
|
||||
auto& result_type = *reinterpret_cast<promise_impl::result_type*>(&result);
|
||||
f(std::get<Idx>(std::move(result_type))...);
|
||||
}
|
||||
|
||||
using allocator_type = Allocator;
|
||||
allocator_type get_allocator() {return allocator;}
|
||||
|
||||
using executor_type = Executor;
|
||||
executor_type get_executor() {return executor;}
|
||||
|
||||
template<typename Func>
|
||||
void apply(Func f)
|
||||
{
|
||||
apply_impl(std::forward<Func>(f),
|
||||
asio::detail::make_index_sequence<sizeof...(Ts)>{});
|
||||
}
|
||||
|
||||
struct completion_base
|
||||
{
|
||||
virtual void invoke(Ts&&...ts) = 0;
|
||||
};
|
||||
|
||||
template<typename Alloc, typename WaitHandler_>
|
||||
struct completion_impl final : completion_base
|
||||
{
|
||||
WaitHandler_ handler;
|
||||
Alloc allocator;
|
||||
void invoke(Ts&&... ts)
|
||||
{
|
||||
auto h = std::move(handler);
|
||||
|
||||
using alloc_t = typename std::allocator_traits<
|
||||
typename asio::decay<Alloc>::type>::template
|
||||
rebind_alloc<completion_impl>;
|
||||
|
||||
alloc_t alloc_{allocator};
|
||||
this->~completion_impl();
|
||||
std::allocator_traits<alloc_t>::deallocate(alloc_, this, 1u);
|
||||
std::move(h)(std::forward<Ts>(ts)...);
|
||||
}
|
||||
|
||||
template<typename Alloc_, typename Handler_>
|
||||
completion_impl(Alloc_&& alloc, Handler_&& wh)
|
||||
: handler(std::forward<Handler_>(wh)),
|
||||
allocator(std::forward<Alloc_>(alloc))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
completion_base* completion = nullptr;
|
||||
typename asio::aligned_storage<sizeof(void*) * 4,
|
||||
alignof(completion_base)>::type completion_opt;
|
||||
|
||||
template<typename Alloc, typename Handler>
|
||||
void set_completion(Alloc&& alloc, Handler&& handler)
|
||||
{
|
||||
if (completion)
|
||||
cancel_();
|
||||
|
||||
using impl_t = completion_impl<
|
||||
typename asio::decay<Alloc>::type, Handler>;
|
||||
using alloc_t = typename std::allocator_traits<
|
||||
typename asio::decay<Alloc>::type>::template rebind_alloc<impl_t>;
|
||||
|
||||
alloc_t alloc_{alloc};
|
||||
auto p = std::allocator_traits<alloc_t>::allocate(alloc_, 1u);
|
||||
completion = new (p) impl_t(std::forward<Alloc>(alloc),
|
||||
std::forward<Handler>(handler));
|
||||
}
|
||||
|
||||
template<typename... T_>
|
||||
void complete(T_&&... ts)
|
||||
{
|
||||
assert(completion);
|
||||
std::exchange(completion, nullptr)->invoke(std::forward<T_>(ts)...);
|
||||
}
|
||||
|
||||
template<std::size_t... Idx>
|
||||
void complete_with_result_impl(asio::detail::index_sequence<Idx...>)
|
||||
{
|
||||
auto& result_type = *reinterpret_cast<promise_impl::result_type*>(&result);
|
||||
this->complete(std::get<Idx>(std::move(result_type))...);
|
||||
}
|
||||
|
||||
void complete_with_result()
|
||||
{
|
||||
complete_with_result_impl(
|
||||
asio::detail::make_index_sequence<sizeof...(Ts)>{});
|
||||
}
|
||||
|
||||
template<typename... T_>
|
||||
void cancel_impl_(std::exception_ptr*, T_*...)
|
||||
{
|
||||
complete(
|
||||
std::make_exception_ptr(
|
||||
asio::system_error(
|
||||
asio::error::operation_aborted)),
|
||||
T_{}...);
|
||||
}
|
||||
|
||||
template<typename... T_>
|
||||
void cancel_impl_(asio::error_code*, T_*...)
|
||||
{
|
||||
complete(asio::error::operation_aborted, T_{}...);
|
||||
}
|
||||
|
||||
template<typename... T_>
|
||||
void cancel_impl_(T_*...)
|
||||
{
|
||||
complete(T_{}...);
|
||||
}
|
||||
|
||||
void cancel_()
|
||||
{
|
||||
cancel_impl_(static_cast<Ts*>(nullptr)...);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Signature = void(),
|
||||
typename Executor = asio::any_io_executor,
|
||||
typename Allocator = std::allocator<void>>
|
||||
struct promise_handler;
|
||||
|
||||
template<typename... Ts, typename Executor, typename Allocator>
|
||||
struct promise_handler<void(Ts...), Executor, Allocator>
|
||||
{
|
||||
using promise_type = promise<void(Ts...), Executor, Allocator>;
|
||||
|
||||
promise_handler(
|
||||
Allocator allocator, Executor executor) // get_associated_allocator(exec)
|
||||
: impl_(
|
||||
std::allocate_shared<promise_impl<void(Ts...), Executor, Allocator>>(
|
||||
allocator, allocator, executor))
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<promise_impl<void(Ts...), Executor, Allocator>> impl_;
|
||||
|
||||
using cancellation_slot_type = cancellation_slot;
|
||||
|
||||
cancellation_slot_type get_cancellation_slot() const noexcept
|
||||
{
|
||||
return impl_->cancel.slot();
|
||||
}
|
||||
|
||||
using allocator_type = Allocator;
|
||||
|
||||
allocator_type get_allocator() const noexcept
|
||||
{
|
||||
return impl_->get_allocator();
|
||||
}
|
||||
|
||||
using executor_type = Executor;
|
||||
|
||||
Executor get_executor() const noexcept
|
||||
{
|
||||
return impl_->get_executor();
|
||||
}
|
||||
|
||||
auto make_promise() -> promise<void(Ts...), executor_type, allocator_type>
|
||||
{
|
||||
return promise<void(Ts...), executor_type, allocator_type>{impl_};
|
||||
}
|
||||
|
||||
void operator()(std::remove_reference_t<Ts>... ts)
|
||||
{
|
||||
assert(impl_);
|
||||
|
||||
using result_type = typename promise_impl<
|
||||
void(Ts...), allocator_type, executor_type>::result_type ;
|
||||
|
||||
new (&impl_->result) result_type(std::move(ts)...);
|
||||
impl_->done = true;
|
||||
|
||||
if (impl_->completion)
|
||||
impl_->complete_with_result();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_IMPL_PROMISE_HPP
|
||||
214
include/asio/experimental/impl/use_coro.hpp
Normal file
214
include/asio/experimental/impl/use_coro.hpp
Normal file
@@ -0,0 +1,214 @@
|
||||
//
|
||||
// experimental/impl/use_coro.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_IMPL_USE_CORO_HPP
|
||||
#define ASIO_EXPERIMENTAL_IMPL_USE_CORO_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/deferred.hpp"
|
||||
#include "asio/experimental/coro.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
template <typename Allocator, typename R>
|
||||
struct async_result<experimental::use_coro_t<Allocator>, R()>
|
||||
{
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate_impl(Initiation initiation,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void() noexcept, void,
|
||||
asio::associated_executor_t<Initiation>, Allocator>
|
||||
{
|
||||
co_await deferred_async_operation<R(), Initiation, InitArgs...>(
|
||||
deferred_init_tag{}, std::move(initiation), std::move(args)...);
|
||||
}
|
||||
|
||||
template <typename... InitArgs>
|
||||
static auto initiate_impl(asio::detail::initiation_archetype<R()>,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void(), void,
|
||||
asio::any_io_executor, Allocator>;
|
||||
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate(Initiation initiation,
|
||||
experimental::use_coro_t<Allocator> tk, InitArgs&&... args)
|
||||
{
|
||||
return initiate_impl(std::move(initiation), std::allocator_arg,
|
||||
tk.get_allocator(), std::forward<InitArgs>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Allocator, typename R>
|
||||
struct async_result<
|
||||
experimental::use_coro_t<Allocator>, R(asio::error_code)>
|
||||
{
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate_impl(Initiation initiation,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void() noexcept, void,
|
||||
asio::associated_executor_t<Initiation>, Allocator>
|
||||
{
|
||||
co_await deferred_async_operation<
|
||||
R(asio::error_code), Initiation, InitArgs...>(
|
||||
deferred_init_tag{}, std::move(initiation), std::move(args)...);
|
||||
}
|
||||
|
||||
template <typename... InitArgs>
|
||||
static auto initiate_impl(
|
||||
asio::detail::initiation_archetype<R(asio::error_code)>,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void(), void,
|
||||
asio::any_io_executor, Allocator>;
|
||||
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate(Initiation initiation,
|
||||
experimental::use_coro_t<Allocator> tk, InitArgs&&... args)
|
||||
{
|
||||
return initiate_impl(std::move(initiation), std::allocator_arg,
|
||||
tk.get_allocator(), std::forward<InitArgs>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Allocator, typename R>
|
||||
struct async_result<
|
||||
experimental::use_coro_t<Allocator>, R(std::exception_ptr)>
|
||||
{
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate_impl(Initiation initiation,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void(), void,
|
||||
asio::associated_executor_t<Initiation>, Allocator>
|
||||
{
|
||||
co_await deferred_async_operation<
|
||||
R(std::exception_ptr), Initiation, InitArgs...>(
|
||||
deferred_init_tag{}, std::move(initiation), std::move(args)...);
|
||||
}
|
||||
|
||||
template <typename... InitArgs>
|
||||
static auto initiate_impl(
|
||||
asio::detail::initiation_archetype<R(std::exception_ptr)>,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void(), void,
|
||||
asio::any_io_executor, Allocator>;
|
||||
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate(Initiation initiation,
|
||||
experimental::use_coro_t<Allocator> tk, InitArgs&&... args)
|
||||
{
|
||||
return initiate_impl(std::move(initiation), std::allocator_arg,
|
||||
tk.get_allocator(), std::forward<InitArgs>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Allocator, typename R, typename T>
|
||||
struct async_result<experimental::use_coro_t<Allocator>, R(T)>
|
||||
{
|
||||
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate_impl(Initiation initiation,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void() noexcept, T,
|
||||
asio::associated_executor_t<Initiation>, Allocator>
|
||||
{
|
||||
co_return co_await deferred_async_operation<R(T), Initiation, InitArgs...>(
|
||||
deferred_init_tag{}, std::move(initiation), std::move(args)...);
|
||||
}
|
||||
|
||||
template <typename... InitArgs>
|
||||
static auto initiate_impl(asio::detail::initiation_archetype<R(T)>,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void() noexcept, T,
|
||||
asio::any_io_executor, Allocator>;
|
||||
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate(Initiation initiation,
|
||||
experimental::use_coro_t<Allocator> tk, InitArgs&&... args)
|
||||
{
|
||||
return initiate_impl(std::move(initiation), std::allocator_arg,
|
||||
tk.get_allocator(), std::forward<InitArgs>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Allocator, typename R, typename T>
|
||||
struct async_result<
|
||||
experimental::use_coro_t<Allocator>, R(asio::error_code, T)>
|
||||
{
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate_impl(Initiation initiation,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void(), T,
|
||||
asio::associated_executor_t<Initiation>, Allocator>
|
||||
{
|
||||
co_return co_await deferred_async_operation<
|
||||
R(asio::error_code, T), Initiation, InitArgs...>(
|
||||
deferred_init_tag{}, std::move(initiation), std::move(args)...);
|
||||
}
|
||||
|
||||
template <typename... InitArgs>
|
||||
static auto initiate_impl(
|
||||
asio::detail::initiation_archetype<
|
||||
R(asio::error_code, T)>,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void(), T, asio::any_io_executor, Allocator>;
|
||||
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate(Initiation initiation,
|
||||
experimental::use_coro_t<Allocator> tk, InitArgs&&... args)
|
||||
{
|
||||
return initiate_impl(std::move(initiation), std::allocator_arg,
|
||||
tk.get_allocator(), std::forward<InitArgs>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Allocator, typename R, typename T>
|
||||
struct async_result<
|
||||
experimental::use_coro_t<Allocator>, R(std::exception_ptr, T)>
|
||||
{
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate_impl(Initiation initiation,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void(), T,
|
||||
asio::associated_executor_t<Initiation>, Allocator>
|
||||
{
|
||||
co_return co_await deferred_async_operation<
|
||||
R(std::exception_ptr, T), Initiation, InitArgs...>(
|
||||
deferred_init_tag{}, std::move(initiation), std::move(args)...);
|
||||
}
|
||||
|
||||
template <typename... InitArgs>
|
||||
static auto initiate_impl(
|
||||
asio::detail::initiation_archetype<R(std::exception_ptr, T)>,
|
||||
std::allocator_arg_t, Allocator, InitArgs... args)
|
||||
-> experimental::coro<void(), T, asio::any_io_executor, Allocator>;
|
||||
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate(Initiation initiation,
|
||||
experimental::use_coro_t<Allocator> tk, InitArgs&&... args)
|
||||
{
|
||||
return initiate_impl(std::move(initiation), std::allocator_arg,
|
||||
tk.get_allocator(), std::forward<InitArgs>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_IMPL_USE_CORO_HPP
|
||||
66
include/asio/experimental/impl/use_promise.hpp
Normal file
66
include/asio/experimental/impl/use_promise.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// experimental/impl/use_promise.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_IMPL_USE_PROMISE_HPP
|
||||
#define ASIO_EXPERIMENTAL_IMPL_USE_PROMISE_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <memory>
|
||||
#include "asio/async_result.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
|
||||
template <typename Allocator>
|
||||
struct use_promise_t;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<typename Signature, typename Executor, typename Allocator>
|
||||
struct promise_handler;
|
||||
|
||||
} // namespace detail
|
||||
} // namespace experimental
|
||||
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
template <typename Allocator, typename R, typename... Args>
|
||||
struct async_result<experimental::use_promise_t<Allocator>, R(Args...)>
|
||||
{
|
||||
template <typename Initiation, typename... InitArgs>
|
||||
static auto initiate(Initiation initiation,
|
||||
experimental::use_promise_t<Allocator> up, InitArgs... args)
|
||||
-> experimental::promise<void(decay_t<Args>...),
|
||||
asio::associated_executor_t<Initiation>, Allocator>
|
||||
{
|
||||
using handler_type = experimental::detail::promise_handler<
|
||||
void(decay_t<Args>...),
|
||||
asio::associated_executor_t<Initiation>, Allocator>;
|
||||
|
||||
handler_type ht{up.get_allocator(), get_associated_executor(initiation)};
|
||||
std::move(initiation)(ht, std::move(args)...);
|
||||
return ht.make_promise();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_IMPL_USE_PROMISE_HPP
|
||||
455
include/asio/experimental/parallel_group.hpp
Normal file
455
include/asio/experimental/parallel_group.hpp
Normal file
@@ -0,0 +1,455 @@
|
||||
//
|
||||
// experimental/parallel_group.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2025 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_PARALLEL_GROUP_HPP
|
||||
#define ASIO_EXPERIMENTAL_PARALLEL_GROUP_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <vector>
|
||||
#include "asio/async_result.hpp"
|
||||
#include "asio/detail/array.hpp"
|
||||
#include "asio/detail/memory.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/detail/utility.hpp"
|
||||
#include "asio/experimental/cancellation_condition.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
namespace detail {
|
||||
|
||||
// Helper trait for getting a tuple from a completion signature.
|
||||
|
||||
template <typename Signature>
|
||||
struct parallel_op_signature_as_tuple;
|
||||
|
||||
template <typename R, typename... Args>
|
||||
struct parallel_op_signature_as_tuple<R(Args...)>
|
||||
{
|
||||
typedef std::tuple<decay_t<Args>...> type;
|
||||
};
|
||||
|
||||
// Helper trait for concatenating completion signatures.
|
||||
|
||||
template <std::size_t N, typename Offsets, typename... Signatures>
|
||||
struct parallel_group_signature;
|
||||
|
||||
template <std::size_t N, typename R0, typename... Args0>
|
||||
struct parallel_group_signature<N, R0(Args0...)>
|
||||
{
|
||||
typedef asio::detail::array<std::size_t, N> order_type;
|
||||
typedef R0 raw_type(Args0...);
|
||||
typedef R0 type(order_type, Args0...);
|
||||
};
|
||||
|
||||
template <std::size_t N,
|
||||
typename R0, typename... Args0,
|
||||
typename R1, typename... Args1>
|
||||
struct parallel_group_signature<N, R0(Args0...), R1(Args1...)>
|
||||
{
|
||||
typedef asio::detail::array<std::size_t, N> order_type;
|
||||
typedef R0 raw_type(Args0..., Args1...);
|
||||
typedef R0 type(order_type, Args0..., Args1...);
|
||||
};
|
||||
|
||||
template <std::size_t N, typename Sig0,
|
||||
typename Sig1, typename... SigN>
|
||||
struct parallel_group_signature<N, Sig0, Sig1, SigN...>
|
||||
{
|
||||
typedef asio::detail::array<std::size_t, N> order_type;
|
||||
typedef typename parallel_group_signature<N,
|
||||
typename parallel_group_signature<N, Sig0, Sig1>::raw_type,
|
||||
SigN...>::raw_type raw_type;
|
||||
typedef typename parallel_group_signature<N,
|
||||
typename parallel_group_signature<N, Sig0, Sig1>::raw_type,
|
||||
SigN...>::type type;
|
||||
};
|
||||
|
||||
template <typename Condition, typename Handler,
|
||||
typename... Ops, std::size_t... I>
|
||||
void parallel_group_launch(Condition cancellation_condition, Handler handler,
|
||||
std::tuple<Ops...>& ops, asio::detail::index_sequence<I...>);
|
||||
|
||||
// Helper trait for determining ranged parallel group completion signatures.
|
||||
|
||||
template <typename Signature, typename Allocator>
|
||||
struct ranged_parallel_group_signature;
|
||||
|
||||
template <typename R, typename... Args, typename Allocator>
|
||||
struct ranged_parallel_group_signature<R(Args...), Allocator>
|
||||
{
|
||||
typedef std::vector<std::size_t,
|
||||
ASIO_REBIND_ALLOC(Allocator, std::size_t)> order_type;
|
||||
typedef R raw_type(
|
||||
std::vector<Args, ASIO_REBIND_ALLOC(Allocator, Args)>...);
|
||||
typedef R type(order_type,
|
||||
std::vector<Args, ASIO_REBIND_ALLOC(Allocator, Args)>...);
|
||||
};
|
||||
|
||||
template <typename Condition, typename Handler,
|
||||
typename Range, typename Allocator>
|
||||
void ranged_parallel_group_launch(Condition cancellation_condition,
|
||||
Handler handler, Range&& range, const Allocator& allocator);
|
||||
|
||||
char (¶llel_group_has_iterator_helper(...))[2];
|
||||
|
||||
template <typename T>
|
||||
char parallel_group_has_iterator_helper(T*, typename T::iterator* = 0);
|
||||
|
||||
template <typename T>
|
||||
struct parallel_group_has_iterator_typedef
|
||||
{
|
||||
enum { value = (sizeof((parallel_group_has_iterator_helper)((T*)(0))) == 1) };
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// Type trait used to determine whether a type is a range of asynchronous
|
||||
/// operations that can be used with with @c make_parallel_group.
|
||||
template <typename T>
|
||||
struct is_async_operation_range
|
||||
{
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
/// The value member is true if the type may be used as a range of
|
||||
/// asynchronous operations.
|
||||
static const bool value;
|
||||
#else
|
||||
enum
|
||||
{
|
||||
value = detail::parallel_group_has_iterator_typedef<T>::value
|
||||
};
|
||||
#endif
|
||||
};
|
||||
|
||||
/// A group of asynchronous operations that may be launched in parallel.
|
||||
/**
|
||||
* See the documentation for asio::experimental::make_parallel_group for
|
||||
* a usage example.
|
||||
*/
|
||||
template <typename... Ops>
|
||||
class parallel_group
|
||||
{
|
||||
private:
|
||||
struct initiate_async_wait
|
||||
{
|
||||
template <typename Handler, typename Condition>
|
||||
void operator()(Handler&& h, Condition&& c, std::tuple<Ops...>&& ops) const
|
||||
{
|
||||
detail::parallel_group_launch(
|
||||
std::forward<Condition>(c), std::forward<Handler>(h),
|
||||
ops, asio::detail::index_sequence_for<Ops...>());
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple<Ops...> ops_;
|
||||
|
||||
public:
|
||||
/// Constructor.
|
||||
explicit parallel_group(Ops... ops)
|
||||
: ops_(std::move(ops)...)
|
||||
{
|
||||
}
|
||||
|
||||
/// The completion signature for the group of operations.
|
||||
typedef typename detail::parallel_group_signature<sizeof...(Ops),
|
||||
completion_signature_of_t<Ops>...>::type signature;
|
||||
|
||||
/// Initiate an asynchronous wait for the group of operations.
|
||||
/**
|
||||
* Launches the group and asynchronously waits for completion.
|
||||
*
|
||||
* @param cancellation_condition A function object, called on completion of
|
||||
* an operation within the group, that is used to determine whether to cancel
|
||||
* the remaining operations. The function object is passed the arguments of
|
||||
* the completed operation's handler. To trigger cancellation of the remaining
|
||||
* operations, it must return a asio::cancellation_type value other
|
||||
* than <tt>asio::cancellation_type::none</tt>.
|
||||
*
|
||||
* @param token A @ref completion_token whose signature is comprised of
|
||||
* a @c std::array<std::size_t, N> indicating the completion order of the
|
||||
* operations, followed by all operations' completion handler arguments.
|
||||
*
|
||||
* The library provides the following @c cancellation_condition types:
|
||||
*
|
||||
* @li asio::experimental::wait_for_all
|
||||
* @li asio::experimental::wait_for_one
|
||||
* @li asio::experimental::wait_for_one_error
|
||||
* @li asio::experimental::wait_for_one_success
|
||||
*/
|
||||
template <typename CancellationCondition,
|
||||
ASIO_COMPLETION_TOKEN_FOR(signature) CompletionToken>
|
||||
auto async_wait(CancellationCondition cancellation_condition,
|
||||
CompletionToken&& token)
|
||||
-> decltype(
|
||||
asio::async_initiate<CompletionToken, signature>(
|
||||
declval<initiate_async_wait>(), token,
|
||||
std::move(cancellation_condition), std::move(ops_)))
|
||||
{
|
||||
return asio::async_initiate<CompletionToken, signature>(
|
||||
initiate_async_wait(), token,
|
||||
std::move(cancellation_condition), std::move(ops_));
|
||||
}
|
||||
};
|
||||
|
||||
/// Create a group of operations that may be launched in parallel.
|
||||
/**
|
||||
* For example:
|
||||
* @code asio::experimental::make_parallel_group(
|
||||
* in.async_read_some(asio::buffer(data)),
|
||||
* timer.async_wait()
|
||||
* ).async_wait(
|
||||
* asio::experimental::wait_for_all(),
|
||||
* [](
|
||||
* std::array<std::size_t, 2> completion_order,
|
||||
* std::error_code ec1, std::size_t n1,
|
||||
* std::error_code ec2
|
||||
* )
|
||||
* {
|
||||
* switch (completion_order[0])
|
||||
* {
|
||||
* case 0:
|
||||
* {
|
||||
* std::cout << "descriptor finished: " << ec1 << ", " << n1 << "\n";
|
||||
* }
|
||||
* break;
|
||||
* case 1:
|
||||
* {
|
||||
* std::cout << "timer finished: " << ec2 << "\n";
|
||||
* }
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
* @endcode
|
||||
*
|
||||
* If preferred, the asynchronous operations may be explicitly packaged as
|
||||
* function objects:
|
||||
* @code asio::experimental::make_parallel_group(
|
||||
* [&](auto token)
|
||||
* {
|
||||
* return in.async_read_some(asio::buffer(data), token);
|
||||
* },
|
||||
* [&](auto token)
|
||||
* {
|
||||
* return timer.async_wait(token);
|
||||
* }
|
||||
* ).async_wait(
|
||||
* asio::experimental::wait_for_all(),
|
||||
* [](
|
||||
* std::array<std::size_t, 2> completion_order,
|
||||
* std::error_code ec1, std::size_t n1,
|
||||
* std::error_code ec2
|
||||
* )
|
||||
* {
|
||||
* switch (completion_order[0])
|
||||
* {
|
||||
* case 0:
|
||||
* {
|
||||
* std::cout << "descriptor finished: " << ec1 << ", " << n1 << "\n";
|
||||
* }
|
||||
* break;
|
||||
* case 1:
|
||||
* {
|
||||
* std::cout << "timer finished: " << ec2 << "\n";
|
||||
* }
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
* @endcode
|
||||
*/
|
||||
template <typename... Ops>
|
||||
ASIO_NODISCARD inline parallel_group<Ops...>
|
||||
make_parallel_group(Ops... ops)
|
||||
{
|
||||
return parallel_group<Ops...>(std::move(ops)...);
|
||||
}
|
||||
|
||||
/// A range-based group of asynchronous operations that may be launched in
|
||||
/// parallel.
|
||||
/**
|
||||
* See the documentation for asio::experimental::make_parallel_group for
|
||||
* a usage example.
|
||||
*/
|
||||
template <typename Range, typename Allocator = std::allocator<void>>
|
||||
class ranged_parallel_group
|
||||
{
|
||||
private:
|
||||
struct initiate_async_wait
|
||||
{
|
||||
template <typename Handler, typename Condition>
|
||||
void operator()(Handler&& h, Condition&& c,
|
||||
Range&& range, const Allocator& allocator) const
|
||||
{
|
||||
detail::ranged_parallel_group_launch(std::move(c),
|
||||
std::move(h), std::forward<Range>(range), allocator);
|
||||
}
|
||||
};
|
||||
|
||||
Range range_;
|
||||
Allocator allocator_;
|
||||
|
||||
public:
|
||||
/// Constructor.
|
||||
explicit ranged_parallel_group(Range range,
|
||||
const Allocator& allocator = Allocator())
|
||||
: range_(std::move(range)),
|
||||
allocator_(allocator)
|
||||
{
|
||||
}
|
||||
|
||||
/// The completion signature for the group of operations.
|
||||
typedef typename detail::ranged_parallel_group_signature<
|
||||
completion_signature_of_t<
|
||||
decay_t<decltype(*std::declval<typename Range::iterator>())>>,
|
||||
Allocator>::type signature;
|
||||
|
||||
/// Initiate an asynchronous wait for the group of operations.
|
||||
/**
|
||||
* Launches the group and asynchronously waits for completion.
|
||||
*
|
||||
* @param cancellation_condition A function object, called on completion of
|
||||
* an operation within the group, that is used to determine whether to cancel
|
||||
* the remaining operations. The function object is passed the arguments of
|
||||
* the completed operation's handler. To trigger cancellation of the remaining
|
||||
* operations, it must return a asio::cancellation_type value other
|
||||
* than <tt>asio::cancellation_type::none</tt>.
|
||||
*
|
||||
* @param token A @ref completion_token whose signature is comprised of
|
||||
* a @c std::vector<std::size_t, Allocator> indicating the completion order of
|
||||
* the operations, followed by a vector for each of the completion signature's
|
||||
* arguments.
|
||||
*
|
||||
* The library provides the following @c cancellation_condition types:
|
||||
*
|
||||
* @li asio::experimental::wait_for_all
|
||||
* @li asio::experimental::wait_for_one
|
||||
* @li asio::experimental::wait_for_one_error
|
||||
* @li asio::experimental::wait_for_one_success
|
||||
*/
|
||||
template <typename CancellationCondition,
|
||||
ASIO_COMPLETION_TOKEN_FOR(signature) CompletionToken>
|
||||
auto async_wait(CancellationCondition cancellation_condition,
|
||||
CompletionToken&& token)
|
||||
-> decltype(
|
||||
asio::async_initiate<CompletionToken, signature>(
|
||||
declval<initiate_async_wait>(), token,
|
||||
std::move(cancellation_condition),
|
||||
std::move(range_), allocator_))
|
||||
{
|
||||
return asio::async_initiate<CompletionToken, signature>(
|
||||
initiate_async_wait(), token,
|
||||
std::move(cancellation_condition),
|
||||
std::move(range_), allocator_);
|
||||
}
|
||||
};
|
||||
|
||||
/// Create a group of operations that may be launched in parallel.
|
||||
/**
|
||||
* @param range A range containing the operations to be launched.
|
||||
*
|
||||
* For example:
|
||||
* @code
|
||||
* using op_type =
|
||||
* decltype(socket1.async_read_some(asio::buffer(data1)));
|
||||
*
|
||||
* std::vector<op_type> ops;
|
||||
* ops.push_back(socket1.async_read_some(asio::buffer(data1)));
|
||||
* ops.push_back(socket2.async_read_some(asio::buffer(data2)));
|
||||
*
|
||||
* asio::experimental::make_parallel_group(ops).async_wait(
|
||||
* asio::experimental::wait_for_all(),
|
||||
* [](
|
||||
* std::vector<std::size_t> completion_order,
|
||||
* std::vector<std::error_code> e,
|
||||
* std::vector<std::size_t> n
|
||||
* )
|
||||
* {
|
||||
* for (std::size_t i = 0; i < completion_order.size(); ++i)
|
||||
* {
|
||||
* std::size_t idx = completion_order[i];
|
||||
* std::cout << "socket " << idx << " finished: ";
|
||||
* std::cout << e[idx] << ", " << n[idx] << "\n";
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
* @endcode
|
||||
*/
|
||||
template <typename Range>
|
||||
ASIO_NODISCARD inline ranged_parallel_group<decay_t<Range>>
|
||||
make_parallel_group(Range&& range,
|
||||
constraint_t<
|
||||
is_async_operation_range<decay_t<Range>>::value
|
||||
> = 0)
|
||||
{
|
||||
return ranged_parallel_group<decay_t<Range>>(std::forward<Range>(range));
|
||||
}
|
||||
|
||||
/// Create a group of operations that may be launched in parallel.
|
||||
/**
|
||||
* @param allocator Specifies the allocator to be used with the result vectors.
|
||||
*
|
||||
* @param range A range containing the operations to be launched.
|
||||
*
|
||||
* For example:
|
||||
* @code
|
||||
* using op_type =
|
||||
* decltype(socket1.async_read_some(asio::buffer(data1)));
|
||||
*
|
||||
* std::vector<op_type> ops;
|
||||
* ops.push_back(socket1.async_read_some(asio::buffer(data1)));
|
||||
* ops.push_back(socket2.async_read_some(asio::buffer(data2)));
|
||||
*
|
||||
* asio::experimental::make_parallel_group(
|
||||
* std::allocator_arg_t,
|
||||
* my_allocator,
|
||||
* ops
|
||||
* ).async_wait(
|
||||
* asio::experimental::wait_for_all(),
|
||||
* [](
|
||||
* std::vector<std::size_t> completion_order,
|
||||
* std::vector<std::error_code> e,
|
||||
* std::vector<std::size_t> n
|
||||
* )
|
||||
* {
|
||||
* for (std::size_t i = 0; i < completion_order.size(); ++i)
|
||||
* {
|
||||
* std::size_t idx = completion_order[i];
|
||||
* std::cout << "socket " << idx << " finished: ";
|
||||
* std::cout << e[idx] << ", " << n[idx] << "\n";
|
||||
* }
|
||||
* }
|
||||
* );
|
||||
* @endcode
|
||||
*/
|
||||
template <typename Allocator, typename Range>
|
||||
ASIO_NODISCARD inline ranged_parallel_group<decay_t<Range>, Allocator>
|
||||
make_parallel_group(allocator_arg_t, const Allocator& allocator, Range&& range,
|
||||
constraint_t<
|
||||
is_async_operation_range<decay_t<Range>>::value
|
||||
> = 0)
|
||||
{
|
||||
return ranged_parallel_group<decay_t<Range>, Allocator>(
|
||||
std::forward<Range>(range), allocator);
|
||||
}
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#include "asio/experimental/impl/parallel_group.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_PARALLEL_GROUP_HPP
|
||||
224
include/asio/experimental/promise.hpp
Normal file
224
include/asio/experimental/promise.hpp
Normal file
@@ -0,0 +1,224 @@
|
||||
//
|
||||
// experimental/promise.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_PROMISE_HPP
|
||||
#define ASIO_EXPERIMENTAL_PROMISE_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
#include "asio/any_io_executor.hpp"
|
||||
#include "asio/associated_cancellation_slot.hpp"
|
||||
#include "asio/associated_executor.hpp"
|
||||
#include "asio/bind_executor.hpp"
|
||||
#include "asio/cancellation_signal.hpp"
|
||||
#include "asio/dispatch.hpp"
|
||||
#include "asio/experimental/impl/promise.hpp"
|
||||
#include "asio/post.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
|
||||
template <typename T>
|
||||
struct is_promise : std::false_type {};
|
||||
|
||||
template <typename ... Ts>
|
||||
struct is_promise<promise<Ts...>> : std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
constexpr bool is_promise_v = is_promise<T>::value;
|
||||
|
||||
template <typename ... Ts>
|
||||
struct promise_value_type
|
||||
{
|
||||
using type = std::tuple<Ts...>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct promise_value_type<T>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct promise_value_type<>
|
||||
{
|
||||
using type = std::tuple<>;
|
||||
};
|
||||
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
/// A disposable handle for an eager operation.
|
||||
/**
|
||||
* @tparam Signature The signature of the operation.
|
||||
*
|
||||
* @tparam Executor The executor to be used by the promise (taken from the
|
||||
* operation).
|
||||
*
|
||||
* @tparam Allocator The allocator used for the promise. Can be set through
|
||||
* use_allocator.
|
||||
*
|
||||
* A promise can be used to initiate an asynchronous option that can be
|
||||
* completed later. If the promise gets destroyed before completion, the
|
||||
* operation gets a cancel signal and the result is ignored.
|
||||
*
|
||||
* A promise fulfills the requirements of async_operation.
|
||||
*
|
||||
* @par Examples
|
||||
* Reading and writing from one coroutine.
|
||||
* @code
|
||||
* awaitable<void> read_write_some(asio::ip::tcp::socket & sock,
|
||||
* asio::mutable_buffer read_buf, asio::const_buffer to_write)
|
||||
* {
|
||||
* auto p = asio::async_read(read_buf,
|
||||
* asio::experimental::use_promise);
|
||||
* co_await asio::async_write_some(to_write);
|
||||
* co_await p;
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
template<typename Signature = void(),
|
||||
typename Executor = asio::any_io_executor,
|
||||
typename Allocator = std::allocator<void>>
|
||||
struct promise
|
||||
#else
|
||||
template <typename ... Ts, typename Executor, typename Allocator>
|
||||
struct promise<void(Ts...), Executor, Allocator>
|
||||
#endif // defined(GENERATING_DOCUMENTATION)
|
||||
{
|
||||
/// The value that's returned by the promise.
|
||||
using value_type = typename promise_value_type<Ts...>::type;
|
||||
|
||||
/// Cancel the promise. Usually done through the destructor.
|
||||
void cancel(cancellation_type level = cancellation_type::all)
|
||||
{
|
||||
if (impl_ && !impl_->done)
|
||||
{
|
||||
asio::dispatch(impl_->executor,
|
||||
[level, impl = impl_]{ impl->cancel.emit(level); });
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the promise is completed already.
|
||||
bool completed() const noexcept
|
||||
{
|
||||
return impl_ && impl_->done;
|
||||
}
|
||||
|
||||
/// Wait for the promise to become ready.
|
||||
template <ASIO_COMPLETION_TOKEN_FOR(void(Ts...)) CompletionToken>
|
||||
inline ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(Ts...))
|
||||
operator()(CompletionToken&& token)
|
||||
{
|
||||
assert(impl_);
|
||||
|
||||
return async_initiate<CompletionToken, void(Ts...)>(
|
||||
initiate_async_wait{impl_}, token);
|
||||
}
|
||||
|
||||
promise() = delete;
|
||||
promise(const promise& ) = delete;
|
||||
promise(promise&& ) noexcept = default;
|
||||
|
||||
/// Destruct the promise and cancel the operation.
|
||||
/**
|
||||
* It is safe to destruct a promise of a promise that didn't complete.
|
||||
*/
|
||||
~promise() { cancel(); }
|
||||
|
||||
private:
|
||||
#if !defined(GENERATING_DOCUMENTATION)
|
||||
template <typename, typename, typename> friend struct promise;
|
||||
friend struct detail::promise_handler<void(Ts...), Executor, Allocator>;
|
||||
#endif // !defined(GENERATING_DOCUMENTATION)
|
||||
|
||||
std::shared_ptr<detail::promise_impl<
|
||||
void(Ts...), Executor, Allocator>> impl_;
|
||||
|
||||
promise(
|
||||
std::shared_ptr<detail::promise_impl<
|
||||
void(Ts...), Executor, Allocator>> impl)
|
||||
: impl_(impl)
|
||||
{
|
||||
}
|
||||
|
||||
struct initiate_async_wait
|
||||
{
|
||||
std::shared_ptr<detail::promise_impl<
|
||||
void(Ts...), Executor, Allocator>> self_;
|
||||
|
||||
template <typename WaitHandler>
|
||||
void operator()(WaitHandler&& handler) const
|
||||
{
|
||||
const auto alloc = get_associated_allocator(
|
||||
handler, self_->get_allocator());
|
||||
|
||||
auto cancel = get_associated_cancellation_slot(handler);
|
||||
|
||||
if (self_->done)
|
||||
{
|
||||
auto exec = asio::get_associated_executor(
|
||||
handler, self_->get_executor());
|
||||
|
||||
asio::post(exec,
|
||||
[self = std::move(self_),
|
||||
handler = std::forward<WaitHandler>(handler)]() mutable
|
||||
{
|
||||
self->apply(std::move(handler));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cancel.is_connected())
|
||||
{
|
||||
struct cancel_handler
|
||||
{
|
||||
std::weak_ptr<detail::promise_impl<
|
||||
void(Ts...), Executor, Allocator>> self;
|
||||
|
||||
cancel_handler(
|
||||
std::weak_ptr<detail::promise_impl<
|
||||
void(Ts...), Executor, Allocator>> self)
|
||||
: self(std::move(self))
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(cancellation_type level) const
|
||||
{
|
||||
if (auto p = self.lock())
|
||||
{
|
||||
p->cancel.emit(level);
|
||||
p->cancel_();
|
||||
}
|
||||
}
|
||||
};
|
||||
cancel.template emplace<cancel_handler>(self_);
|
||||
}
|
||||
|
||||
self_->set_completion(alloc, std::forward<WaitHandler>(handler));
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace experimental
|
||||
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_PROMISE_HPP
|
||||
189
include/asio/experimental/use_coro.hpp
Normal file
189
include/asio/experimental/use_coro.hpp
Normal file
@@ -0,0 +1,189 @@
|
||||
//
|
||||
// experimental/use_coro.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_USE_CORO_HPP
|
||||
#define ASIO_EXPERIMENTAL_USE_CORO_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <memory>
|
||||
#include "asio/deferred.hpp"
|
||||
#include "asio/detail/source_location.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
|
||||
class any_io_executor;
|
||||
|
||||
namespace experimental {
|
||||
|
||||
/// A @ref completion_token that creates another coro for the task completion.
|
||||
/**
|
||||
* The @c use_coro_t class, with its value @c use_coro, is used to represent an
|
||||
* operation that can be awaited by the current resumable coroutine. This
|
||||
* completion token may be passed as a handler to an asynchronous operation.
|
||||
* For example:
|
||||
*
|
||||
* @code coro<void> my_coroutine(tcp::socket my_socket)
|
||||
* {
|
||||
* std::size_t n = co_await my_socket.async_read_some(buffer, use_coro);
|
||||
* ...
|
||||
* } @endcode
|
||||
*
|
||||
* When used with co_await, the initiating function (@c async_read_some in the
|
||||
* above example) suspends the current coroutine. The coroutine is resumed when
|
||||
* the asynchronous operation completes, and the result of the operation is
|
||||
* returned.
|
||||
*
|
||||
* Note that this token is not the most efficient (use the default completion
|
||||
* token @c asio::deferred for that) but does provide type erasure, as it
|
||||
* will always return a @c coro.
|
||||
*/
|
||||
template <typename Allocator = std::allocator<void>>
|
||||
struct use_coro_t
|
||||
{
|
||||
|
||||
/// The allocator type. The allocator is used when constructing the
|
||||
/// @c std::promise object for a given asynchronous operation.
|
||||
typedef Allocator allocator_type;
|
||||
|
||||
/// Default constructor.
|
||||
constexpr use_coro_t(
|
||||
allocator_type allocator = allocator_type{}
|
||||
#if defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
# if defined(ASIO_HAS_SOURCE_LOCATION)
|
||||
, asio::detail::source_location location =
|
||||
asio::detail::source_location::current()
|
||||
# endif // defined(ASIO_HAS_SOURCE_LOCATION)
|
||||
#endif // defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
)
|
||||
: allocator_(allocator)
|
||||
#if defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
# if defined(ASIO_HAS_SOURCE_LOCATION)
|
||||
, file_name_(location.file_name()),
|
||||
line_(location.line()),
|
||||
function_name_(location.function_name())
|
||||
# else // defined(ASIO_HAS_SOURCE_LOCATION)
|
||||
, file_name_(0),
|
||||
line_(0),
|
||||
function_name_(0)
|
||||
# endif // defined(ASIO_HAS_SOURCE_LOCATION)
|
||||
#endif // defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
{
|
||||
}
|
||||
|
||||
/// Specify an alternate allocator.
|
||||
template <typename OtherAllocator>
|
||||
use_coro_t<OtherAllocator> rebind(const OtherAllocator& allocator) const
|
||||
{
|
||||
return use_future_t<OtherAllocator>(allocator);
|
||||
}
|
||||
|
||||
/// Obtain allocator.
|
||||
allocator_type get_allocator() const
|
||||
{
|
||||
return allocator_;
|
||||
}
|
||||
|
||||
/// Constructor used to specify file name, line, and function name.
|
||||
constexpr use_coro_t(const char* file_name,
|
||||
int line, const char* function_name,
|
||||
allocator_type allocator = allocator_type{}) :
|
||||
#if defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
file_name_(file_name),
|
||||
line_(line),
|
||||
function_name_(function_name),
|
||||
#endif // defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
allocator_(allocator)
|
||||
{
|
||||
#if !defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
(void)file_name;
|
||||
(void)line;
|
||||
(void)function_name;
|
||||
#endif // !defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
}
|
||||
|
||||
/// Adapts an executor to add the @c use_coro_t completion token as the
|
||||
/// default.
|
||||
template <typename InnerExecutor>
|
||||
struct executor_with_default : InnerExecutor
|
||||
{
|
||||
/// Specify @c use_coro_t as the default completion token type.
|
||||
typedef use_coro_t default_completion_token_type;
|
||||
|
||||
/// Construct the adapted executor from the inner executor type.
|
||||
template <typename InnerExecutor1>
|
||||
executor_with_default(const InnerExecutor1& ex,
|
||||
constraint_t<
|
||||
conditional_t<
|
||||
!is_same<InnerExecutor1, executor_with_default>::value,
|
||||
is_convertible<InnerExecutor1, InnerExecutor>,
|
||||
false_type
|
||||
>::value
|
||||
> = 0) noexcept
|
||||
: InnerExecutor(ex)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// Type alias to adapt an I/O object to use @c use_coro_t as its
|
||||
/// default completion token type.
|
||||
template <typename T>
|
||||
using as_default_on_t = typename T::template rebind_executor<
|
||||
executor_with_default<typename T::executor_type>>::other;
|
||||
|
||||
/// Function helper to adapt an I/O object to use @c use_coro_t as its
|
||||
/// default completion token type.
|
||||
template <typename T>
|
||||
static typename decay_t<T>::template rebind_executor<
|
||||
executor_with_default<typename decay_t<T>::executor_type>
|
||||
>::other
|
||||
as_default_on(T&& object)
|
||||
{
|
||||
return typename decay_t<T>::template rebind_executor<
|
||||
executor_with_default<typename decay_t<T>::executor_type>
|
||||
>::other(static_cast<T&&>(object));
|
||||
}
|
||||
|
||||
#if defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
const char* file_name_;
|
||||
int line_;
|
||||
const char* function_name_;
|
||||
#endif // defined(ASIO_ENABLE_HANDLER_TRACKING)
|
||||
|
||||
private:
|
||||
Allocator allocator_;
|
||||
};
|
||||
|
||||
/// A @ref completion_token object that represents the currently executing
|
||||
/// resumable coroutine.
|
||||
/**
|
||||
* See the documentation for asio::use_coro_t for a usage example.
|
||||
*/
|
||||
#if defined(GENERATING_DOCUMENTATION)
|
||||
ASIO_INLINE_VARIABLE constexpr use_coro_t<> use_coro;
|
||||
#else
|
||||
ASIO_INLINE_VARIABLE constexpr use_coro_t<> use_coro(0, 0, 0);
|
||||
#endif
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#include "asio/experimental/impl/use_coro.hpp"
|
||||
#include "asio/experimental/coro.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_USE_CORO_HPP
|
||||
111
include/asio/experimental/use_promise.hpp
Normal file
111
include/asio/experimental/use_promise.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// experimental/use_promise.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2021-2023 Klemens D. Morgenstern
|
||||
// (klemens dot morgenstern at gmx dot net)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef ASIO_EXPERIMENTAL_USE_PROMISE_HPP
|
||||
#define ASIO_EXPERIMENTAL_USE_PROMISE_HPP
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
# pragma once
|
||||
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)
|
||||
|
||||
#include "asio/detail/config.hpp"
|
||||
#include <memory>
|
||||
#include "asio/detail/type_traits.hpp"
|
||||
|
||||
#include "asio/detail/push_options.hpp"
|
||||
|
||||
namespace asio {
|
||||
namespace experimental {
|
||||
|
||||
template <typename Allocator = std::allocator<void>>
|
||||
struct use_promise_t
|
||||
{
|
||||
/// The allocator type. The allocator is used when constructing the
|
||||
/// @c promise object for a given asynchronous operation.
|
||||
typedef Allocator allocator_type;
|
||||
|
||||
/// Construct using default-constructed allocator.
|
||||
constexpr use_promise_t()
|
||||
{
|
||||
}
|
||||
|
||||
/// Construct using specified allocator.
|
||||
explicit use_promise_t(const Allocator& allocator)
|
||||
: allocator_(allocator)
|
||||
{
|
||||
}
|
||||
|
||||
/// Obtain allocator.
|
||||
allocator_type get_allocator() const noexcept
|
||||
{
|
||||
return allocator_;
|
||||
}
|
||||
|
||||
/// Adapts an executor to add the @c use_promise_t completion token as the
|
||||
/// default.
|
||||
template <typename InnerExecutor>
|
||||
struct executor_with_default : InnerExecutor
|
||||
{
|
||||
/// Specify @c use_promise_t as the default completion token type.
|
||||
typedef use_promise_t<Allocator> default_completion_token_type;
|
||||
|
||||
/// Construct the adapted executor from the inner executor type.
|
||||
executor_with_default(const InnerExecutor& ex) noexcept
|
||||
: InnerExecutor(ex)
|
||||
{
|
||||
}
|
||||
|
||||
/// Convert the specified executor to the inner executor type, then use
|
||||
/// that to construct the adapted executor.
|
||||
template <typename OtherExecutor>
|
||||
executor_with_default(const OtherExecutor& ex,
|
||||
constraint_t<
|
||||
is_convertible<OtherExecutor, InnerExecutor>::value
|
||||
> = 0) noexcept
|
||||
: InnerExecutor(ex)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// Function helper to adapt an I/O object to use @c use_promise_t as its
|
||||
/// default completion token type.
|
||||
template <typename T>
|
||||
static typename decay_t<T>::template rebind_executor<
|
||||
executor_with_default<typename decay_t<T>::executor_type>
|
||||
>::other
|
||||
as_default_on(T&& object)
|
||||
{
|
||||
return typename decay_t<T>::template rebind_executor<
|
||||
executor_with_default<typename decay_t<T>::executor_type>
|
||||
>::other(static_cast<T&&>(object));
|
||||
}
|
||||
|
||||
/// Specify an alternate allocator.
|
||||
template <typename OtherAllocator>
|
||||
use_promise_t<OtherAllocator> rebind(const OtherAllocator& allocator) const
|
||||
{
|
||||
return use_promise_t<OtherAllocator>(allocator);
|
||||
}
|
||||
|
||||
private:
|
||||
Allocator allocator_;
|
||||
};
|
||||
|
||||
ASIO_INLINE_VARIABLE constexpr use_promise_t<> use_promise;
|
||||
|
||||
} // namespace experimental
|
||||
} // namespace asio
|
||||
|
||||
#include "asio/detail/pop_options.hpp"
|
||||
|
||||
#include "asio/experimental/impl/use_promise.hpp"
|
||||
|
||||
#endif // ASIO_EXPERIMENTAL_USE_CORO_HPP
|
||||
Reference in New Issue
Block a user