diff --git a/include/beman/execution/detail/completion_storage.hpp b/include/beman/execution/detail/completion_storage.hpp new file mode 100644 index 00000000..31dc44dc --- /dev/null +++ b/include/beman/execution/detail/completion_storage.hpp @@ -0,0 +1,129 @@ +// include/beman/execution/detail/completion_storage.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION_STORAGE +#define INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION_STORAGE + +#include +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution.detail.completion_signatures; +import beman.execution.detail.receiver_of; +import beman.execution.detail.receiver; +import beman.execution.detail.set_error; +import beman.execution.detail.unreachable; +#else +#include +#include +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { + +/// For a signature `Sig` of the form `Tag(Args...)`, compute the corresponding tuple type that can store the +/// completion. +template +struct tuple_for_signature; + +template +struct tuple_for_signature { + /// The tuple type corresponding to the signature `Tag(Args...)`. + using type = ::std::tuple...>; + + /// Helper function to be used in overloaded scenario to select the correct tuple type from a set of arguments. + static auto select_tuple(Tag t, Args&&... args) -> type; +}; + +/// The variant storage for a set of completion signatures. +template +struct variant_for_signatures; + +template +struct variant_for_signatures<::beman::execution::completion_signatures> + : ::beman::execution::detail::tuple_for_signature... { + /// The type of the variant that can store any of the completions in `Sigs...`. + using type = + ::std::variant::type...>; + + /// Stores in `storage` the completion with tag `t` and arguments `args...`. + /// + /// If the construction of the tuple throws, it will store a `set_error` completion with the current exception. + template + static auto store_into_variant(type& storage, Tag t, Args&&... args) noexcept { + using tuple_type = decltype(select_tuple(t, ::std::forward(args)...)); + if constexpr (std::is_nothrow_constructible_v) { + storage.template emplace(t, ::std::forward(args)...); + } else { + try { + storage.template emplace(t, ::std::forward(args)...); + } catch (...) { + store_into_variant(storage, ::beman::execution::set_error_t{}, ::std::current_exception()); + } + } + } + + /// Completes `receiver` with the completion stored in `storage`. + /// + /// The completion receives a copy of the stored tuple, in case the completion destroys the storage. + template <::beman::execution::receiver Receiver> + static void complete_from_variant(type&& storage, Receiver&& receiver) noexcept { + std::visit( + [&](auto&& tuple_data) { + using tuple_type = ::std::remove_cvref_t; + if constexpr (std::is_same_v) { + ::beman::execution::detail::unreachable(); + } else { + ::std::apply( + [&](const auto tag, auto&&... args) { + tag(::std::forward(receiver), ::std::forward(args)...); + }, + // Make a copy, in case the tuple is destroyed by the completion call. + tuple_type{::std::move(tuple_data)}); + } + }, + ::std::move(storage)); + } + + /// Helper to create an overload set to be able to select the right tuple from a set of arguments. + using ::beman::execution::detail::tuple_for_signature::select_tuple...; +}; + +/// Storage for a set of completions. +template +struct completion_storage { + completion_storage() noexcept = default; + + /// Stores the completion with tag `t` and arguments `args...`. + template + void store(Tag t, Args&&... args) noexcept { + variant_impl::store_into_variant(storage, t, ::std::forward(args)...); + } + + /// Completes `receiver` with the completion stored in this storage. + template <::beman::execution::receiver_of Receiver> + void complete(Receiver&& receiver) && noexcept { + variant_impl::complete_from_variant(::std::move(storage), ::std::forward(receiver)); + } + + private: + using variant_impl = ::beman::execution::detail::variant_for_signatures; + typename variant_impl::type storage; +}; + +} // namespace beman::execution::detail + +// ---------------------------------------------------------------------------- + +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION_STORAGE diff --git a/include/beman/execution/detail/elide.hpp b/include/beman/execution/detail/elide.hpp new file mode 100644 index 00000000..3f3e89d8 --- /dev/null +++ b/include/beman/execution/detail/elide.hpp @@ -0,0 +1,45 @@ +// include/beman/execution/detail/elide.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_ELIDE +#define INCLUDED_BEMAN_EXECUTION_DETAIL_ELIDE + +#include +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { + +/// Helper to elide move construction of a value when it is only used once. +/// +/// Uses a small lambda and conversion operator to elide the move construction. +template +struct elide { + /// The resulting type of the elision. + using result_t = std::invoke_result_t; + + /// Constructs the elision wrapper from callable `f`. + elide(F&& f) noexcept : f_(::std::forward(f)) {} + + /// Evaluates the callable given at construction and returns the result, eliding the move construction of the + /// result. + operator result_t() && noexcept(::std::is_nothrow_invocable_v) { return ::std::invoke(::std::forward(f_)); } + + private: + /// The callable to evaluate when the elision is converted to the result type. + F f_; +}; + +} // namespace beman::execution::detail + +// ---------------------------------------------------------------------------- + +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_ELIDE diff --git a/include/beman/execution/detail/enter_scope_sender.hpp b/include/beman/execution/detail/enter_scope_sender.hpp new file mode 100644 index 00000000..748cdfb1 --- /dev/null +++ b/include/beman/execution/detail/enter_scope_sender.hpp @@ -0,0 +1,33 @@ +// include/beman/execution/detail/enter_scope_sender.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_ENTER_SCOPE_SENDER +#define INCLUDED_BEMAN_EXECUTION_DETAIL_ENTER_SCOPE_SENDER + +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution.detail.sender; +#else +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution { +template +/*! + * \brief Sender corresponding to the action of entering a scope. + * \headerfile beman/execution/execution.hpp + */ +concept enter_scope_sender = ::beman::execution::sender; +} // namespace beman::execution + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/enter_scope_sender_in.hpp b/include/beman/execution/detail/enter_scope_sender_in.hpp new file mode 100644 index 00000000..5b1eb557 --- /dev/null +++ b/include/beman/execution/detail/enter_scope_sender_in.hpp @@ -0,0 +1,41 @@ +// include/beman/execution/detail/enter_scope_sender_in.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_ENTER_SCOPE_SENDER_IN +#define INCLUDED_BEMAN_EXECUTION_DETAIL_ENTER_SCOPE_SENDER_IN + +#include +#ifdef BEMAN_HAS_MODULES +import beman.execution.detail.enter_scope_sender; +import beman.execution.detail.exit_scope_sender_of_t; +import beman.execution.detail.sender_in; +#else +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution { +template +/*! + * \brief An enter scope sender that can operate in the environment `Env...`. + * \headerfile beman/execution/execution.hpp + * + * \details + * Requirements: + * - `Sender` models `enter_scope_sender`. + * - `Sender` models `sender_in`. + * - The completion signatures contains exactly one value completion signature, that completion + * signature is unary, and the type of the value models `exit_sender_in`. + * - Semantic: The above exit sender undoes the action of the enter scope sender which sent it. + */ +concept enter_scope_sender_in = + ::beman::execution::enter_scope_sender && ::beman::execution::sender_in && + requires { typename ::beman::execution::exit_scope_sender_of_t; }; +} // namespace beman::execution + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/exit_scope_sender.hpp b/include/beman/execution/detail/exit_scope_sender.hpp new file mode 100644 index 00000000..2c4d6e67 --- /dev/null +++ b/include/beman/execution/detail/exit_scope_sender.hpp @@ -0,0 +1,35 @@ +// include/beman/execution/detail/exit_scope_sender.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_EXIT_SCOPE_SENDER +#define INCLUDED_BEMAN_EXECUTION_DETAIL_EXIT_SCOPE_SENDER + +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution.detail.sender; +#else +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution { +/*! + * \brief A sender that does not throw when decay-copied or moved. + * \headerfile beman/execution/execution.hpp + */ +template +concept exit_scope_sender = + ::beman::execution::sender && std::is_nothrow_constructible_v<::std::remove_cvref_t, Sender> && + std::is_nothrow_move_constructible_v<::std::remove_cvref_t>; +} // namespace beman::execution + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/exit_scope_sender_in.hpp b/include/beman/execution/detail/exit_scope_sender_in.hpp new file mode 100644 index 00000000..5c5b3867 --- /dev/null +++ b/include/beman/execution/detail/exit_scope_sender_in.hpp @@ -0,0 +1,85 @@ +// include/beman/execution/detail/exit_scope_sender_in.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_EXIT_SCOPE_SENDER_IN +#define INCLUDED_BEMAN_EXECUTION_DETAIL_EXIT_SCOPE_SENDER_IN + +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution.detail.completion_signatures_of_t; +import beman.execution.detail.completion_signatures; +import beman.execution.detail.connect; +import beman.execution.detail.env; +import beman.execution.detail.exit_scope_sender; +import beman.execution.detail.nothrow_callable; +import beman.execution.detail.receiver; +import beman.execution.detail.sender_in; +import beman.execution.detail.set_value; +import beman.execution.detail.unreachable; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { + +/// A receiver with environment `Env` that accepts every possible completion. +template > +struct generic_receiver { + using receiver_concept = ::beman::execution::receiver_tag; + + template + void set_value(T&&...) && noexcept {} + template + void set_error(E&&) && noexcept {} + void set_stopped() && noexcept {} + + auto get_env() const noexcept -> Env { ::beman::execution::detail::unreachable(); } +}; + +/// A sender that doesn't throw when connected to a receiver. +template +concept nothrow_connectable = + ::beman::execution::detail::nothrow_callable<::beman::execution::connect_t, Sender, generic_receiver>; + +} // namespace beman::execution::detail + +namespace beman::execution { +/*! + * \brief An exit scope sender that can operate in the environment `Env...`. + * \headerfile beman/execution/execution.hpp + * + * \details + * Requirements: + * - `Sender` models `exit_scope_sender`. + * - `Sender` models `sender_in`. + * - `Sender` is nothrow connectable to any receiver with environment `Env...`. + * - The completion signatures of `Sender` in `Env...` is exactly `completion_signatures`. + */ +template +concept exit_scope_sender_in = + ::beman::execution::exit_scope_sender && ::beman::execution::sender_in && + ::beman::execution::detail::nothrow_connectable && + std::is_same_v<::beman::execution::completion_signatures_of_t, + ::beman::execution::completion_signatures<::beman::execution::set_value_t()>>; +} // namespace beman::execution + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/exit_scope_sender_of_t.hpp b/include/beman/execution/detail/exit_scope_sender_of_t.hpp new file mode 100644 index 00000000..a839f96b --- /dev/null +++ b/include/beman/execution/detail/exit_scope_sender_of_t.hpp @@ -0,0 +1,87 @@ +// include/beman/execution/detail/exit_scope_sender_of_t.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_EXIT_SCOPE_SENDER_OF_T +#define INCLUDED_BEMAN_EXECUTION_DETAIL_EXIT_SCOPE_SENDER_OF_T + +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution.detail.completion_signatures_of_t; +import beman.execution.detail.completion_signatures; +import beman.execution.detail.enter_scope_sender; +import beman.execution.detail.exit_scope_sender_in; +import beman.execution.detail.set_value; +#else +#include +#include +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution { + +namespace detail { + +/// `true` if `Sig` is a value completion signature, i.e. `set_value_t(Args...)`. +template +inline constexpr bool is_value_completion_sig = false; +template +inline constexpr bool is_value_completion_sig<::beman::execution::set_value_t(Args...)> = true; + +/// Finds the type T from the unique unary set_value_t(T) in a signature list. +/// Not defined (substitution failure) if there are zero or two or more value signatures, +/// or if the sole value signature is not unary. +template +struct single_value_sender; + +template + requires(!(::beman::execution::detail::is_value_completion_sig || ...)) +struct single_value_sender<::beman::execution::set_value_t(T), Rest...> { + using type = T; +}; + +template + requires(!::beman::execution::detail::is_value_completion_sig) +struct single_value_sender : ::beman::execution::detail::single_value_sender {}; + +/// Helper that extracts the exit scope sender type from a list of completion signatures. +template +struct extract_exit_scope_sender; + +template + requires requires { typename ::beman::execution::detail::single_value_sender::type; } && + ::beman::execution:: + exit_scope_sender_in::type, Env...> +struct extract_exit_scope_sender<::beman::execution::completion_signatures, Env...> { + using type = typename ::beman::execution::detail::single_value_sender::type; +}; + +} // namespace detail + +/*! + * \brief Represents the type of exit scope sender which an asynchronous operation formed from + * `Sender` in `Env...` yield on completion. + * \headerfile beman/execution/execution.hpp + * + * \details + * This looks at the completion signatures of `Sender` in `Env...` and finds the unique value completion signature. + * If there is no value completion signature, or if there are two or more value completion signatures, this type is not + * defined. + */ +template <::beman::execution::enter_scope_sender Sender, typename... Env> +using exit_scope_sender_of_t = typename ::beman::execution::detail:: + extract_exit_scope_sender<::beman::execution::completion_signatures_of_t, Env...>::type; +} // namespace beman::execution + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/within.hpp b/include/beman/execution/detail/within.hpp new file mode 100644 index 00000000..4518446d --- /dev/null +++ b/include/beman/execution/detail/within.hpp @@ -0,0 +1,378 @@ +// include/beman/execution/detail/within.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_WITHIN +#define INCLUDED_BEMAN_EXECUTION_DETAIL_WITHIN + +#include +#include +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#include +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution.detail.basic_sender; +import beman.execution.detail.completion_signatures_of_t; +import beman.execution.detail.completion_signatures; +import beman.execution.detail.completion_storage; +import beman.execution.detail.connect; +import beman.execution.detail.connect_result_t; +import beman.execution.detail.default_impls; +import beman.execution.detail.elide; +import beman.execution.detail.enter_scope_sender; +import beman.execution.detail.env; +import beman.execution.detail.env_of_t; +import beman.execution.detail.error_types_of_t; +import beman.execution.detail.exit_scope_sender_of_t; +import beman.execution.detail.get_env; +import beman.execution.detail.impls_for; +import beman.execution.detail.make_sender; +import beman.execution.detail.meta.combine; +import beman.execution.detail.meta.unique; +import beman.execution.detail.nothrow_callable; +import beman.execution.detail.product_type; +import beman.execution.detail.operation_state; +import beman.execution.detail.receiver; +import beman.execution.detail.sender; +import beman.execution.detail.sends_stopped; +import beman.execution.detail.set_error; +import beman.execution.detail.set_stopped; +import beman.execution.detail.set_value; +import beman.execution.detail.start; +import beman.execution.detail.unreachable; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { + +/// The type of the `within` algorithm. +struct within_t { + /// Creates a sender that runs `work` within the scope of `scope`. + template <::beman::execution::enter_scope_sender Scope, ::beman::execution::sender Work> + auto operator()(Scope&& scope, Work&& work) const + noexcept(::std::is_nothrow_constructible_v<::std::remove_cvref_t, Scope> && + ::std::is_nothrow_constructible_v<::std::remove_cvref_t, Work>) { + return ::beman::execution::detail::make_sender( + *this, + ::beman::execution::detail::product_type{::std::forward(scope), + ::std::forward(work)}); + } + + private: + /// Helper that yield the signatures of the `within` sender `Sender` when used with the environment `Env`. + template + struct get_signatures; + template + struct get_signatures : get_signatures> {}; + template + struct get_signatures< + ::beman::execution::detail::basic_sender<::beman::execution::detail::within_t, + ::beman::execution::detail::product_type>, + Env> { + template + /// Converts error types `E...` to a `completion_signatures` type with `set_error_t(E)...` signatures. + using as_set_error = ::beman::execution::completion_signatures<::beman::execution::set_error_t(E)...>; + + /// The error signatures of `Scope` sender. + using scope_error_types = ::beman::execution::error_types_of_t; + /// The stopped signatures of `Scope` sender. + using scope_stopped_types = + ::std::conditional_t<::beman::execution::sends_stopped, + ::beman::execution::completion_signatures<::beman::execution::set_stopped_t()>, + ::beman::execution::completion_signatures<>>; + /// The resulting completion signatures of the `within` sender. + /// This is the combination of the completion signatures of `Work` and the error and stopped signatures of + /// `Scope`. + using type = ::beman::execution::detail::meta::unique<::beman::execution::detail::meta::combine< + ::beman::execution::completion_signatures_of_t, + ::beman::execution::detail::meta::combine>>; + }; + + public: + /// Returns the completion signatures of the `within` sender `Sender` when used with the environment `Env`. + template + static consteval auto get_completion_signatures() noexcept { + return typename get_signatures<::std::remove_cvref_t, Env...>::type{}; + } + + template <::beman::execution::enter_scope_sender Scope, + ::beman::execution::sender Work, + ::beman::execution::receiver Receiver> + struct state; + + struct impls_for : ::beman::execution::detail::default_impls { + struct get_state_impl { + + template + auto operator()(auto&& sender, Receiver& receiver) const noexcept(false) { + auto&& data = ::std::forward(sender).template get<1>(); + return ::beman::execution::detail::within_t::state< + decltype(::std::forward(data).template get<0>()), + decltype(::std::forward(data).template get<1>()), + Receiver>(::std::forward(data).template get<0>(), + ::std::forward(data).template get<1>(), + receiver); + } + }; + static constexpr auto get_state{get_state_impl{}}; + struct start_impl { + auto operator()(auto& s, auto&) const noexcept { s.start(); } + }; + static constexpr auto start{start_impl{}}; + }; +}; + +/// Base class for a receiver that delegates to a state object. +template +struct within_receiver_base { + /// This is a receiver. + using receiver_concept = ::beman::execution::receiver_tag; + + /// Returns the environment of the receiver. + auto get_env() const noexcept -> Env { return state_.get_env(); } + + /// The state object that this receiver delegates to. + State& state_; +}; + +/// Receiver connected to the enter operation. +template +struct within_enter_receiver : ::beman::execution::detail::within_receiver_base { + using ::beman::execution::detail::within_receiver_base::state_; + + /// Handles success completion of the enter operation. + template + void set_value(Args&&... args) && noexcept { + state_.on_enter_set_value(::std::forward(args)...); + } + /// Handles error completion of the enter operation. + template + void set_error(E&& e) && noexcept { + state_.on_enter_set_error(::std::forward(e)); + } + /// Handles stopped completion of the enter operation. + void set_stopped() && noexcept { state_.on_enter_set_stopped(); } +}; + +/// Receiver connected to the exit operation. +template +struct within_exit_receiver : ::beman::execution::detail::within_receiver_base { + using ::beman::execution::detail::within_receiver_base::state_; + + /// Handles success completion of the exit operation. + void set_value() && noexcept { state_.on_exit_set_value(); } +}; + +/// Receiver connected to the work operation. +template +struct within_work_receiver : ::beman::execution::detail::within_receiver_base { + using ::beman::execution::detail::within_receiver_base::state_; + + /// Handles success completion of the work operation. + template + void set_value(Args&&... args) && noexcept { + state_.on_work_completion(::beman::execution::set_value_t{}, ::std::forward(args)...); + } + /// Handles error completion of the work operation. + template + void set_error(E&& e) && noexcept { + state_.on_work_completion(::beman::execution::set_error_t{}, ::std::forward(e)); + } + /// Handles stopped completion of the work operation. + void set_stopped() && noexcept { state_.on_work_completion(::beman::execution::set_stopped_t{}); } +}; + +/// The operation state of the `within` sender constructed from `Scope` and `Work`, and connected to `Receiver`. +template <::beman::execution::enter_scope_sender Scope, + ::beman::execution::sender Work, + ::beman::execution::receiver Receiver> +struct within_t::state { + using operation_state_concept = ::beman::execution::operation_state_tag; + using self_t = state; + using scope_t = ::std::remove_cvref_t; + using work_t = ::std::remove_cvref_t; + using env_t = ::beman::execution::env_of_t; + using exit_scope_sender_t = ::beman::execution::exit_scope_sender_of_t; + using enter_receiver_t = ::beman::execution::detail::within_enter_receiver; + using work_receiver_t = ::beman::execution::detail::within_work_receiver; + using exit_receiver_t = ::beman::execution::detail::within_exit_receiver; + using enter_op_t = ::beman::execution::connect_result_t; + using work_op_t = ::beman::execution::connect_result_t; + using exit_op_t = ::beman::execution::connect_result_t; + using work_completions_t = ::beman::execution::completion_signatures_of_t; + + /// The state that is needed during the execution of "enter" async operation. + struct enter_state { + /// The operation state of the "enter" async operation. + enter_op_t op; + /// The work sender that will be executed after the "enter" async operation completes successfully. + work_t work; + }; + + /// The state that is needed during the execution of "work" async operation. + struct work_state { + /// The operation state of the "work" async operation. + work_op_t op; + /// The exit scope sender that will be executed after the "work" async operation completes successfully. + exit_scope_sender_t exit_sender; + }; + + /// The state that is needed during the execution of "exit" async operation. + struct exit_state { + /// The operation state of the "exit" async operation. + exit_op_t op; + /// The completion signals produced by "work" operation, which will be used to complete our operation. + ::beman::execution::detail::completion_storage work_completion; + }; + + /// The state needed in various stages of our operation. + std::variant state_; + /// The final receiver of this operation. + Receiver& receiver; + + /// Constructs `*this` from given arguments. + template <::beman::execution::sender S, ::beman::execution::sender W> + state(S&& scope, W&& work, Receiver& r) + : state_(std::in_place_type, ::beman::execution::detail::elide([&]() noexcept { + return enter_state{ + ::beman::execution::connect(::std::forward(scope), enter_receiver_t{{*this}}), + ::std::forward(work)}; + })), + receiver(r) {} + + /// Starts the `within` async operation represented by `*this`. + auto start() noexcept -> void { + // Start the operation for the enter scope sender; this would eventually trigger the rest of the work. + const auto p = std::get_if(&state_); + assert(p); + ::beman::execution::start(p->op); + } + + /// Returns the environment of the target receiver. + auto get_env() const noexcept -> env_t { return ::beman::execution::get_env(this->receiver); } + + /// Called when the enter scope sender completes successfully with `exit_sender`. + void on_enter_set_value(exit_scope_sender_t&& exit_sender) noexcept { + constexpr auto nothrow = + ::beman::execution::detail::nothrow_callable<::beman::execution::connect_t, work_t, work_receiver_t>; + + const auto p = std::get_if(&state_); + assert(p); + try { + auto work_sender = ::std::move(p->work); + auto& new_state = + this->state_.template emplace(::beman::execution::detail::elide([&]() noexcept { + return work_state{::beman::execution::connect(::std::move(work_sender), work_receiver_t{{*this}}), + ::std::move(exit_sender)}; + })); + ::beman::execution::start(new_state.op); + } catch (...) { + if constexpr (nothrow) { + ::beman::execution::detail::unreachable(); + } else { + ::beman::execution::set_error(::std::move(this->receiver), ::std::current_exception()); + } + } + } + + /// Called when the enter scope sender completes with an error `e`. + template + void on_enter_set_error(E&& e) noexcept { + ::beman::execution::set_error(::std::move(this->receiver), ::std::forward(e)); + } + + /// Called when the enter scope sender is stopped. + void on_enter_set_stopped() noexcept { ::beman::execution::set_stopped(::std::move(this->receiver)); } + + /// Called when the work sender completes with tag `t` and `args...` to move to the exit state. + template + void on_work_completion(Tag t, Args&&... args) noexcept { + const auto p = std::get_if(&state_); + assert(p); + + // Put the args in the completion storage, before destroying the operation state. + ::beman::execution::detail::completion_storage completion; + completion.store(t, ::std::forward(args)...); + auto exit_sender = ::std::move(p->exit_sender); + // Replace the state as we transition to "exit" state. + auto& new_state = this->state_.template emplace(::beman::execution::detail::elide([&]() noexcept { + return exit_state{::beman::execution::connect(::std::move(exit_sender), exit_receiver_t{{*this}}), + std::move(completion)}; + })); + ::beman::execution::start(new_state.op); + } + + /// Called when the exit scope sender completes successfully to complete the entire operation in `*this`. + void on_exit_set_value() noexcept { + const auto p = std::get_if(&state_); + assert(p); + std::move(p->work_completion).complete(::std::move(this->receiver)); + } +}; + +}; // namespace beman::execution::detail + +#include + +namespace beman::execution { +/*! + * \brief `within_t` is the type of `within`. + * \headerfile beman/execution/execution.hpp + */ +using within_t = ::beman::execution::detail::within_t; + +/*! + * \brief `within` is a sender adaptor that runs a work sender within the scope of a scope sender. + * \headerfile beman/execution/execution.hpp + * + * The `within` sender adaptor takes two senders: a scope sender and a work sender. It runs the work sender within the + * context of the scope. + * + * Usually forwards the completion of the work sender, but may also complete with an error or stopped signal if the + * scope sender completes with an error or is stopped. + */ +inline constexpr ::beman::execution::within_t within{}; + +} // namespace beman::execution + +// ---------------------------------------------------------------------------- + +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_WITHIN diff --git a/include/beman/execution/execution.hpp b/include/beman/execution/execution.hpp index 70a5f5c5..1e762461 100644 --- a/include/beman/execution/execution.hpp +++ b/include/beman/execution/execution.hpp @@ -19,8 +19,13 @@ import beman.execution.detail.connect; import beman.execution.detail.continues_on; import beman.execution.detail.counting_scope; import beman.execution.detail.dependent_sender; +import beman.execution.detail.enter_scope_sender_in; +import beman.execution.detail.enter_scope_sender; import beman.execution.detail.env; import beman.execution.detail.execution_policy; +import beman.execution.detail.exit_scope_sender_in; +import beman.execution.detail.exit_scope_sender_of_t; +import beman.execution.detail.exit_scope_sender; import beman.execution.detail.forwarding_query; import beman.execution.detail.get_allocator; import beman.execution.detail.get_await_completion_adaptor; @@ -72,6 +77,7 @@ import beman.execution.detail.valid_completion_signatures; import beman.execution.detail.when_all_with_variant; import beman.execution.detail.when_all; import beman.execution.detail.with_awaitable_senders; +import beman.execution.detail.within; import beman.execution.detail.write_env; #else #include @@ -84,8 +90,13 @@ import beman.execution.detail.write_env; #include #include #include +#include +#include #include #include +#include +#include +#include #include #include #include @@ -137,6 +148,7 @@ import beman.execution.detail.write_env; #include #include #include +#include #include #endif diff --git a/src/beman/execution/CMakeLists.txt b/src/beman/execution/CMakeLists.txt index f7e0cd11..8b5d2dc2 100644 --- a/src/beman/execution/CMakeLists.txt +++ b/src/beman/execution/CMakeLists.txt @@ -55,6 +55,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/completion_signatures_for.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/completion_signatures_of_t.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/completion_tag.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/completion_storage.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/connect.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/connect_all.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/connect_all_result.hpp @@ -74,13 +75,19 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/dependent_sender.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/dependent_sender_error.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/execution_policy.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/elide.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/emplace_from.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/enter_scope_sender.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/enter_scope_sender_in.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/env.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/env_of_t.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/env_promise.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/env_type.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/error_types_of.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/error_types_of_t.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/exit_scope_sender.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/exit_scope_sender_in.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/exit_scope_sender_of_t.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/find_allocator.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/forward_like.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/forwarding_query.hpp @@ -223,6 +230,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/with_await_transform.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/with_awaitable_senders.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/with_error.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/within.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/write_env.hpp ) @@ -264,6 +272,7 @@ if(BEMAN_USE_MODULES) completion_signatures_of_t.cppm completion_signatures.cppm completion_tag.cppm + completion_storage.cppm connect_all_result.cppm connect_all.cppm connect_awaitable.cppm @@ -283,13 +292,19 @@ if(BEMAN_USE_MODULES) dependent_sender.cppm dependent_sender_error.cppm execution_policy.cppm + elide.cppm emplace_from.cppm + enter_scope_sender.cppm + enter_scope_sender_in.cppm enable_sender.cppm env_of_t.cppm env_promise.cppm env_type.cppm env.cppm error_types_of_t.cppm + exit_scope_sender.cppm + exit_scope_sender_in.cppm + exit_scope_sender_of_t.cppm forward_like.cppm forwarding_query.cppm fwd_env.cppm @@ -423,6 +438,7 @@ if(BEMAN_USE_MODULES) when_all_with_variant.cppm with_await_transform.cppm with_awaitable_senders.cppm + within.cppm write_env.cppm ) target_compile_definitions( diff --git a/src/beman/execution/completion_storage.cppm b/src/beman/execution/completion_storage.cppm new file mode 100644 index 00000000..12ee8fe6 --- /dev/null +++ b/src/beman/execution/completion_storage.cppm @@ -0,0 +1,11 @@ +module; +// src/beman/execution/completion_storage.cppm -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +export module beman.execution.detail.completion_storage; + +namespace beman::execution::detail { +export using beman::execution::detail::completion_storage; +} // namespace beman::execution::detail diff --git a/src/beman/execution/elide.cppm b/src/beman/execution/elide.cppm new file mode 100644 index 00000000..8be3b287 --- /dev/null +++ b/src/beman/execution/elide.cppm @@ -0,0 +1,11 @@ +module; +// src/beman/execution/elide.cppm -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +export module beman.execution.detail.elide; + +namespace beman::execution::detail { +export using beman::execution::detail::elide; +} // namespace beman::execution::detail diff --git a/src/beman/execution/enter_scope_sender.cppm b/src/beman/execution/enter_scope_sender.cppm new file mode 100644 index 00000000..6ceda8f8 --- /dev/null +++ b/src/beman/execution/enter_scope_sender.cppm @@ -0,0 +1,11 @@ +module; +// src/beman/execution/enter_scope_sender.cppm -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +export module beman.execution.detail.enter_scope_sender; + +namespace beman::execution { +export using beman::execution::enter_scope_sender; +} // namespace beman::execution diff --git a/src/beman/execution/enter_scope_sender_in.cppm b/src/beman/execution/enter_scope_sender_in.cppm new file mode 100644 index 00000000..f38a796a --- /dev/null +++ b/src/beman/execution/enter_scope_sender_in.cppm @@ -0,0 +1,11 @@ +module; +// src/beman/execution/enter_scope_sender_in.cppm -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +export module beman.execution.detail.enter_scope_sender_in; + +namespace beman::execution { +export using beman::execution::enter_scope_sender_in; +} // namespace beman::execution diff --git a/src/beman/execution/execution.cppm b/src/beman/execution/execution.cppm index 9d0c91d7..f81147bd 100644 --- a/src/beman/execution/execution.cppm +++ b/src/beman/execution/execution.cppm @@ -20,11 +20,16 @@ export import beman.execution.detail.connect_result_t; // [exec.connect], the co import beman.execution.detail.continues_on; import beman.execution.detail.counting_scope; import beman.execution.detail.default_domain; +export import beman.execution.detail.enter_scope_sender; +export import beman.execution.detail.enter_scope_sender_in; export import beman.execution.detail.indeterminate_domain; export import beman.execution.detail.env; export import beman.execution.detail.env_of_t; export import beman.execution.detail.error_types_of_t; // [exec.getcomplsigs], completion signatures export import beman.execution.detail.execution_policy; +export import beman.execution.detail.exit_scope_sender; +export import beman.execution.detail.exit_scope_sender_in; +export import beman.execution.detail.exit_scope_sender_of_t; import beman.execution.detail.forwarding_query; import beman.execution.detail.get_allocator; import beman.execution.detail.get_await_completion_adaptor; @@ -82,6 +87,7 @@ export import beman.execution.detail.value_types_of_t; // [exec.getcomplsigs], c import beman.execution.detail.when_all; import beman.execution.detail.when_all_with_variant; export import beman.execution.detail.with_awaitable_senders; // [exec.with.awaitable.senders] +export import beman.execution.detail.within; import beman.execution.detail.write_env; import beman.execution.detail.inline_scheduler; diff --git a/src/beman/execution/exit_scope_sender.cppm b/src/beman/execution/exit_scope_sender.cppm new file mode 100644 index 00000000..5909e788 --- /dev/null +++ b/src/beman/execution/exit_scope_sender.cppm @@ -0,0 +1,11 @@ +module; +// src/beman/execution/exit_scope_sender.cppm -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +export module beman.execution.detail.exit_scope_sender; + +namespace beman::execution { +export using beman::execution::exit_scope_sender; +} // namespace beman::execution diff --git a/src/beman/execution/exit_scope_sender_in.cppm b/src/beman/execution/exit_scope_sender_in.cppm new file mode 100644 index 00000000..c8c1294f --- /dev/null +++ b/src/beman/execution/exit_scope_sender_in.cppm @@ -0,0 +1,11 @@ +module; +// src/beman/execution/exit_scope_sender_in.cppm -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +export module beman.execution.detail.exit_scope_sender_in; + +namespace beman::execution { +export using beman::execution::exit_scope_sender_in; +} // namespace beman::execution diff --git a/src/beman/execution/exit_scope_sender_of_t.cppm b/src/beman/execution/exit_scope_sender_of_t.cppm new file mode 100644 index 00000000..7995d2e2 --- /dev/null +++ b/src/beman/execution/exit_scope_sender_of_t.cppm @@ -0,0 +1,11 @@ +module; +// src/beman/execution/exit_scope_sender_of_t.cppm -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +export module beman.execution.detail.exit_scope_sender_of_t; + +namespace beman::execution { +export using beman::execution::exit_scope_sender_of_t; +} // namespace beman::execution diff --git a/src/beman/execution/within.cppm b/src/beman/execution/within.cppm new file mode 100644 index 00000000..6a52e181 --- /dev/null +++ b/src/beman/execution/within.cppm @@ -0,0 +1,12 @@ +module; +// src/beman/execution/within.cppm -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +export module beman.execution.detail.within; + +namespace beman::execution { +export using beman::execution::within_t; +export using beman::execution::within; +} // namespace beman::execution diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index 59e58f6a..2e6c3b4c 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -50,6 +50,7 @@ list( exec-scope-concepts.test exec-scope-counting.test exec-scope-simple-counting.test + exec-scope-snd-concepts.test exec-sender-adaptor-closure.test exec-set-error.test exec-set-stopped.test @@ -70,6 +71,7 @@ list( exec-utils-cmplsigs.test exec-when-all.test exec-with-awaitable-senders.test + exec-within.test execution-queryable-concept.test execution-syn.test forward-like.test diff --git a/tests/beman/execution/exec-scope-snd-concepts.test.cpp b/tests/beman/execution/exec-scope-snd-concepts.test.cpp new file mode 100644 index 00000000..64293b12 --- /dev/null +++ b/tests/beman/execution/exec-scope-snd-concepts.test.cpp @@ -0,0 +1,86 @@ +// src/beman/execution/tests/exec-scope-snd-concepts.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#ifdef BEMAN_HAS_MODULES +import beman.execution; +#else +#include +#include +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace { +template +struct sender { + using sender_concept = test_std::sender_tag; + using completion_signatures = test_std::completion_signatures; + template + static consteval auto get_completion_signatures() -> completion_signatures { + return {}; + } +}; +} // namespace + +TEST(exec_scope_snd_concepts) { + // any sender models `exit_scope_sender`. + static_assert(test_std::exit_scope_sender); + static_assert(test_std::exit_scope_sender); + static_assert(test_std::exit_scope_sender); + // no-value sender + static_assert(test_std::exit_scope_sender_in>); + // sender with value completion doesn't model `exit_scope_sender_in` + static_assert(!test_std::exit_scope_sender_in>); + + // any sender models `enter_scope_sender`. + static_assert(test_std::enter_scope_sender); + static_assert(test_std::enter_scope_sender); + static_assert(test_std::enter_scope_sender); + + // simple senders don't model `enter_scope_sender_in` + static_assert(!test_std::enter_scope_sender_in); + static_assert(!test_std::enter_scope_sender_in); + static_assert(!test_std::enter_scope_sender_in); + + // senders producing senders + static_assert(test_std::enter_scope_sender_in>); + static_assert(!test_std::enter_scope_sender_in>); + static_assert( + !test_std::enter_scope_sender_in>); + static_assert( + !test_std::enter_scope_sender_in>); + + // extracting the exit sender + static_assert( + std::is_same_v>>); + + // different completion signatures for enter senders + using just_sender_t = decltype(test_std::just()); + using t1 = sender<>; + static_assert(!test_std::enter_scope_sender_in>); + + using t2 = sender; + static_assert(test_std::enter_scope_sender_in>); + static_assert(std::is_same_v>, just_sender_t>); + + using t3 = sender; + static_assert(test_std::enter_scope_sender_in>); + static_assert(std::is_same_v>, just_sender_t>); + + using t4 = sender; + static_assert(!test_std::enter_scope_sender_in>); + + using t5 = sender; + static_assert(!test_std::enter_scope_sender_in>); + + // TODO: use sender<...> +} diff --git a/tests/beman/execution/exec-within.test.cpp b/tests/beman/execution/exec-within.test.cpp new file mode 100644 index 00000000..e766a04a --- /dev/null +++ b/tests/beman/execution/exec-within.test.cpp @@ -0,0 +1,271 @@ +// src/beman/execution/tests/exec-within.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#ifdef BEMAN_HAS_MODULES +import beman.execution; +#else +#include +#include +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +/// Returns a scope that does nothing and completes immediately. +auto empty_scope() -> test_std::sender auto { + auto r = test_std::just(test_std::just()); + static_assert(test_std::enter_scope_sender_in>); + return r; +} + +/// Returns a scope that sets `enter_executed` to true when entered and sets `exit_executed` to true when exited. +auto tracing_scope(bool& enter_executed, bool& exit_executed) -> test_std::sender auto { + test_std::sender auto exit_sender = + test_std::just() | test_std::then([&exit_executed]() noexcept { exit_executed = true; }); + test_std::sender auto r = + test_std::just() | test_std::then([&enter_executed, exit_sender]() noexcept -> decltype(exit_sender) { + enter_executed = true; + return exit_sender; + }); + static_assert(test_std::enter_scope_sender_in>); + return r; +} + +/// Checks that we can make a trivial invocation of `within` and that it behaves as expected. +auto test_trivial_invocation() -> void { + // Arrange + bool executed{false}; + constexpr int expected{13}; + test_std::sender auto scope = empty_scope(); + test_std::sender auto work = test_std::just() | test_std::then([&executed]() noexcept { executed = true; }); + + // Act + test_std::sync_wait(test_std::within(std::move(scope), std::move(work))); + + // Assert + ASSERT(executed); +} + +/// Checks that scope action is properly executed when using `within`. +auto test_scope_use() -> void { + + // Arrange + bool executed{false}; + bool enter_executed{false}; + bool exit_executed{false}; + test_std::sender auto scope = tracing_scope(enter_executed, exit_executed); + test_std::sender auto work = test_std::just() | test_std::then([&executed]() noexcept { executed = true; }); + + // Act + test_std::sync_wait(test_std::within(std::move(scope), std::move(work))); + + // Assert + ASSERT(executed); + ASSERT(enter_executed); + ASSERT(exit_executed); +} + +/// Checks that the completion values of work is properly propagated when using `within`. +auto test_work_completes_with_value() -> void { + + // Arrange + bool enter_executed{false}; + bool exit_executed{false}; + test_std::sender auto scope = tracing_scope(enter_executed, exit_executed); + test_std::sender auto work = test_std::just(13, 17, 19); + + // Act + auto r = test_std::sync_wait(test_std::within(std::move(scope), std::move(work))); + + // Assert + ASSERT(enter_executed); + ASSERT(exit_executed); + ASSERT(r.has_value()); + ASSERT((*r == std::tuple{13, 17, 19})); +} + +/// Sender with multiple value completion alternatives, but always completes with the int,int alternative. +struct sender_with_multiple_completions { + using sender_concept = test_std::sender_tag; + using completion_signatures = test_std::completion_signatures; + template + static consteval auto get_completion_signatures() -> completion_signatures { + return {}; + } + + template + auto connect(Receiver&& rcvr) && noexcept { + return test_std::connect(test_std::just(13, 17), std::forward(rcvr)); + } +}; + +/// Receiver that accepts all completions, but fails if the wrong completion is called. +struct int_int_receiver { + using receiver_concept = test_std::receiver_tag; + auto set_value(int x, int y) && noexcept -> void { + ASSERT(x == 13); + ASSERT(y == 17); + } + template + auto set_value(Args&&...) && noexcept -> void { + ASSERT(!"generic set_value should not be called"); + } + template + auto set_error(E&&) && noexcept -> void { + ASSERT(!"set_error should not be called"); + } + auto set_stopped() && noexcept -> void { ASSERT(!"set_stopped should not be called"); } +}; + +/// If the work has multiple value completion alternatives, check that the completion selection works appropriately. +auto test_work_with_multiple_values_alternatives() -> void { + + // Arrange + bool enter_executed{false}; + bool exit_executed{false}; + test_std::sender auto scope = tracing_scope(enter_executed, exit_executed); + test_std::sender auto work = sender_with_multiple_completions{}; + + // Act & assert + test_std::sender auto s = test_std::within(std::move(scope), std::move(work)); + auto op = test_std::connect(std::move(s), int_int_receiver{}); + test_std::start(op); + + // Assert + ASSERT(enter_executed); + ASSERT(exit_executed); +} + +/// Checks that if the work completes with an error, the error is propagated and the exit scope is executed. +auto test_work_completes_with_error() -> void { + + // Arrange + bool enter_executed{false}; + bool exit_executed{false}; + int result{0}; + test_std::sender auto scope = tracing_scope(enter_executed, exit_executed); + test_std::sender auto work = test_std::just_error(19); + + // Act + test_std::sync_wait(test_std::within(std::move(scope), std::move(work)) | + test_std::upon_error([&result](int x) noexcept { result = x; })); + + // Assert + ASSERT(enter_executed); + ASSERT(exit_executed); + ASSERT(result == 19); +} + +/// Checks that if the work completes with stopped, the completion is propagated and the exit scope is executed. +auto test_work_completes_with_stopped() -> void { + + // Arrange + bool enter_executed{false}; + bool exit_executed{false}; + bool work_stopped{false}; + test_std::sender auto scope = tracing_scope(enter_executed, exit_executed); + test_std::sender auto work = test_std::just_stopped(); + + // Act + test_std::sync_wait(test_std::within(std::move(scope), std::move(work)) | + test_std::upon_stopped([&work_stopped]() noexcept { work_stopped = true; })); + + // Assert + ASSERT(enter_executed); + ASSERT(exit_executed); + ASSERT(work_stopped); +} + +/// Checks that if the enter scope completes with error, the work isn't executed and the error is propagated. +auto test_scope_enter_completes_with_error() -> void { + + // Arrange + bool enter_executed{false}; + bool exit_executed{false}; + bool work_executed{false}; + int resulting_error{0}; + test_std::sender auto exit_sender = + test_std::just() | test_std::then([&exit_executed]() noexcept { exit_executed = true; }); + test_std::sender auto scope = + test_std::just() | test_std::then([&enter_executed, exit_sender]() -> decltype(exit_sender) { + enter_executed = true; + throw 13; // simulate error in enter scope + return exit_sender; + }); + test_std::sender auto work = + test_std::just() | test_std::then([&work_executed]() noexcept { work_executed = true; }); + + // Act & assert + try { + test_std::sync_wait(test_std::within(std::move(scope), std::move(work))); + ASSERT(!"Expected exception to be thrown"); + } catch (int e) { + ASSERT(e == 13); + } + + // Assert + ASSERT(enter_executed); + ASSERT(!work_executed); + ASSERT(!exit_executed); +} + +/// Sender that can complete with value or stopped, but always completes with stopped. +struct sender_completing_with_stopped { + using sender_concept = test_std::sender_tag; + using completion_signatures = test_std::completion_signatures; + template + static consteval auto get_completion_signatures() -> completion_signatures { + return {}; + } + + template + auto connect(Receiver&& rcvr) && noexcept { + return test_std::connect(test_std::just_stopped(), std::forward(rcvr)); + } +}; + +/// Checks that if the enter scope completes with stopped, the work isn't executed and the stopped is propagated. +auto test_scope_enter_completes_with_stopped() -> void { + + // Arrange + bool exit_executed{false}; + bool work_executed{false}; + bool work_stopped{false}; + test_std::sender auto exit_sender = + test_std::just() | test_std::then([&exit_executed]() noexcept { exit_executed = true; }); + test_std::sender auto scope = + sender_completing_with_stopped{} | + test_std::then([exit_sender]() noexcept -> decltype(exit_sender) { return exit_sender; }); + test_std::sender auto work = + test_std::just() | test_std::then([&work_executed]() noexcept { work_executed = true; }); + + // Act + test_std::sync_wait(test_std::within(std::move(scope), std::move(work)) | + test_std::upon_stopped([&work_stopped]() noexcept { work_stopped = true; })); + + // Assert + ASSERT(!work_executed); + ASSERT(!exit_executed); + ASSERT(work_stopped); +} + +TEST(exec_within) { + static_assert(std::same_as); + + test_trivial_invocation(); + test_scope_use(); + test_work_completes_with_value(); + test_work_with_multiple_values_alternatives(); + test_work_completes_with_error(); + test_work_completes_with_stopped(); + test_scope_enter_completes_with_error(); + test_scope_enter_completes_with_stopped(); +}