From 65e70e382a1d370edc116613bdeb77d2b0f160bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Thu, 15 May 2025 00:08:19 +0100 Subject: [PATCH 01/23] added async_scope_token --- .../execution/detail/async_scope_token.hpp | 43 ++++++++++++ include/beman/execution/detail/nest.hpp | 23 +++++++ tests/beman/execution/CMakeLists.txt | 2 + tests/beman/execution/exec-nest.test.cpp | 25 +++++++ .../execution/exec-scope-concepts.test.cpp | 65 +++++++++++++++++++ 5 files changed, 158 insertions(+) create mode 100644 include/beman/execution/detail/async_scope_token.hpp create mode 100644 include/beman/execution/detail/nest.hpp create mode 100644 tests/beman/execution/exec-nest.test.cpp create mode 100644 tests/beman/execution/exec-scope-concepts.test.cpp diff --git a/include/beman/execution/detail/async_scope_token.hpp b/include/beman/execution/detail/async_scope_token.hpp new file mode 100644 index 00000000..4132b01d --- /dev/null +++ b/include/beman/execution/detail/async_scope_token.hpp @@ -0,0 +1,43 @@ +// include/beman/execution/detail/async_scope_token.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_ASYNC_SCOPE_TOKEN +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_ASYNC_SCOPE_TOKEN + +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { + struct token_test_env {}; + + struct token_test_sender { + using sender_concept = ::beman::execution::sender_t; + auto get_completion_signatures(::beman::execution::detail::token_test_env) const noexcept { + return ::beman::execution::completion_signatures<>{}; + } + }; + static_assert(::beman::execution::sender<::beman::execution::detail::token_test_sender>); + static_assert(::beman::execution::sender_in<::beman::execution::detail::token_test_sender, ::beman::execution::detail::token_test_env>); +} + +namespace beman::execution { + template + concept async_scope_token + = ::std::copyable + && requires(Token token) { + { token.try_associate() } -> ::std::same_as; + { token.disassociate() } noexcept; + { token.wrap(::std::declval<::beman::execution::detail::token_test_sender>()) } + -> ::beman::execution::sender_in<::beman::execution::detail::token_test_env>; + } + ; +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/nest.hpp b/include/beman/execution/detail/nest.hpp new file mode 100644 index 00000000..7ba6d3a0 --- /dev/null +++ b/include/beman/execution/detail/nest.hpp @@ -0,0 +1,23 @@ +// include/beman/execution/detail/nest.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_NEST +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_NEST + +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::execution { + struct nest_t { + template <::beman::execution::sender Sender, ::beman::execution::async_scope_token Token> + auto operator()(Sender&&, Token&&) const { + } + }; + inline constexpr nest_t nest{}; +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index d66fbf45..706d6c45 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -11,6 +11,8 @@ endif() list( APPEND execution_tests + exec-scope-concepts.test + exec-nest.test issue-144.test exec-on.test notify.test diff --git a/tests/beman/execution/exec-nest.test.cpp b/tests/beman/execution/exec-nest.test.cpp new file mode 100644 index 00000000..2ef4e58d --- /dev/null +++ b/tests/beman/execution/exec-nest.test.cpp @@ -0,0 +1,25 @@ +// tests/beman/execution/exec-nest.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace { + struct sender { + using sender_concept = test_std::sender_t; + }; + static_assert(test_std::sender); + static_assert(test_std::sender); + static_assert(test_std::sender); +} + +TEST(exec_nest) { + static_assert(std::same_as); + + sender sndr{}; + int token{}; + //test_std::nest(sndr, token); +} diff --git a/tests/beman/execution/exec-scope-concepts.test.cpp b/tests/beman/execution/exec-scope-concepts.test.cpp new file mode 100644 index 00000000..c492e8c6 --- /dev/null +++ b/tests/beman/execution/exec-scope-concepts.test.cpp @@ -0,0 +1,65 @@ +// tests/beman/execution/exec-scope-concepts.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace { + struct sender { + using sender_concept = test_std::sender_t; + }; + static_assert(test_std::sender); + + struct copyable {}; + static_assert(std::copyable); + struct non_copyable { + non_copyable() = default; + non_copyable(non_copyable const&) = delete; + }; + static_assert(not std::copyable); + + struct empty {}; + + template + struct wrap { + using sender_concept = test_std::sender_t; + std::remove_cvref_t sndr; + template + auto get_completion_signatures(E const& e) const noexcept { + return test_std::get_completion_signatures(sndr, e); + } + }; + static_assert(test_std::sender>); + + template + struct bad { + using sender_concept = test_std::sender_t; + }; + + + template class Wrap> + struct token { + Mem mem{}; + auto try_associate() -> Bool { return {}; } + auto disassociate() noexcept(Noexcept) -> void {} + template + auto wrap(Sender&& sndr) -> Wrap { return Wrap(std::forward(sndr)); } + }; +} + +TEST(exec_scope_concepts) { + static_assert(test_std::async_scope_token>); + static_assert(not test_std::async_scope_token>); + static_assert(not test_std::async_scope_token>); + static_assert(not test_std::async_scope_token>); + static_assert(not test_std::async_scope_token>); + static_assert(not test_std::async_scope_token); +} From 170901fc67cadda6141b1be1cf4f139ce16a6aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Thu, 15 May 2025 00:09:46 +0100 Subject: [PATCH 02/23] clang-format --- .../execution/detail/async_scope_token.hpp | 43 ++++++----- include/beman/execution/detail/nest.hpp | 13 ++-- tests/beman/execution/exec-nest.test.cpp | 20 ++--- .../execution/exec-scope-concepts.test.cpp | 73 ++++++++++--------- 4 files changed, 74 insertions(+), 75 deletions(-) diff --git a/include/beman/execution/detail/async_scope_token.hpp b/include/beman/execution/detail/async_scope_token.hpp index 4132b01d..595dc17b 100644 --- a/include/beman/execution/detail/async_scope_token.hpp +++ b/include/beman/execution/detail/async_scope_token.hpp @@ -13,30 +13,29 @@ // ---------------------------------------------------------------------------- namespace beman::execution::detail { - struct token_test_env {}; - - struct token_test_sender { - using sender_concept = ::beman::execution::sender_t; - auto get_completion_signatures(::beman::execution::detail::token_test_env) const noexcept { - return ::beman::execution::completion_signatures<>{}; - } - }; - static_assert(::beman::execution::sender<::beman::execution::detail::token_test_sender>); - static_assert(::beman::execution::sender_in<::beman::execution::detail::token_test_sender, ::beman::execution::detail::token_test_env>); -} +struct token_test_env {}; + +struct token_test_sender { + using sender_concept = ::beman::execution::sender_t; + auto get_completion_signatures(::beman::execution::detail::token_test_env) const noexcept { + return ::beman::execution::completion_signatures<>{}; + } +}; +static_assert(::beman::execution::sender<::beman::execution::detail::token_test_sender>); +static_assert(::beman::execution::sender_in<::beman::execution::detail::token_test_sender, + ::beman::execution::detail::token_test_env>); +} // namespace beman::execution::detail namespace beman::execution { - template - concept async_scope_token - = ::std::copyable - && requires(Token token) { - { token.try_associate() } -> ::std::same_as; - { token.disassociate() } noexcept; - { token.wrap(::std::declval<::beman::execution::detail::token_test_sender>()) } - -> ::beman::execution::sender_in<::beman::execution::detail::token_test_env>; - } - ; -} +template +concept async_scope_token = ::std::copyable && requires(Token token) { + { token.try_associate() } -> ::std::same_as; + { token.disassociate() } noexcept; + { + token.wrap(::std::declval<::beman::execution::detail::token_test_sender>()) + } -> ::beman::execution::sender_in<::beman::execution::detail::token_test_env>; +}; +} // namespace beman::execution // ---------------------------------------------------------------------------- diff --git a/include/beman/execution/detail/nest.hpp b/include/beman/execution/detail/nest.hpp index 7ba6d3a0..9002ec85 100644 --- a/include/beman/execution/detail/nest.hpp +++ b/include/beman/execution/detail/nest.hpp @@ -10,13 +10,12 @@ // ---------------------------------------------------------------------------- namespace beman::execution { - struct nest_t { - template <::beman::execution::sender Sender, ::beman::execution::async_scope_token Token> - auto operator()(Sender&&, Token&&) const { - } - }; - inline constexpr nest_t nest{}; -} +struct nest_t { + template <::beman::execution::sender Sender, ::beman::execution::async_scope_token Token> + auto operator()(Sender&&, Token&&) const {} +}; +inline constexpr nest_t nest{}; +} // namespace beman::execution // ---------------------------------------------------------------------------- diff --git a/tests/beman/execution/exec-nest.test.cpp b/tests/beman/execution/exec-nest.test.cpp index 2ef4e58d..19bc2ac3 100644 --- a/tests/beman/execution/exec-nest.test.cpp +++ b/tests/beman/execution/exec-nest.test.cpp @@ -8,18 +8,18 @@ // ---------------------------------------------------------------------------- namespace { - struct sender { - using sender_concept = test_std::sender_t; - }; - static_assert(test_std::sender); - static_assert(test_std::sender); - static_assert(test_std::sender); -} +struct sender { + using sender_concept = test_std::sender_t; +}; +static_assert(test_std::sender); +static_assert(test_std::sender); +static_assert(test_std::sender); +} // namespace TEST(exec_nest) { - static_assert(std::same_as); + static_assert(std::same_as); sender sndr{}; - int token{}; - //test_std::nest(sndr, token); + int token{}; + // test_std::nest(sndr, token); } diff --git a/tests/beman/execution/exec-scope-concepts.test.cpp b/tests/beman/execution/exec-scope-concepts.test.cpp index c492e8c6..c67b839b 100644 --- a/tests/beman/execution/exec-scope-concepts.test.cpp +++ b/tests/beman/execution/exec-scope-concepts.test.cpp @@ -13,47 +13,48 @@ // ---------------------------------------------------------------------------- namespace { - struct sender { - using sender_concept = test_std::sender_t; - }; - static_assert(test_std::sender); +struct sender { + using sender_concept = test_std::sender_t; +}; +static_assert(test_std::sender); - struct copyable {}; - static_assert(std::copyable); - struct non_copyable { - non_copyable() = default; - non_copyable(non_copyable const&) = delete; - }; - static_assert(not std::copyable); +struct copyable {}; +static_assert(std::copyable); +struct non_copyable { + non_copyable() = default; + non_copyable(const non_copyable&) = delete; +}; +static_assert(not std::copyable); - struct empty {}; +struct empty {}; - template - struct wrap { - using sender_concept = test_std::sender_t; - std::remove_cvref_t sndr; - template - auto get_completion_signatures(E const& e) const noexcept { - return test_std::get_completion_signatures(sndr, e); - } - }; - static_assert(test_std::sender>); +template +struct wrap { + using sender_concept = test_std::sender_t; + std::remove_cvref_t sndr; + template + auto get_completion_signatures(const E& e) const noexcept { + return test_std::get_completion_signatures(sndr, e); + } +}; +static_assert(test_std::sender>); - template - struct bad { - using sender_concept = test_std::sender_t; - }; +template +struct bad { + using sender_concept = test_std::sender_t; +}; - - template class Wrap> - struct token { - Mem mem{}; - auto try_associate() -> Bool { return {}; } - auto disassociate() noexcept(Noexcept) -> void {} - template - auto wrap(Sender&& sndr) -> Wrap { return Wrap(std::forward(sndr)); } - }; -} +template class Wrap> +struct token { + Mem mem{}; + auto try_associate() -> Bool { return {}; } + auto disassociate() noexcept(Noexcept) -> void {} + template + auto wrap(Sender&& sndr) -> Wrap { + return Wrap(std::forward(sndr)); + } +}; +} // namespace TEST(exec_scope_concepts) { static_assert(test_std::async_scope_token>); From 72273744a31b7b3db7b4f8270f5a7f9d975883ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 26 May 2025 16:39:42 +0200 Subject: [PATCH 03/23] updated the simple_counting_scope implementation --- .../detail/simple_counting_scope.hpp | 186 ++++++++++++------ tests/beman/execution/CMakeLists.txt | 4 +- .../exec-scope-simple-counting.test.cpp | 125 ++++++++++++ tests/beman/execution/exec-scounting.test.cpp | 99 ---------- .../execution/include/test/execution.hpp | 29 +++ .../include/test/inline_scheduler.hpp | 54 +++++ 6 files changed, 339 insertions(+), 158 deletions(-) create mode 100644 tests/beman/execution/exec-scope-simple-counting.test.cpp delete mode 100644 tests/beman/execution/exec-scounting.test.cpp create mode 100644 tests/beman/execution/include/test/inline_scheduler.hpp diff --git a/include/beman/execution/detail/simple_counting_scope.hpp b/include/beman/execution/detail/simple_counting_scope.hpp index dc84e91e..5f593874 100644 --- a/include/beman/execution/detail/simple_counting_scope.hpp +++ b/include/beman/execution/detail/simple_counting_scope.hpp @@ -7,7 +7,15 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include #include @@ -18,12 +26,89 @@ namespace beman::execution { class simple_counting_scope; } +namespace beman::execution::detail { +struct simple_counting_scope_join_t {}; +struct simple_counting_scope_state_base { + ::beman::execution::simple_counting_scope* scope; + ::beman::execution::detail::simple_counting_scope_state_base* next{}; + simple_counting_scope_state_base(::beman::execution::simple_counting_scope* s) : scope(s) {} + virtual ~simple_counting_scope_state_base() = default; + auto start() noexcept -> void; + + virtual auto complete() noexcept -> void = 0; + virtual auto complete_inline() noexcept -> void = 0; +}; + +template +struct completion_signatures_for_impl< + ::beman::execution::detail::basic_sender<::beman::execution::detail::simple_counting_scope_join_t, + ::beman::execution::simple_counting_scope*>, + Env> { + using type = ::beman::execution::completion_signatures<::beman::execution::set_value_t()>; +}; + +template <> +struct impls_for<::beman::execution::detail::simple_counting_scope_join_t> + : ::beman::execution::detail::default_impls { + template + struct state : ::beman::execution::detail::simple_counting_scope_state_base { + ::std::remove_cvref_t& receiver; + using op_t = decltype(::beman::execution::connect( + ::beman::execution::schedule(::beman::execution::get_scheduler(::beman::execution::get_env(receiver))), + receiver)); + op_t op; + state(::beman::execution::simple_counting_scope* s, Receiver& r) + : ::beman::execution::detail::simple_counting_scope_state_base(s), + receiver(r), + op(::beman::execution::connect(::beman::execution::schedule(::beman::execution::get_scheduler( + ::beman::execution::get_env(this->receiver))), + this->receiver)) {} + auto complete() noexcept -> void override { ::beman::execution::start(this->op); } + auto complete_inline() noexcept -> void override { + ::beman::execution::set_value(::std::move(this->receiver)); + } + }; + + static constexpr auto get_state = [](auto&& sender, Receiver& receiver) noexcept(false) { + auto [_, self] = sender; + return state(self, receiver); + }; + static constexpr auto start = [](::beman::execution::detail::simple_counting_scope_state_base& s, auto&) noexcept { + s.start(); + }; +}; +} // namespace beman::execution::detail + // ---------------------------------------------------------------------------- class beman::execution::simple_counting_scope : ::beman::execution::detail::immovable { + private: + friend struct ::beman::execution::detail::simple_counting_scope_state_base; + enum class state_t : unsigned char { + unused, + open, + open_and_joining, + closed, + closed_and_joining, + unused_and_closed, + joined + }; + public: class token; - class assoc; + + simple_counting_scope() = default; + simple_counting_scope(simple_counting_scope&&) = delete; + ~simple_counting_scope() { + switch (this->state) { + default: + ::std::terminate(); + case state_t::unused: + case state_t::unused_and_closed: + case state_t::joined: + break; + } + } auto get_token() noexcept -> token; auto close() noexcept -> void { @@ -42,43 +127,24 @@ class beman::execution::simple_counting_scope : ::beman::execution::detail::immo } } auto join() noexcept { - bool complete{false}; - { - ::std::lock_guard kerberos(this->mutex); - if (0u == this->count) { - this->state = state_t::joined; - complete = true; - } - } - if (complete) { - n.complete(); - } - return ::beman::execution::detail::notify(this->n); + return ::beman::execution::detail::make_sender(::beman::execution::detail::simple_counting_scope_join_t{}, + this); } private: - enum class state_t : unsigned char { - unused, - open, - open_and_joining, - closed, - closed_and_joining, - unused_and_closed, - joined - }; - friend class assoc; - auto try_associate() noexcept -> simple_counting_scope* { + friend class token; + auto try_associate() noexcept -> bool { ::std::lock_guard lock(this->mutex); switch (this->state) { default: - return nullptr; + return false; case state_t::unused: this->state = state_t::open; // fall-through! [[fallthrough]]; case state_t::open: case state_t::open_and_joining: ++this->count; - return this; + return true; } } auto disassociate() noexcept -> void { @@ -88,43 +154,25 @@ class beman::execution::simple_counting_scope : ::beman::execution::detail::immo return; this->state = state_t::joined; } - n.complete(); + this->complete(); + } + auto complete() noexcept -> void { + auto current{[this] { + ::std::lock_guard lock(this->mutex); + return ::std::exchange(this->head, nullptr); + }()}; + while (current) { + ::std::exchange(current, current->next)->complete(); + } } ::std::mutex mutex; ::std::size_t count{}; state_t state{state_t::unused}; - ::beman::execution::detail::notifier n; + ::beman::execution::detail::simple_counting_scope_state_base* head{}; }; // ---------------------------------------------------------------------------- -// NOLINTBEGIN(misc-unconventional-assign-operator,hicpp-special-member-functions) -class beman::execution::simple_counting_scope::assoc { - public: - assoc() = default; - assoc(const assoc& other) noexcept : assoc(other.scope) {} - assoc(assoc&& other) noexcept : scope(::std::exchange(other.scope, nullptr)) {} - ~assoc() { - if (this->scope) - this->scope->disassociate(); - } - - auto operator=(assoc other) noexcept -> assoc& { - ::std::swap(this->scope, other.scope); - return *this; - } - - explicit operator bool() const noexcept { return this->scope != nullptr; } - - private: - friend class beman::execution::simple_counting_scope::token; - explicit assoc(beman::execution::simple_counting_scope* scp) : scope(scp ? scp->try_associate() : nullptr) {} - beman::execution::simple_counting_scope* scope{}; -}; -// NOLINTEND(misc-unconventional-assign-operator,hicpp-special-member-functions) - -// ---------------------------------------------------------------------------- - class beman::execution::simple_counting_scope::token { public: template <::beman::execution::sender Sender> @@ -132,9 +180,8 @@ class beman::execution::simple_counting_scope::token { return ::std::forward(sender); } - auto try_associate() const -> beman::execution::simple_counting_scope::assoc { - return beman::execution::simple_counting_scope::assoc(this->scope); - } + auto try_associate() const noexcept -> bool { return this->scope->try_associate(); } + auto disassociate() const noexcept -> void { this->scope->disassociate(); } private: friend class beman::execution::simple_counting_scope; @@ -149,6 +196,29 @@ inline auto beman::execution::simple_counting_scope::get_token() noexcept return beman::execution::simple_counting_scope::token(this); } +auto beman::execution::detail::simple_counting_scope_state_base::start() noexcept -> void { + switch (this->scope->state) { + case ::beman::execution::simple_counting_scope::state_t::unused: + case ::beman::execution::simple_counting_scope::state_t::unused_and_closed: + case ::beman::execution::simple_counting_scope::state_t::joined: + this->scope->state = ::beman::execution::simple_counting_scope::state_t::joined; + this->complete_inline(); + return; + case ::beman::execution::simple_counting_scope::state_t::open: + this->scope->state = ::beman::execution::simple_counting_scope::state_t::open_and_joining; + break; + case ::beman::execution::simple_counting_scope::state_t::open_and_joining: + break; + case ::beman::execution::simple_counting_scope::state_t::closed: + this->scope->state = ::beman::execution::simple_counting_scope::state_t::closed_and_joining; + break; + case ::beman::execution::simple_counting_scope::state_t::closed_and_joining: + break; + } + ::std::lock_guard kerberos(this->scope->mutex); + this->next = std::exchange(this->scope->head, this); +} + // ---------------------------------------------------------------------------- #endif diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index 706d6c45..0532af95 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -11,12 +11,12 @@ endif() list( APPEND execution_tests + exec-scope-simple-counting.test exec-scope-concepts.test exec-nest.test issue-144.test exec-on.test notify.test - exec-scounting.test exec-awaitable.test allocator-requirements-general.test exec-connect.test @@ -134,6 +134,7 @@ foreach(test ${execution_tests}) add_test(NAME ${TEST_EXE} COMMAND $) endforeach() +if(FALSE) if(NOT PROJECT_IS_TOP_LEVEL AND BEMAN_EXECUTION_ENABLE_TESTING) # test if the targets are findable from the build directory # cmake-format: off @@ -156,3 +157,4 @@ if(NOT PROJECT_IS_TOP_LEVEL AND BEMAN_EXECUTION_ENABLE_TESTING) ) # cmake-format: on endif() +endif() diff --git a/tests/beman/execution/exec-scope-simple-counting.test.cpp b/tests/beman/execution/exec-scope-simple-counting.test.cpp new file mode 100644 index 00000000..e88b0c0e --- /dev/null +++ b/tests/beman/execution/exec-scope-simple-counting.test.cpp @@ -0,0 +1,125 @@ +// tests/beman/execution/exec-scope-simple-counting.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace { + +auto general() -> void { + using scope = test_std::simple_counting_scope; + using token = scope::token; + + //-dk:TODO static_assert(requires(token const& tok){ { tok.wrap(test_std::just(10)) } noexcept -> + //std::same_as; }); -dk:TODO static_assert(requires(token const& tok){ { + //tok.try_associate() } noexcept -> std::same_as; }); + static_assert(requires(const token& tok) { tok.try_associate(); }); + //-dk:TODO static_assert(requires(token const& tok){ { tok.disassociate() } noexcept; }); + + static_assert(noexcept(scope{})); + static_assert(!std::is_move_constructible_v); + static_assert(!std::is_copy_constructible_v); + static_assert(requires(scope sc) { + { sc.get_token() } noexcept -> std::same_as; + }); + static_assert(requires(scope sc) { + { sc.close() } noexcept -> std::same_as; + }); + static_assert(requires(scope sc) { + { sc.join() } noexcept; + }); +} + +struct join_receiver { + using receiver_concept = test_std::receiver_t; + + struct env { + auto query(const test_std::get_scheduler_t&) const noexcept -> test::inline_scheduler { return {}; } + }; + + bool& called; + auto set_value() && noexcept { this->called = true; } + auto get_env() const noexcept -> env { return {}; } +}; + +auto ctor() -> void { + { + test_std::simple_counting_scope scope; + } + test::death([] { + test_std::simple_counting_scope scope; + scope.get_token().try_associate(); + }); + test::death([] { + test_std::simple_counting_scope scope; + scope.get_token().try_associate(); + bool called{false}; + auto state(test_std::connect(scope.join(), join_receiver{called})); + test_std::start(state); + }); + test::death([] { + test_std::simple_counting_scope scope; + scope.get_token().try_associate(); + scope.close(); + }); + test::death([] { + test_std::simple_counting_scope scope; + scope.get_token().try_associate(); + bool called{false}; + auto state(test_std::connect(scope.join(), join_receiver{called})); + test_std::start(state); + scope.close(); + }); + { + test_std::simple_counting_scope scope; + scope.close(); + } + { + test_std::simple_counting_scope scope; + scope.join(); + } +} + +auto mem() -> void { + { + + test_std::simple_counting_scope scope; + test_std::simple_counting_scope::token token{scope.get_token()}; + + ASSERT(true == token.try_associate()); + token.disassociate(); + scope.close(); + ASSERT(false == token.try_associate()); + + test_std::sync_wait(scope.join()); + } + { + test_std::simple_counting_scope scope; + ASSERT(true == scope.get_token().try_associate()); + bool called{false}; + ASSERT(called == false); + auto state(test_std::connect(scope.join(), join_receiver(called))); + ASSERT(called == false); + test_std::start(state); + ASSERT(called == false); + + scope.get_token().disassociate(); + ASSERT(called == true); + } +} + +} // namespace + +TEST(exec_scope_simple_counting) { + general(); + ctor(); + mem(); +} diff --git a/tests/beman/execution/exec-scounting.test.cpp b/tests/beman/execution/exec-scounting.test.cpp deleted file mode 100644 index c3bb3682..00000000 --- a/tests/beman/execution/exec-scounting.test.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// src/beman/execution/tests/exec-scounting-ctor.test.cpp -*-C++-*- -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#include -#include -#include -#include -#include - -// ---------------------------------------------------------------------------- - -namespace { -struct receiver { - using receiver_concept = test_std::receiver_t; - bool& flag; - auto set_value() && noexcept -> void { this->flag = true; } -}; - -auto test_scounting_ctor() -> void { - static_assert(std::default_initializable); - static_assert(noexcept(test_std::simple_counting_scope{})); - static_assert(std::destructible); - static_assert(not std::copy_constructible); - static_assert(not std::move_constructible); - static_assert(not std::assignable_from); - static_assert(not std::assignable_from); - - // a scope is created unused and can be destroyed: - { - test_std::simple_counting_scope scope{}; - test::use(scope); - } - // a scope can be used and closed and can be destroyed: - { - test_std::simple_counting_scope scope{}; - scope.close(); - } - // a scope can be joined and destroyed: - { - test_std::simple_counting_scope scope{}; - test_std::sync_wait(scope.join()); - } - // a scope can be closed, joined, and destroyed: - { - test_std::simple_counting_scope scope{}; - scope.close(); - test_std::sync_wait(scope.join()); - } - // a scope with an outstanding token can be destroyed: - { - std::optional scope_opt; - scope_opt.emplace(); - auto token{scope_opt->get_token()}; - scope_opt.reset(); - test::use(token); - } -} - -auto test_scounting_assoc_ctor() -> void { - static_assert(noexcept(test_std::simple_counting_scope::assoc{})); - ASSERT(test_std::simple_counting_scope::assoc{} ? false : true); -} - -auto test_scounting_associate() -> void { - test_std::simple_counting_scope scope; - std::optional assoc(scope.get_token().try_associate()); - ASSERT(*assoc ? true : false); - ASSERT(scope.get_token().try_associate() ? true : false); - std::optional assoc2(scope.get_token().try_associate()); - scope.close(); - ASSERT(scope.get_token().try_associate() ? false : true); - - bool flag{}; - auto op{test_std::connect(scope.join(), receiver{flag})}; - ASSERT(flag == false); - test_std::start(op); - - ASSERT(flag == false); - assoc.reset(); - - std::optional assoc3{test_std::simple_counting_scope::assoc()}; - *assoc3 = ::std::move(*assoc2); - - ASSERT(flag == false); - assoc2.reset(); - - ASSERT(flag == false); - assoc3.reset(); - ASSERT(flag == true); -} -} // namespace - -// ---------------------------------------------------------------------------- - -TEST(exec_scounting_ctor) { - test_scounting_ctor(); - test_scounting_assoc_ctor(); - test_scounting_associate(); -} diff --git a/tests/beman/execution/include/test/execution.hpp b/tests/beman/execution/include/test/execution.hpp index 13ac3469..a68459ac 100644 --- a/tests/beman/execution/include/test/execution.hpp +++ b/tests/beman/execution/include/test/execution.hpp @@ -7,6 +7,12 @@ #include #include #include +#ifndef _MSC_VER +#include +#include +#include +#include +#endif #undef NDEBUG #include @@ -41,6 +47,29 @@ struct throws { auto operator=(const throws&) noexcept(false) -> throws& = default; }; +inline auto death([[maybe_unused]] auto fun, + [[maybe_unused]] ::std::source_location location = std::source_location::current()) noexcept + -> void { +#ifndef _MSC_VER + switch (::pid_t rc = ::fork()) { + default: { + int stat{}; + ASSERT(rc == ::wait(&stat)); + if (stat == EXIT_SUCCESS) { + ::std::cerr << "failed death test at " + << "file=" << location.file_name() << ":" << location.line() << "\n" + << std::flush; + ASSERT(stat != EXIT_SUCCESS); + } + } break; + case 0: { + ::close(2); + fun(); + ::std::exit(EXIT_SUCCESS); + } break; + } +#endif +} } // namespace test #endif // INCLUDED_TEST_EXECUTION diff --git a/tests/beman/execution/include/test/inline_scheduler.hpp b/tests/beman/execution/include/test/inline_scheduler.hpp new file mode 100644 index 00000000..694864fd --- /dev/null +++ b/tests/beman/execution/include/test/inline_scheduler.hpp @@ -0,0 +1,54 @@ +// tests/beman/execution/include/test/inline_scheduler.hpp -*-C++-*- +// ---------------------------------------------------------------------------- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// ---------------------------------------------------------------------------- + +#ifndef INCLUDED_TESTS_BEMAN_EXECUTION_INCLUDE_TEST_INLINE_SCHEDULER +#define INCLUDED_TESTS_BEMAN_EXECUTION_INCLUDE_TEST_INLINE_SCHEDULER + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace test { +struct inline_scheduler { + struct env { + inline_scheduler query(const test_std::get_completion_scheduler_t&) const noexcept { + return {}; + } + }; + template + struct state { + using operation_state_concept = test_std::operation_state_t; + std::remove_cvref_t receiver; + void start() & noexcept { ::beman::execution::set_value(std::move(receiver)); } + }; + struct sender { + using sender_concept = test_std::sender_t; + using completion_signatures = test_std::completion_signatures<::beman::execution::set_value_t()>; + + env get_env() const noexcept { return {}; } + template + state connect(Receiver&& receiver) { + return {std::forward(receiver)}; + } + }; + static_assert(test_std::sender); + + using scheduler_concept = test_std::scheduler_t; + constexpr sender schedule() noexcept { return {}; } + bool operator==(const inline_scheduler&) const = default; +}; +} // namespace test + +// ---------------------------------------------------------------------------- + +#endif From 30bfa8f9da201df5db47bccf391a46091c253aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 26 May 2025 17:22:07 +0200 Subject: [PATCH 04/23] added missing token tests --- .../exec-scope-simple-counting.test.cpp | 76 +++++++++++-------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/tests/beman/execution/exec-scope-simple-counting.test.cpp b/tests/beman/execution/exec-scope-simple-counting.test.cpp index e88b0c0e..0d41d250 100644 --- a/tests/beman/execution/exec-scope-simple-counting.test.cpp +++ b/tests/beman/execution/exec-scope-simple-counting.test.cpp @@ -18,59 +18,54 @@ auto general() -> void { using scope = test_std::simple_counting_scope; using token = scope::token; - //-dk:TODO static_assert(requires(token const& tok){ { tok.wrap(test_std::just(10)) } noexcept -> - //std::same_as; }); -dk:TODO static_assert(requires(token const& tok){ { - //tok.try_associate() } noexcept -> std::same_as; }); - static_assert(requires(const token& tok) { tok.try_associate(); }); - //-dk:TODO static_assert(requires(token const& tok){ { tok.disassociate() } noexcept; }); + //static_assert(requires(token const& tok){ { tok.wrap(test_std::just(10)) } noexcept; }); + static_assert(requires(token const& tok){ { tok.try_associate() } noexcept -> std::same_as; }); + static_assert(requires(token const& tok){ tok.try_associate(); }); + static_assert(requires(token const& tok){ { tok.disassociate() } noexcept; }); static_assert(noexcept(scope{})); - static_assert(!std::is_move_constructible_v); - static_assert(!std::is_copy_constructible_v); - static_assert(requires(scope sc) { - { sc.get_token() } noexcept -> std::same_as; - }); - static_assert(requires(scope sc) { - { sc.close() } noexcept -> std::same_as; - }); - static_assert(requires(scope sc) { - { sc.join() } noexcept; - }); + static_assert(! std::is_move_constructible_v); + static_assert(! std::is_copy_constructible_v); + static_assert(requires(scope sc){ { sc.get_token() } noexcept -> std::same_as; }); + static_assert(requires(scope sc){ { sc.close() } noexcept -> std::same_as; }); + static_assert(requires(scope sc){ { sc.join() } noexcept; }); } struct join_receiver { using receiver_concept = test_std::receiver_t; struct env { - auto query(const test_std::get_scheduler_t&) const noexcept -> test::inline_scheduler { return {}; } + auto query(test_std::get_scheduler_t const&) const noexcept -> test::inline_scheduler { + return {}; + } }; bool& called; - auto set_value() && noexcept { this->called = true; } - auto get_env() const noexcept -> env { return {}; } + auto set_value() && noexcept { this->called = true; } + auto get_env() const noexcept -> env { return {}; } }; auto ctor() -> void { { test_std::simple_counting_scope scope; } - test::death([] { + test::death([]{ test_std::simple_counting_scope scope; scope.get_token().try_associate(); }); - test::death([] { + test::death([]{ test_std::simple_counting_scope scope; scope.get_token().try_associate(); bool called{false}; auto state(test_std::connect(scope.join(), join_receiver{called})); test_std::start(state); }); - test::death([] { + test::death([]{ test_std::simple_counting_scope scope; scope.get_token().try_associate(); scope.close(); }); - test::death([] { + test::death([]{ test_std::simple_counting_scope scope; scope.get_token().try_associate(); bool called{false}; @@ -91,15 +86,15 @@ auto ctor() -> void { auto mem() -> void { { - test_std::simple_counting_scope scope; - test_std::simple_counting_scope::token token{scope.get_token()}; + test_std::simple_counting_scope scope; + test_std::simple_counting_scope::token token{scope.get_token()}; - ASSERT(true == token.try_associate()); - token.disassociate(); - scope.close(); - ASSERT(false == token.try_associate()); + ASSERT(true == token.try_associate()); + token.disassociate(); + scope.close(); + ASSERT(false == token.try_associate()); - test_std::sync_wait(scope.join()); + test_std::sync_wait(scope.join()); } { test_std::simple_counting_scope scope; @@ -115,11 +110,30 @@ auto mem() -> void { ASSERT(called == true); } } +auto token() -> void { + test_std::simple_counting_scope scope; + auto const tok{scope.get_token()}; + auto sndr{tok.wrap(test_std::just(10))}; + static_assert(std::same_as); + + ASSERT(true == tok.try_associate()); + bool called{false}; + auto state(test_std::connect(scope.join(), join_receiver(called))); + test_std::start(state); + ASSERT(false == called); + scope.close(); + ASSERT(false == called); + ASSERT(false == tok.try_associate()); + ASSERT(false == called); + tok.disassociate(); + ASSERT(true == called); +} -} // namespace +} TEST(exec_scope_simple_counting) { general(); ctor(); mem(); + token(); } From 38efec7437f9c570c04be93346d8fe696c60e744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Fri, 16 May 2025 20:02:47 +0100 Subject: [PATCH 05/23] working on bring the scope up to date --- .../beman/execution/detail/counting_scope.hpp | 18 ++++++++++++++++++ include/beman/execution/detail/spawn.hpp | 18 ++++++++++++++++++ .../beman/execution/detail/spawn_future.hpp | 18 ++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 include/beman/execution/detail/counting_scope.hpp create mode 100644 include/beman/execution/detail/spawn.hpp create mode 100644 include/beman/execution/detail/spawn_future.hpp diff --git a/include/beman/execution/detail/counting_scope.hpp b/include/beman/execution/detail/counting_scope.hpp new file mode 100644 index 00000000..3a06f298 --- /dev/null +++ b/include/beman/execution/detail/counting_scope.hpp @@ -0,0 +1,18 @@ +// include/beman/execution/detail/counting_scope.hpp -*-C++-*- +// ---------------------------------------------------------------------------- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// ---------------------------------------------------------------------------- + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_COUNTING_SCOPE +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_COUNTING_SCOPE + +// ---------------------------------------------------------------------------- + +namespace nstd { + namespace xxx { + } +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/spawn.hpp b/include/beman/execution/detail/spawn.hpp new file mode 100644 index 00000000..873c1107 --- /dev/null +++ b/include/beman/execution/detail/spawn.hpp @@ -0,0 +1,18 @@ +// include/beman/execution/detail/spawn.hpp -*-C++-*- +// ---------------------------------------------------------------------------- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// ---------------------------------------------------------------------------- + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_SPAWN +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_SPAWN + +// ---------------------------------------------------------------------------- + +namespace nstd { + namespace xxx { + } +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/spawn_future.hpp b/include/beman/execution/detail/spawn_future.hpp new file mode 100644 index 00000000..cf823f0f --- /dev/null +++ b/include/beman/execution/detail/spawn_future.hpp @@ -0,0 +1,18 @@ +// include/beman/execution/detail/spawn_future.hpp -*-C++-*- +// ---------------------------------------------------------------------------- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// ---------------------------------------------------------------------------- + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_SPAWN_FUTURE +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_SPAWN_FUTURE + +// ---------------------------------------------------------------------------- + +namespace nstd { + namespace xxx { + } +} + +// ---------------------------------------------------------------------------- + +#endif From 87bd8ec5703222a4cba682ee68f326d7da46708c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Mon, 2 Jun 2025 22:39:02 +0100 Subject: [PATCH 06/23] added state_base --- include/beman/execution/detail/as_tuple.hpp | 21 ++++ .../beman/execution/detail/spawn_future.hpp | 56 ++++++++++- src/beman/execution/CMakeLists.txt | 9 ++ tests/beman/execution/CMakeLists.txt | 1 + .../execution/exec-spawn-future.test.cpp | 95 +++++++++++++++++++ 5 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 include/beman/execution/detail/as_tuple.hpp create mode 100644 tests/beman/execution/exec-spawn-future.test.cpp diff --git a/include/beman/execution/detail/as_tuple.hpp b/include/beman/execution/detail/as_tuple.hpp new file mode 100644 index 00000000..a08abc40 --- /dev/null +++ b/include/beman/execution/detail/as_tuple.hpp @@ -0,0 +1,21 @@ +// include/beman/execution/detail/as_tuple.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_AS_TUPLE +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_AS_TUPLE + +#include + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { + template struct as_tuple; + template + struct as_tuple { using type = ::beman::execution::detail::decayed_tuple; }; + + template using as_tuple_t = typename ::beman::execution::detail::as_tuple::type; +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/spawn_future.hpp b/include/beman/execution/detail/spawn_future.hpp index cf823f0f..df8dd486 100644 --- a/include/beman/execution/detail/spawn_future.hpp +++ b/include/beman/execution/detail/spawn_future.hpp @@ -1,16 +1,62 @@ // include/beman/execution/detail/spawn_future.hpp -*-C++-*- -// ---------------------------------------------------------------------------- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// ---------------------------------------------------------------------------- #ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_SPAWN_FUTURE #define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_SPAWN_FUTURE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + // ---------------------------------------------------------------------------- -namespace nstd { - namespace xxx { - } +namespace beman::execution::detail { + template struct non_throwing_args_copy; + template struct non_throwing_args_copy { + static constexpr bool value = (true && ... && ::std::is_nothrow_constructible_v<::std::decay_t, A> ); + }; + template inline constexpr bool non_throwing_args_copy_v{non_throwing_args_copy::value}; + + template struct spawn_future_state_base; + template + struct spawn_future_state_base<::beman::execution::completion_signatures> { + using variant_t = ::beman::execution::detail::meta::unique< + ::std::conditional_t< + (true && ... && non_throwing_args_copy_v), + ::std::variant<::std::monostate, ::beman::execution::detail::as_tuple_t...>, + ::std::variant<::std::monostate, ::std::tuple<::beman::execution::set_error_t, ::std::exception_ptr>, ::beman::execution::detail::as_tuple_t...> + > + >; + + variant_t result{}; + virtual ~spawn_future_state_base() = default; + virtual auto complete() noexcept -> void = 0; + }; + + class spawn_future_t { + public: + template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok, typename Env> + requires ::beman::execution::detail::queryable<::std::remove_cvref_t> + auto operator()(Sndr&&, Tok&&, Env&&) const { + } + template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok> + auto operator()(Sndr&& sndr, Tok&& tok) const { + return (*this)(::std::forward(sndr), ::std::forward(tok), ::beman::execution::empty_env{}); + } + }; +} + +namespace beman::execution { + using spawn_future_t = ::beman::execution::detail::spawn_future_t; + inline constexpr spawn_future_t spawn_future{}; } // ---------------------------------------------------------------------------- diff --git a/src/beman/execution/CMakeLists.txt b/src/beman/execution/CMakeLists.txt index ea6aecae..bc641671 100644 --- a/src/beman/execution/CMakeLists.txt +++ b/src/beman/execution/CMakeLists.txt @@ -27,6 +27,8 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/execution.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/functional.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/stop_token.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution26/execution.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution26/stop_token.hpp PUBLIC FILE_SET ${TARGET_NAME}_detail_headers TYPE @@ -39,6 +41,8 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/apply_sender.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/as_awaitable.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/as_except_ptr.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/as_tuple.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/async_scope_token.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/atomic_intrusive_stack.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/await_result_type.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/await_suspend_result.hpp @@ -47,6 +51,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/basic_receiver.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/basic_sender.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/basic_state.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/bulk.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/call_result_t.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/callable.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/check_type_alias_exist.hpp @@ -65,6 +70,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/connect_awaitable.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/connect_result_t.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/continues_on.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/counting_scope.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/decayed_same_as.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/decayed_tuple.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/decayed_type_list.hpp @@ -119,6 +125,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/meta_transform.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/meta_unique.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/movable_value.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/nest.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/never_stop_token.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/nostopstate.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/nothrow_callable.hpp @@ -156,6 +163,8 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/simple_counting_scope.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/single_sender.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/single_sender_value_type.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/spawn.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/spawn_future.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/split.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/start.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/starts_on.hpp diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index 0532af95..8bce086d 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -12,6 +12,7 @@ list( APPEND execution_tests exec-scope-simple-counting.test + exec-spawn-future.test exec-scope-concepts.test exec-nest.test issue-144.test diff --git a/tests/beman/execution/exec-spawn-future.test.cpp b/tests/beman/execution/exec-spawn-future.test.cpp new file mode 100644 index 00000000..7320b78f --- /dev/null +++ b/tests/beman/execution/exec-spawn-future.test.cpp @@ -0,0 +1,95 @@ +// tests/beman/execution/exec-spawn-future.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace +{ + struct non_env { ~non_env() = delete; }; + static_assert(not test_detail::queryable); + + struct env {}; + static_assert(test_detail::queryable); + + struct non_sender { + }; + static_assert(not test_std::sender); + + struct sender { + using sender_concept = test_std::sender_t; + }; + static_assert(test_std::sender); + + template + struct token { + auto try_associate() -> bool { return {}; } + auto disassociate() noexcept(Noexcept) -> void {} + template + auto wrap(Sender&& sender) -> Sender { return std::forward(sender); } + }; + static_assert(test_std::async_scope_token>); + static_assert(not test_std::async_scope_token>); + + struct throws { + throws() = default; + throws(throws&&) noexcept(false) = default; + }; + static_assert(!std::is_nothrow_constructible_v, throws>); + + template + auto test_spawn_future_interface(Sender&& sndr, Token&& tok, Env&& e = Env{}) -> void { + if constexpr (!std::same_as, non_env>){ + static_assert(Expect == requires{ test_std::spawn_future(std::forward(sndr), std::forward(tok)); }); + } + static_assert(Expect == requires{ test_std::spawn_future(std::forward(sndr), std::forward(tok), std::forward(e)); }); + } + + template + struct state_base: test_detail::spawn_future_state_base { + auto complete() noexcept -> void override {} + }; + auto test_state_base() { + static_assert(noexcept(std::declval>&>().complete())); + using state0_t = state_base>; + [[maybe_unused]] state0_t b0; + static_assert(std::same_as, state0_t::variant_t>); + static_assert(std::same_as); + + using state1_t = state_base>; + [[maybe_unused]] state1_t b1; + static_assert(std::same_as>, state1_t::variant_t>); + static_assert(std::same_as); + + using state2_t = state_base>; + [[maybe_unused]] state2_t b2; + static_assert(std::same_as, std::tuple>, typename state2_t::variant_t>); + static_assert(std::same_as); + + using state3_t = state_base>; + [[maybe_unused]] state3_t b3; + static_assert(std::same_as, std::tuple>, typename state3_t::variant_t>); + static_assert(std::same_as); + + using state4_t = state_base>; + [[maybe_unused]] state4_t b4; + static_assert(std::same_as, std::tuple>, typename state4_t::variant_t>); + static_assert(std::same_as); + } +} + +TEST(exec_spawn_future) { + static_assert(std::same_as); + test_spawn_future_interface(sender{}, token{}); + test_spawn_future_interface(non_sender{}, token{}); + test_spawn_future_interface(sender{}, token{}); + test_spawn_future_interface(sender{}, token{}, *new non_env{}); + + test_state_base(); +} From 131457be3f88848b0aac566137f92b3cd89919ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Tue, 3 Jun 2025 00:33:28 +0100 Subject: [PATCH 07/23] added spawn_future receiver --- .../beman/execution/detail/spawn_future.hpp | 40 ++++++++- .../execution/exec-spawn-future.test.cpp | 85 ++++++++++++++++++- 2 files changed, 122 insertions(+), 3 deletions(-) diff --git a/include/beman/execution/detail/spawn_future.hpp b/include/beman/execution/detail/spawn_future.hpp index df8dd486..b01cca38 100644 --- a/include/beman/execution/detail/spawn_future.hpp +++ b/include/beman/execution/detail/spawn_future.hpp @@ -10,6 +10,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -28,9 +32,10 @@ namespace beman::execution::detail { template struct spawn_future_state_base; template struct spawn_future_state_base<::beman::execution::completion_signatures> { + static constexpr bool has_non_throwing_args_copy = (true && ... && non_throwing_args_copy_v); using variant_t = ::beman::execution::detail::meta::unique< ::std::conditional_t< - (true && ... && non_throwing_args_copy_v), + has_non_throwing_args_copy, ::std::variant<::std::monostate, ::beman::execution::detail::as_tuple_t...>, ::std::variant<::std::monostate, ::std::tuple<::beman::execution::set_error_t, ::std::exception_ptr>, ::beman::execution::detail::as_tuple_t...> > @@ -41,6 +46,39 @@ namespace beman::execution::detail { virtual auto complete() noexcept -> void = 0; }; + template + struct spawn_future_receiver { + using receiver_concept = ::beman::execution::receiver_t; + using state_t = ::beman::execution::detail::spawn_future_state_base; + + state_t* state{}; + + template + auto set_value(A&&... a) && noexcept -> void { + this->set_complete<::beman::execution::set_value_t>(::std::forward(a)...); + } + template + auto set_error(E&& e) && noexcept -> void { + this->set_complete<::beman::execution::set_error_t>(::std::forward(e)); + } + auto set_stopped() && noexcept -> void { + this->set_complete<::beman::execution::set_stopped_t>(); + } + + template + auto set_complete(T&&... t) noexcept { + try { + this->state->result.template emplace<::beman::execution::detail::decayed_tuple>(Tag(), ::std::forward(t)...); + } + catch (...) { + if constexpr (!state_t::has_non_throwing_args_copy) { + this->state->result.template emplace<::std::tuple<::beman::execution::set_error_t, ::std::exception_ptr>>(::beman::execution::set_error_t{}, ::std::current_exception()); + } + } + this->state->complete(); + } + }; + class spawn_future_t { public: template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok, typename Env> diff --git a/tests/beman/execution/exec-spawn-future.test.cpp b/tests/beman/execution/exec-spawn-future.test.cpp index 7320b78f..42328a47 100644 --- a/tests/beman/execution/exec-spawn-future.test.cpp +++ b/tests/beman/execution/exec-spawn-future.test.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -37,9 +38,10 @@ namespace static_assert(test_std::async_scope_token>); static_assert(not test_std::async_scope_token>); + struct exception { int value; }; struct throws { throws() = default; - throws(throws&&) noexcept(false) = default; + throws(throws&&) noexcept(false) { throw exception{42}; } }; static_assert(!std::is_nothrow_constructible_v, throws>); @@ -53,7 +55,8 @@ namespace template struct state_base: test_detail::spawn_future_state_base { - auto complete() noexcept -> void override {} + bool called{false}; + auto complete() noexcept -> void override { this->called = true; } }; auto test_state_base() { static_assert(noexcept(std::declval>&>().complete())); @@ -81,6 +84,83 @@ namespace [[maybe_unused]] state4_t b4; static_assert(std::same_as, std::tuple>, typename state4_t::variant_t>); static_assert(std::same_as); + + using state5_t = state_base>; + [[maybe_unused]] state5_t b5; + static_assert(std::same_as>, typename state5_t::variant_t>); + static_assert(std::same_as); + } + + auto test_receiver() { + { + using c_t = test_std::completion_signatures; + state_base state{}; + ASSERT(state.called == false); + ASSERT(std::holds_alternative(state.result)); + static_assert(test_std::receiver>); + [[maybe_unused]] test_detail::spawn_future_receiver r0{&state}; + [](auto&& r){ static_assert(not requires{ r.set_stopped(); }); }(r0); + static_assert(noexcept(std::move(r0).set_stopped())); + std::move(r0).set_stopped(); + ASSERT(state.called == true); + ASSERT((std::holds_alternative>(state.result))); + } + + { + using c_t = test_std::completion_signatures; + state_base state{}; + ASSERT(state.called == false); + ASSERT(std::holds_alternative(state.result)); + static_assert(test_std::receiver>); + [[maybe_unused]] test_detail::spawn_future_receiver r0{&state}; + [](auto&& r){ static_assert(not requires{ r.set_error(17); }); }(r0); + static_assert(noexcept(std::move(r0).set_error(17))); + std::move(r0).set_error(17); + ASSERT(state.called == true); + ASSERT((std::holds_alternative>(state.result))); + ASSERT((std::get<1>(std::get>(state.result)) == 17)); + } + + { + using c_t = test_std::completion_signatures; + state_base state{}; + ASSERT(state.called == false); + ASSERT(std::holds_alternative(state.result)); + static_assert(test_std::receiver>); + [[maybe_unused]] test_detail::spawn_future_receiver r0{&state}; + [](auto&& r){ static_assert(not requires{ r.set_value(17, true, 'x'); }); }(r0); + static_assert(noexcept(std::move(r0).set_value(17, true, 'x'))); + std::move(r0).set_value(17, true, 'x'); + ASSERT(state.called == true); + ASSERT((std::holds_alternative>(state.result))); + ASSERT((std::get<1>(std::get>(state.result)) == 17)); + ASSERT((std::get<2>(std::get>(state.result)) == true)); + ASSERT((std::get<3>(std::get>(state.result)) == 'x')); + } + + { + using c_t = test_std::completion_signatures; + state_base state{}; + ASSERT(state.called == false); + ASSERT(std::holds_alternative(state.result)); + static_assert(test_std::receiver>); + [[maybe_unused]] test_detail::spawn_future_receiver r0{&state}; + [](auto&& r){ static_assert(not requires{ r.set_value(17, throws(), 'x'); }); }(r0); + static_assert(noexcept(std::move(r0).set_value(17, throws(), 'x'))); + std::move(r0).set_value(17, throws(), 'x'); + ASSERT(state.called == true); + ASSERT((std::holds_alternative>(state.result))); + try { + std::rethrow_exception(std::get<1>(std::get>(state.result))); + ASSERT(nullptr == "not reached"); + } + catch (exception const& ex) { + ASSERT(ex.value == 42); + } + catch (...) { + ASSERT(nullptr == "not reached"); + } + } } } @@ -92,4 +172,5 @@ TEST(exec_spawn_future) { test_spawn_future_interface(sender{}, token{}, *new non_env{}); test_state_base(); + test_receiver(); } From d5a84cfdad606f4715de042885f160c415059102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Tue, 3 Jun 2025 22:11:04 +0100 Subject: [PATCH 08/23] added exec.prop --- include/beman/execution/detail/prop.hpp | 28 +++++++++++++++++ src/beman/execution/CMakeLists.txt | 1 + tests/beman/execution/CMakeLists.txt | 1 + tests/beman/execution/exec-prop.test.cpp | 39 ++++++++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 include/beman/execution/detail/prop.hpp create mode 100644 tests/beman/execution/exec-prop.test.cpp diff --git a/include/beman/execution/detail/prop.hpp b/include/beman/execution/detail/prop.hpp new file mode 100644 index 00000000..a100c476 --- /dev/null +++ b/include/beman/execution/detail/prop.hpp @@ -0,0 +1,28 @@ +// include/beman/execution/detail/prop.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_PROP +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_PROP + +#include + +// ---------------------------------------------------------------------------- + +namespace beman::execution { + template + struct prop { + [[no_unique_address]] Query query_; + Value value_; + + auto operator=(prop const&) = delete; + + constexpr auto query(Query) const noexcept -> Value { return this->value_; } + }; + + template + prop(Query, Value) -> prop>; +} + +// ---------------------------------------------------------------------------- + +#endif diff --git a/src/beman/execution/CMakeLists.txt b/src/beman/execution/CMakeLists.txt index bc641671..41d2dc20 100644 --- a/src/beman/execution/CMakeLists.txt +++ b/src/beman/execution/CMakeLists.txt @@ -135,6 +135,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/operation_state.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/operation_state_task.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/product_type.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/prop.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/query_with_default.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/queryable.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/read_env.hpp diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index 8bce086d..77bbd077 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -11,6 +11,7 @@ endif() list( APPEND execution_tests + exec-prop.test exec-scope-simple-counting.test exec-spawn-future.test exec-scope-concepts.test diff --git a/tests/beman/execution/exec-prop.test.cpp b/tests/beman/execution/exec-prop.test.cpp new file mode 100644 index 00000000..f5336eeb --- /dev/null +++ b/tests/beman/execution/exec-prop.test.cpp @@ -0,0 +1,39 @@ +// tests/beman/execution/exec-prop.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace { + constexpr struct test_query_t { + template + requires requires(const test_query_t& self, const Env& e) { e.query(self); } + decltype(auto) operator()(const Env& e) const noexcept(noexcept(e.query(*this))) { + return e.query(*this); + } + constexpr auto query(const test_std::forwarding_query_t&) const noexcept -> bool { return true; } + } test_query{}; + + struct env { + auto query(test_query_t const&) const noexcept { return 42; } + }; + + template + auto test_prop(Env&& env, Value&& value) { + static_assert(requires{ { test_query(env) } noexcept; }); + ASSERT(test_query(env) == value); + } +} + +TEST(exec_prop) { + test_prop(env{}, 42); + test_prop(test_std::prop(test_query, 42), 42); + [[maybe_unused]] auto p{test_std::prop(test_query, 2.5)}; + static_assert(not std::is_assignable_v); +} + From 54e9cc9e38b047c72b49479079754e5fcbe8b9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Tue, 3 Jun 2025 23:56:55 +0100 Subject: [PATCH 09/23] added stop_when --- include/beman/execution/detail/as_tuple.hpp | 14 +- .../beman/execution/detail/counting_scope.hpp | 5 +- include/beman/execution/detail/prop.hpp | 20 +- include/beman/execution/detail/spawn.hpp | 5 +- .../beman/execution/detail/spawn_future.hpp | 126 ++++++----- include/beman/execution/detail/stop_when.hpp | 133 +++++++++++ include/beman/execution/execution.hpp | 1 + src/beman/execution/CMakeLists.txt | 1 + tests/beman/execution/CMakeLists.txt | 1 + tests/beman/execution/exec-prop.test.cpp | 23 +- .../exec-scope-simple-counting.test.cpp | 64 +++--- .../execution/exec-spawn-future.test.cpp | 212 ++++++++++-------- tests/beman/execution/exec-stop-when.test.cpp | 143 ++++++++++++ 13 files changed, 530 insertions(+), 218 deletions(-) create mode 100644 include/beman/execution/detail/stop_when.hpp create mode 100644 tests/beman/execution/exec-stop-when.test.cpp diff --git a/include/beman/execution/detail/as_tuple.hpp b/include/beman/execution/detail/as_tuple.hpp index a08abc40..1b9726c0 100644 --- a/include/beman/execution/detail/as_tuple.hpp +++ b/include/beman/execution/detail/as_tuple.hpp @@ -9,12 +9,16 @@ // ---------------------------------------------------------------------------- namespace beman::execution::detail { - template struct as_tuple; - template - struct as_tuple { using type = ::beman::execution::detail::decayed_tuple; }; +template +struct as_tuple; +template +struct as_tuple { + using type = ::beman::execution::detail::decayed_tuple; +}; - template using as_tuple_t = typename ::beman::execution::detail::as_tuple::type; -} +template +using as_tuple_t = typename ::beman::execution::detail::as_tuple::type; +} // namespace beman::execution::detail // ---------------------------------------------------------------------------- diff --git a/include/beman/execution/detail/counting_scope.hpp b/include/beman/execution/detail/counting_scope.hpp index 3a06f298..b32ff51c 100644 --- a/include/beman/execution/detail/counting_scope.hpp +++ b/include/beman/execution/detail/counting_scope.hpp @@ -9,9 +9,8 @@ // ---------------------------------------------------------------------------- namespace nstd { - namespace xxx { - } -} +namespace xxx {} +} // namespace nstd // ---------------------------------------------------------------------------- diff --git a/include/beman/execution/detail/prop.hpp b/include/beman/execution/detail/prop.hpp index a100c476..3568021f 100644 --- a/include/beman/execution/detail/prop.hpp +++ b/include/beman/execution/detail/prop.hpp @@ -9,19 +9,19 @@ // ---------------------------------------------------------------------------- namespace beman::execution { - template - struct prop { - [[no_unique_address]] Query query_; - Value value_; +template +struct prop { + [[no_unique_address]] Query query_; + Value value_; - auto operator=(prop const&) = delete; + auto operator=(const prop&) = delete; - constexpr auto query(Query) const noexcept -> Value { return this->value_; } - }; + constexpr auto query(Query) const noexcept -> Value { return this->value_; } +}; - template - prop(Query, Value) -> prop>; -} +template +prop(Query, Value) -> prop>; +} // namespace beman::execution // ---------------------------------------------------------------------------- diff --git a/include/beman/execution/detail/spawn.hpp b/include/beman/execution/detail/spawn.hpp index 873c1107..a00f7409 100644 --- a/include/beman/execution/detail/spawn.hpp +++ b/include/beman/execution/detail/spawn.hpp @@ -9,9 +9,8 @@ // ---------------------------------------------------------------------------- namespace nstd { - namespace xxx { - } -} +namespace xxx {} +} // namespace nstd // ---------------------------------------------------------------------------- diff --git a/include/beman/execution/detail/spawn_future.hpp b/include/beman/execution/detail/spawn_future.hpp index b01cca38..e1812bf8 100644 --- a/include/beman/execution/detail/spawn_future.hpp +++ b/include/beman/execution/detail/spawn_future.hpp @@ -23,79 +23,81 @@ // ---------------------------------------------------------------------------- namespace beman::execution::detail { - template struct non_throwing_args_copy; - template struct non_throwing_args_copy { - static constexpr bool value = (true && ... && ::std::is_nothrow_constructible_v<::std::decay_t, A> ); - }; - template inline constexpr bool non_throwing_args_copy_v{non_throwing_args_copy::value}; +template +struct non_throwing_args_copy; +template +struct non_throwing_args_copy { + static constexpr bool value = (true && ... && ::std::is_nothrow_constructible_v<::std::decay_t, A>); +}; +template +inline constexpr bool non_throwing_args_copy_v{non_throwing_args_copy::value}; - template struct spawn_future_state_base; - template - struct spawn_future_state_base<::beman::execution::completion_signatures> { - static constexpr bool has_non_throwing_args_copy = (true && ... && non_throwing_args_copy_v); - using variant_t = ::beman::execution::detail::meta::unique< - ::std::conditional_t< - has_non_throwing_args_copy, - ::std::variant<::std::monostate, ::beman::execution::detail::as_tuple_t...>, - ::std::variant<::std::monostate, ::std::tuple<::beman::execution::set_error_t, ::std::exception_ptr>, ::beman::execution::detail::as_tuple_t...> - > - >; +template +struct spawn_future_state_base; +template +struct spawn_future_state_base<::beman::execution::completion_signatures> { + static constexpr bool has_non_throwing_args_copy = (true && ... && non_throwing_args_copy_v); + using variant_t = ::beman::execution::detail::meta::unique< + ::std::conditional_t...>, + ::std::variant<::std::monostate, + ::std::tuple<::beman::execution::set_error_t, ::std::exception_ptr>, + ::beman::execution::detail::as_tuple_t...>>>; - variant_t result{}; - virtual ~spawn_future_state_base() = default; - virtual auto complete() noexcept -> void = 0; - }; + variant_t result{}; + virtual ~spawn_future_state_base() = default; + virtual auto complete() noexcept -> void = 0; +}; - template - struct spawn_future_receiver { - using receiver_concept = ::beman::execution::receiver_t; - using state_t = ::beman::execution::detail::spawn_future_state_base; +template +struct spawn_future_receiver { + using receiver_concept = ::beman::execution::receiver_t; + using state_t = ::beman::execution::detail::spawn_future_state_base; - state_t* state{}; + state_t* state{}; - template - auto set_value(A&&... a) && noexcept -> void { - this->set_complete<::beman::execution::set_value_t>(::std::forward(a)...); - } - template - auto set_error(E&& e) && noexcept -> void { - this->set_complete<::beman::execution::set_error_t>(::std::forward(e)); - } - auto set_stopped() && noexcept -> void { - this->set_complete<::beman::execution::set_stopped_t>(); - } + template + auto set_value(A&&... a) && noexcept -> void { + this->set_complete<::beman::execution::set_value_t>(::std::forward(a)...); + } + template + auto set_error(E&& e) && noexcept -> void { + this->set_complete<::beman::execution::set_error_t>(::std::forward(e)); + } + auto set_stopped() && noexcept -> void { this->set_complete<::beman::execution::set_stopped_t>(); } - template - auto set_complete(T&&... t) noexcept { - try { - this->state->result.template emplace<::beman::execution::detail::decayed_tuple>(Tag(), ::std::forward(t)...); + template + auto set_complete(T&&... t) noexcept { + try { + this->state->result.template emplace<::beman::execution::detail::decayed_tuple>( + Tag(), ::std::forward(t)...); + } catch (...) { + if constexpr (!state_t::has_non_throwing_args_copy) { + this->state->result + .template emplace<::std::tuple<::beman::execution::set_error_t, ::std::exception_ptr>>( + ::beman::execution::set_error_t{}, ::std::current_exception()); } - catch (...) { - if constexpr (!state_t::has_non_throwing_args_copy) { - this->state->result.template emplace<::std::tuple<::beman::execution::set_error_t, ::std::exception_ptr>>(::beman::execution::set_error_t{}, ::std::current_exception()); - } - } - this->state->complete(); } - }; + this->state->complete(); + } +}; - class spawn_future_t { - public: - template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok, typename Env> - requires ::beman::execution::detail::queryable<::std::remove_cvref_t> - auto operator()(Sndr&&, Tok&&, Env&&) const { - } - template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok> - auto operator()(Sndr&& sndr, Tok&& tok) const { - return (*this)(::std::forward(sndr), ::std::forward(tok), ::beman::execution::empty_env{}); - } - }; -} +class spawn_future_t { + public: + template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok, typename Env> + requires ::beman::execution::detail::queryable<::std::remove_cvref_t> + auto operator()(Sndr&&, Tok&&, Env&&) const {} + template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok> + auto operator()(Sndr&& sndr, Tok&& tok) const { + return (*this)(::std::forward(sndr), ::std::forward(tok), ::beman::execution::empty_env{}); + } +}; +} // namespace beman::execution::detail namespace beman::execution { - using spawn_future_t = ::beman::execution::detail::spawn_future_t; - inline constexpr spawn_future_t spawn_future{}; -} +using spawn_future_t = ::beman::execution::detail::spawn_future_t; +inline constexpr spawn_future_t spawn_future{}; +} // namespace beman::execution // ---------------------------------------------------------------------------- diff --git a/include/beman/execution/detail/stop_when.hpp b/include/beman/execution/detail/stop_when.hpp new file mode 100644 index 00000000..0ec7937d --- /dev/null +++ b/include/beman/execution/detail/stop_when.hpp @@ -0,0 +1,133 @@ +// include/beman/execution/detail/stop_when.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_STOP_WHEN +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_STOP_WHEN + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { +inline constexpr struct stop_when_t { + + template <::beman::execution::sender Sndr, ::beman::execution::stoppable_token Tok> + struct sender { + using sender_concept = ::beman::execution::sender_t; + + std::remove_cvref_t sndr; + std::remove_cvref_t tok; + + template <::beman::execution::receiver Rcvr> + struct state { + using operation_state_concept = ::beman::execution::operation_state_t; + using rcvr_t = ::std::remove_cvref_t; + using token1_t = ::std::remove_cvref_t; + using token2_t = + decltype(::beman::execution::get_stop_token(::beman::execution::get_env(::std::declval()))); + + struct cb_t { + ::beman::execution::inplace_stop_source& source; + auto operator()() const noexcept { this->source.request_stop(); } + }; + struct base_state { + rcvr_t rcvr; + ::beman::execution::inplace_stop_source source{}; + }; + struct env { + base_state* st; + auto query(const ::beman::execution::get_stop_token_t&) const noexcept { + return this->st->source.get_token(); + } + template + requires requires(const Q& q, A&&... a, const rcvr_t& r) { + q(::beman::execution::get_env(r), ::std::forward(a)...); + } + auto query(const Q& q, A&&... a) const noexcept { + return q(::beman::execution::get_env(this->st->rcvr), ::std::forward(a)...); + } + }; + + struct receiver { + using receiver_concept = ::beman::execution::receiver_t; + base_state* st; + + auto get_env() const noexcept -> env { return env{this->st}; } + template + auto set_value(A&&... a) const noexcept -> void { + ::beman::execution::set_value(::std::move(this->st->rcvr), ::std::forward(a)...); + } + template + auto set_error(E&& e) const noexcept -> void { + ::beman::execution::set_error(::std::move(this->st->rcvr), ::std::forward(e)); + } + auto set_stopped() const noexcept -> void { + ::beman::execution::set_stopped(::std::move(this->st->rcvr)); + } + }; + using inner_state_t = + decltype(::beman::execution::connect(::std::declval(), ::std::declval())); + + token1_t tok; + base_state base; + std::optional<::beman::execution::stop_callback_for_t> cb1; + std::optional<::beman::execution::stop_callback_for_t> cb2; + inner_state_t inner_state; + + template <::beman::execution::sender S, + ::beman::execution::stoppable_token T, + ::beman::execution::receiver R> + state(S&& s, T&& t, R&& r) + : tok(::std::forward(t)), + base{::std::forward(r)}, + inner_state(::beman::execution::connect(::std::forward(s), receiver(&this->base))) {} + + auto start() & noexcept { + this->cb1.emplace(this->tok, cb_t(this->base.source)); + this->cb2.emplace(::beman::execution::get_stop_token(::beman::execution::get_env(this->base.rcvr)), + cb_t(this->base.source)); + ::beman::execution::start(this->inner_state); + } + }; + + template + auto get_completion_signatures(const E& e) const noexcept { + return ::beman::execution::get_completion_signatures(this->sndr, e); + } + template <::beman::execution::receiver Rcvr> + auto connect(Rcvr&& rcvr) && -> state { + return state{std::move(this->sndr), ::std::move(this->tok), ::std::forward(rcvr)}; + } + }; + + template <::beman::execution::sender Sndr, ::beman::execution::stoppable_token Tok> + auto operator()(Sndr&& sndr, Tok&& tok) const noexcept { + if constexpr (::beman::execution::unstoppable_token) { + return ::std::forward(sndr); + } else { + return sender(::std::forward(sndr), ::std::forward(tok)); + } + } +} stop_when{}; +} // namespace beman::execution::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/execution.hpp b/include/beman/execution/execution.hpp index 5a203e14..b87c5ae0 100644 --- a/include/beman/execution/execution.hpp +++ b/include/beman/execution/execution.hpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include diff --git a/src/beman/execution/CMakeLists.txt b/src/beman/execution/CMakeLists.txt index 41d2dc20..0b996c0b 100644 --- a/src/beman/execution/CMakeLists.txt +++ b/src/beman/execution/CMakeLists.txt @@ -173,6 +173,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/stop_callback_for_t.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/stop_source.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/stop_token_of_t.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/stop_when.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/stoppable_source.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/stoppable_token.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/suppress_pop.hpp diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index 77bbd077..047f0937 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -11,6 +11,7 @@ endif() list( APPEND execution_tests + exec-stop-when.test exec-prop.test exec-scope-simple-counting.test exec-spawn-future.test diff --git a/tests/beman/execution/exec-prop.test.cpp b/tests/beman/execution/exec-prop.test.cpp index f5336eeb..7adbbff3 100644 --- a/tests/beman/execution/exec-prop.test.cpp +++ b/tests/beman/execution/exec-prop.test.cpp @@ -10,25 +10,27 @@ // ---------------------------------------------------------------------------- namespace { - constexpr struct test_query_t { +constexpr struct test_query_t { template requires requires(const test_query_t& self, const Env& e) { e.query(self); } decltype(auto) operator()(const Env& e) const noexcept(noexcept(e.query(*this))) { return e.query(*this); } constexpr auto query(const test_std::forwarding_query_t&) const noexcept -> bool { return true; } - } test_query{}; +} test_query{}; - struct env { - auto query(test_query_t const&) const noexcept { return 42; } - }; +struct env { + auto query(const test_query_t&) const noexcept { return 42; } +}; - template - auto test_prop(Env&& env, Value&& value) { - static_assert(requires{ { test_query(env) } noexcept; }); - ASSERT(test_query(env) == value); - } +template +auto test_prop(Env&& env, Value&& value) { + static_assert(requires { + { test_query(env) } noexcept; + }); + ASSERT(test_query(env) == value); } +} // namespace TEST(exec_prop) { test_prop(env{}, 42); @@ -36,4 +38,3 @@ TEST(exec_prop) { [[maybe_unused]] auto p{test_std::prop(test_query, 2.5)}; static_assert(not std::is_assignable_v); } - diff --git a/tests/beman/execution/exec-scope-simple-counting.test.cpp b/tests/beman/execution/exec-scope-simple-counting.test.cpp index 0d41d250..bd505c8f 100644 --- a/tests/beman/execution/exec-scope-simple-counting.test.cpp +++ b/tests/beman/execution/exec-scope-simple-counting.test.cpp @@ -18,54 +18,62 @@ auto general() -> void { using scope = test_std::simple_counting_scope; using token = scope::token; - //static_assert(requires(token const& tok){ { tok.wrap(test_std::just(10)) } noexcept; }); - static_assert(requires(token const& tok){ { tok.try_associate() } noexcept -> std::same_as; }); - static_assert(requires(token const& tok){ tok.try_associate(); }); - static_assert(requires(token const& tok){ { tok.disassociate() } noexcept; }); + // static_assert(requires(token const& tok){ { tok.wrap(test_std::just(10)) } noexcept; }); + static_assert(requires(const token& tok) { + { tok.try_associate() } noexcept -> std::same_as; + }); + static_assert(requires(const token& tok) { tok.try_associate(); }); + static_assert(requires(const token& tok) { + { tok.disassociate() } noexcept; + }); static_assert(noexcept(scope{})); - static_assert(! std::is_move_constructible_v); - static_assert(! std::is_copy_constructible_v); - static_assert(requires(scope sc){ { sc.get_token() } noexcept -> std::same_as; }); - static_assert(requires(scope sc){ { sc.close() } noexcept -> std::same_as; }); - static_assert(requires(scope sc){ { sc.join() } noexcept; }); + static_assert(!std::is_move_constructible_v); + static_assert(!std::is_copy_constructible_v); + static_assert(requires(scope sc) { + { sc.get_token() } noexcept -> std::same_as; + }); + static_assert(requires(scope sc) { + { sc.close() } noexcept -> std::same_as; + }); + static_assert(requires(scope sc) { + { sc.join() } noexcept; + }); } struct join_receiver { using receiver_concept = test_std::receiver_t; struct env { - auto query(test_std::get_scheduler_t const&) const noexcept -> test::inline_scheduler { - return {}; - } + auto query(const test_std::get_scheduler_t&) const noexcept -> test::inline_scheduler { return {}; } }; bool& called; - auto set_value() && noexcept { this->called = true; } - auto get_env() const noexcept -> env { return {}; } + auto set_value() && noexcept { this->called = true; } + auto get_env() const noexcept -> env { return {}; } }; auto ctor() -> void { { test_std::simple_counting_scope scope; } - test::death([]{ + test::death([] { test_std::simple_counting_scope scope; scope.get_token().try_associate(); }); - test::death([]{ + test::death([] { test_std::simple_counting_scope scope; scope.get_token().try_associate(); bool called{false}; auto state(test_std::connect(scope.join(), join_receiver{called})); test_std::start(state); }); - test::death([]{ + test::death([] { test_std::simple_counting_scope scope; scope.get_token().try_associate(); scope.close(); }); - test::death([]{ + test::death([] { test_std::simple_counting_scope scope; scope.get_token().try_associate(); bool called{false}; @@ -86,15 +94,15 @@ auto ctor() -> void { auto mem() -> void { { - test_std::simple_counting_scope scope; - test_std::simple_counting_scope::token token{scope.get_token()}; + test_std::simple_counting_scope scope; + test_std::simple_counting_scope::token token{scope.get_token()}; - ASSERT(true == token.try_associate()); - token.disassociate(); - scope.close(); - ASSERT(false == token.try_associate()); + ASSERT(true == token.try_associate()); + token.disassociate(); + scope.close(); + ASSERT(false == token.try_associate()); - test_std::sync_wait(scope.join()); + test_std::sync_wait(scope.join()); } { test_std::simple_counting_scope scope; @@ -112,8 +120,8 @@ auto mem() -> void { } auto token() -> void { test_std::simple_counting_scope scope; - auto const tok{scope.get_token()}; - auto sndr{tok.wrap(test_std::just(10))}; + const auto tok{scope.get_token()}; + auto sndr{tok.wrap(test_std::just(10))}; static_assert(std::same_as); ASSERT(true == tok.try_associate()); @@ -129,7 +137,7 @@ auto token() -> void { ASSERT(true == called); } -} +} // namespace TEST(exec_scope_simple_counting) { general(); diff --git a/tests/beman/execution/exec-spawn-future.test.cpp b/tests/beman/execution/exec-spawn-future.test.cpp index 42328a47..3c069862 100644 --- a/tests/beman/execution/exec-spawn-future.test.cpp +++ b/tests/beman/execution/exec-spawn-future.test.cpp @@ -11,124 +11,145 @@ // ---------------------------------------------------------------------------- -namespace -{ - struct non_env { ~non_env() = delete; }; - static_assert(not test_detail::queryable); - - struct env {}; - static_assert(test_detail::queryable); - - struct non_sender { - }; - static_assert(not test_std::sender); - - struct sender { - using sender_concept = test_std::sender_t; - }; - static_assert(test_std::sender); - - template - struct token { - auto try_associate() -> bool { return {}; } - auto disassociate() noexcept(Noexcept) -> void {} - template - auto wrap(Sender&& sender) -> Sender { return std::forward(sender); } - }; - static_assert(test_std::async_scope_token>); - static_assert(not test_std::async_scope_token>); - - struct exception { int value; }; - struct throws { - throws() = default; - throws(throws&&) noexcept(false) { throw exception{42}; } - }; - static_assert(!std::is_nothrow_constructible_v, throws>); - - template - auto test_spawn_future_interface(Sender&& sndr, Token&& tok, Env&& e = Env{}) -> void { - if constexpr (!std::same_as, non_env>){ - static_assert(Expect == requires{ test_std::spawn_future(std::forward(sndr), std::forward(tok)); }); - } - static_assert(Expect == requires{ test_std::spawn_future(std::forward(sndr), std::forward(tok), std::forward(e)); }); +namespace { +struct non_env { + ~non_env() = delete; +}; +static_assert(not test_detail::queryable); + +struct env {}; +static_assert(test_detail::queryable); + +struct non_sender {}; +static_assert(not test_std::sender); + +struct sender { + using sender_concept = test_std::sender_t; +}; +static_assert(test_std::sender); + +template +struct token { + auto try_associate() -> bool { return {}; } + auto disassociate() noexcept(Noexcept) -> void {} + template + auto wrap(Sender&& sender) -> Sender { + return std::forward(sender); } - - template - struct state_base: test_detail::spawn_future_state_base { - bool called{false}; - auto complete() noexcept -> void override { this->called = true; } - }; - auto test_state_base() { - static_assert(noexcept(std::declval>&>().complete())); - using state0_t = state_base>; - [[maybe_unused]] state0_t b0; - static_assert(std::same_as, state0_t::variant_t>); - static_assert(std::same_as); - - using state1_t = state_base>; - [[maybe_unused]] state1_t b1; - static_assert(std::same_as>, state1_t::variant_t>); - static_assert(std::same_as); - - using state2_t = state_base>; - [[maybe_unused]] state2_t b2; - static_assert(std::same_as, std::tuple>, typename state2_t::variant_t>); - static_assert(std::same_as); - - using state3_t = state_base>; - [[maybe_unused]] state3_t b3; - static_assert(std::same_as, std::tuple>, typename state3_t::variant_t>); - static_assert(std::same_as); - - using state4_t = state_base>; - [[maybe_unused]] state4_t b4; - static_assert(std::same_as, std::tuple>, typename state4_t::variant_t>); - static_assert(std::same_as); - - using state5_t = state_base>; - [[maybe_unused]] state5_t b5; - static_assert(std::same_as>, typename state5_t::variant_t>); - static_assert(std::same_as); +}; +static_assert(test_std::async_scope_token>); +static_assert(not test_std::async_scope_token>); + +struct exception { + int value; +}; +struct throws { + throws() = default; + throws(throws&&) noexcept(false) { throw exception{42}; } +}; +static_assert(!std::is_nothrow_constructible_v, throws>); + +template +auto test_spawn_future_interface(Sender&& sndr, Token&& tok, Env&& e = Env{}) -> void { + if constexpr (!std::same_as, non_env>) { + static_assert(Expect == + requires { test_std::spawn_future(std::forward(sndr), std::forward(tok)); }); } + static_assert(Expect == requires { + test_std::spawn_future(std::forward(sndr), std::forward(tok), std::forward(e)); + }); +} - auto test_receiver() { - { +template +struct state_base : test_detail::spawn_future_state_base { + bool called{false}; + auto complete() noexcept -> void override { this->called = true; } +}; +auto test_state_base() { + static_assert( + noexcept(std::declval>&>().complete())); + using state0_t = state_base>; + [[maybe_unused]] state0_t b0; + static_assert(std::same_as, state0_t::variant_t>); + static_assert(std::same_as); + + using state1_t = state_base>; + [[maybe_unused]] state1_t b1; + static_assert( + std::same_as>, state1_t::variant_t>); + static_assert(std::same_as); + + using state2_t = state_base>; + [[maybe_unused]] state2_t b2; + static_assert(std::same_as, + std::tuple>, + typename state2_t::variant_t>); + static_assert(std::same_as); + + using state3_t = state_base< + test_std::completion_signatures>; + [[maybe_unused]] state3_t b3; + static_assert(std::same_as, + std::tuple>, + typename state3_t::variant_t>); + static_assert(std::same_as); + + using state4_t = state_base< + test_std::completion_signatures>; + [[maybe_unused]] state4_t b4; + static_assert(std::same_as, + std::tuple>, + typename state4_t::variant_t>); + static_assert(std::same_as); + + using state5_t = state_base>; + [[maybe_unused]] state5_t b5; + static_assert( + std::same_as>, typename state5_t::variant_t>); + static_assert(std::same_as); +} + +auto test_receiver() { + { using c_t = test_std::completion_signatures; state_base state{}; ASSERT(state.called == false); ASSERT(std::holds_alternative(state.result)); static_assert(test_std::receiver>); [[maybe_unused]] test_detail::spawn_future_receiver r0{&state}; - [](auto&& r){ static_assert(not requires{ r.set_stopped(); }); }(r0); + [](auto&& r) { static_assert(not requires { r.set_stopped(); }); }(r0); static_assert(noexcept(std::move(r0).set_stopped())); std::move(r0).set_stopped(); ASSERT(state.called == true); ASSERT((std::holds_alternative>(state.result))); - } + } - { + { using c_t = test_std::completion_signatures; state_base state{}; ASSERT(state.called == false); ASSERT(std::holds_alternative(state.result)); static_assert(test_std::receiver>); [[maybe_unused]] test_detail::spawn_future_receiver r0{&state}; - [](auto&& r){ static_assert(not requires{ r.set_error(17); }); }(r0); + [](auto&& r) { static_assert(not requires { r.set_error(17); }); }(r0); static_assert(noexcept(std::move(r0).set_error(17))); std::move(r0).set_error(17); ASSERT(state.called == true); ASSERT((std::holds_alternative>(state.result))); ASSERT((std::get<1>(std::get>(state.result)) == 17)); - } + } - { + { using c_t = test_std::completion_signatures; state_base state{}; ASSERT(state.called == false); ASSERT(std::holds_alternative(state.result)); static_assert(test_std::receiver>); [[maybe_unused]] test_detail::spawn_future_receiver r0{&state}; - [](auto&& r){ static_assert(not requires{ r.set_value(17, true, 'x'); }); }(r0); + [](auto&& r) { static_assert(not requires { r.set_value(17, true, 'x'); }); }(r0); static_assert(noexcept(std::move(r0).set_value(17, true, 'x'))); std::move(r0).set_value(17, true, 'x'); ASSERT(state.called == true); @@ -136,36 +157,35 @@ namespace ASSERT((std::get<1>(std::get>(state.result)) == 17)); ASSERT((std::get<2>(std::get>(state.result)) == true)); ASSERT((std::get<3>(std::get>(state.result)) == 'x')); - } + } - { + { using c_t = test_std::completion_signatures; state_base state{}; ASSERT(state.called == false); ASSERT(std::holds_alternative(state.result)); static_assert(test_std::receiver>); [[maybe_unused]] test_detail::spawn_future_receiver r0{&state}; - [](auto&& r){ static_assert(not requires{ r.set_value(17, throws(), 'x'); }); }(r0); + [](auto&& r) { static_assert(not requires { r.set_value(17, throws(), 'x'); }); }(r0); static_assert(noexcept(std::move(r0).set_value(17, throws(), 'x'))); std::move(r0).set_value(17, throws(), 'x'); ASSERT(state.called == true); ASSERT((std::holds_alternative>(state.result))); try { - std::rethrow_exception(std::get<1>(std::get>(state.result))); + std::rethrow_exception( + std::get<1>(std::get>(state.result))); ASSERT(nullptr == "not reached"); - } - catch (exception const& ex) { + } catch (const exception& ex) { ASSERT(ex.value == 42); - } - catch (...) { + } catch (...) { ASSERT(nullptr == "not reached"); } - } } } +} // namespace TEST(exec_spawn_future) { - static_assert(std::same_as); + static_assert(std::same_as); test_spawn_future_interface(sender{}, token{}); test_spawn_future_interface(non_sender{}, token{}); test_spawn_future_interface(sender{}, token{}); diff --git a/tests/beman/execution/exec-stop-when.test.cpp b/tests/beman/execution/exec-stop-when.test.cpp new file mode 100644 index 00000000..9708e0d5 --- /dev/null +++ b/tests/beman/execution/exec-stop-when.test.cpp @@ -0,0 +1,143 @@ +// tests/beman/execution/exec-stop-when.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace { +struct sender { + using sender_concept = test_std::sender_t; + using completion_signatures = test_std::completion_signatures; + + template + struct state { + using operation_state_concept = test_std::operation_state_t; + using rcvr_t = std::remove_cvref_t; + using token_t = decltype(test_std::get_stop_token(test_std::get_env(std::declval()))); + + struct cb_t { + rcvr_t& rcvr; + auto operator()() noexcept { test_std::set_stopped(std::move(this->rcvr)); } + }; + using stop_cb_t = test_std::stop_callback_for_t; + + rcvr_t rcvr; + std::optional stop_cb; + + template + state(R&& r) : rcvr(r) {} + + auto start() & noexcept { + this->stop_cb.emplace(test_std::get_stop_token(test_std::get_env(this->rcvr)), cb_t{this->rcvr}); + } + }; + + template + auto connect(Rcvr&& rcvr) -> state { + return state(std::forward(rcvr)); + } +}; +static_assert(test_std::sender); + +struct env { + test_std::inplace_stop_token token; + auto query(const test_std::get_stop_token_t&) const noexcept { return this->token; } +}; +enum class completion : char { none, value, stopped }; + +struct receiver { + using receiver_concept = test_std::receiver_t; + + test_std::inplace_stop_token token; + completion& comp; + + auto set_value() && noexcept { this->comp = completion::value; } + auto set_stopped() && noexcept { this->comp = completion::stopped; } + + auto get_env() const noexcept { return env{this->token}; } +}; +static_assert(test_std::receiver); +} // namespace + +TEST(exec_stop_when) { + static_assert(std::same_as); + static_assert(requires(const env& e) { + { test_std::get_stop_token(e) } -> std::same_as; + }); + static_assert(requires(const receiver& r) { + { test_std::get_stop_token(test_std::get_env(r)) } -> std::same_as; + }); + + { + test_std::inplace_stop_source source; + completion comp{completion::none}; + auto state{test_std::connect(sender{}, receiver{source.get_token(), comp})}; + ASSERT(comp == completion::none); + test_std::start(state); + ASSERT(comp == completion::none); + + source.request_stop(); + ASSERT(comp == completion::stopped); + } + { + auto sndr{test_detail::stop_when(sender{}, test_std::never_stop_token{})}; + static_assert(std::same_as); + } + { + test_std::inplace_stop_source source; + completion comp{completion::none}; + auto state{test_std::connect(test_detail::stop_when(sender{}, test_std::never_stop_token{}), + receiver{source.get_token(), comp})}; + ASSERT(comp == completion::none); + test_std::start(state); + ASSERT(comp == completion::none); + + source.request_stop(); + ASSERT(comp == completion::stopped); + } + { + test_std::inplace_stop_source source1; + test_std::inplace_stop_source source2; + completion comp{completion::none}; + auto state{test_std::connect(test_detail::stop_when(sender{}, source1.get_token()), + receiver{source2.get_token(), comp})}; + ASSERT(comp == completion::none); + test_std::start(state); + ASSERT(comp == completion::none); + + source1.request_stop(); + ASSERT(comp == completion::stopped); + } + { + test_std::inplace_stop_source source1; + test_std::inplace_stop_source source2; + completion comp{completion::none}; + auto state{test_std::connect(test_detail::stop_when(sender{}, source1.get_token()), + receiver{source2.get_token(), comp})}; + ASSERT(comp == completion::none); + test_std::start(state); + ASSERT(comp == completion::none); + + source2.request_stop(); + ASSERT(comp == completion::stopped); + } +} From 58bd68c1186f82cc28a4bf3a7480b0bf814fed3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Wed, 4 Jun 2025 19:08:23 +0100 Subject: [PATCH 10/23] fix test issues with Linux and Windows --- tests/beman/execution/exec-spawn-future.test.cpp | 11 ++++++----- tests/beman/execution/include/test/execution.hpp | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/beman/execution/exec-spawn-future.test.cpp b/tests/beman/execution/exec-spawn-future.test.cpp index 3c069862..d570d3f1 100644 --- a/tests/beman/execution/exec-spawn-future.test.cpp +++ b/tests/beman/execution/exec-spawn-future.test.cpp @@ -49,14 +49,14 @@ struct throws { }; static_assert(!std::is_nothrow_constructible_v, throws>); -template -auto test_spawn_future_interface(Sender&& sndr, Token&& tok, Env&& e = Env{}) -> void { +template +auto test_spawn_future_interface(Sender&& sndr, Token&& tok) -> void { if constexpr (!std::same_as, non_env>) { static_assert(Expect == requires { test_std::spawn_future(std::forward(sndr), std::forward(tok)); }); } - static_assert(Expect == requires { - test_std::spawn_future(std::forward(sndr), std::forward(tok), std::forward(e)); + static_assert(Expect == requires(Env const& e){ + test_std::spawn_future(std::forward(sndr), std::forward(tok), e); }); } @@ -189,7 +189,8 @@ TEST(exec_spawn_future) { test_spawn_future_interface(sender{}, token{}); test_spawn_future_interface(non_sender{}, token{}); test_spawn_future_interface(sender{}, token{}); - test_spawn_future_interface(sender{}, token{}, *new non_env{}); + + test_spawn_future_interface(sender{}, token{}); test_state_base(); test_receiver(); diff --git a/tests/beman/execution/include/test/execution.hpp b/tests/beman/execution/include/test/execution.hpp index a68459ac..382bc8a0 100644 --- a/tests/beman/execution/include/test/execution.hpp +++ b/tests/beman/execution/include/test/execution.hpp @@ -10,9 +10,10 @@ #ifndef _MSC_VER #include #include +#include #include -#include #endif +#include #undef NDEBUG #include @@ -27,6 +28,16 @@ namespace test_std = ::beman::execution; namespace test_detail = ::beman::execution::detail; namespace test { +#if 201907L <= __cpp_lib_source_location +using source_location = ::std::source_location; +#else +struct source_location { + static auto current() -> source_location { return {}; } + auto file_name() const -> char const* { return ""; } + auto line() const -> char const* { return ""; } +}; +#endif + inline bool unreachable_helper() { return false; } template @@ -48,7 +59,7 @@ struct throws { }; inline auto death([[maybe_unused]] auto fun, - [[maybe_unused]] ::std::source_location location = std::source_location::current()) noexcept + [[maybe_unused]] ::std::source_location location = test::source_location::current()) noexcept -> void { #ifndef _MSC_VER switch (::pid_t rc = ::fork()) { From 3d2595ee2aa093d5f58b7ce64ffe41607677405a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Thu, 5 Jun 2025 00:42:03 +0100 Subject: [PATCH 11/23] some progress on implementing spawn_future --- include/beman/execution/detail/prop.hpp | 7 +- .../detail/simple_counting_scope.hpp | 6 +- .../beman/execution/detail/spawn_future.hpp | 97 ++++++++++++++++++- include/beman/execution/detail/write_env.hpp | 5 + .../execution/exec-spawn-future.test.cpp | 94 +++++++++++++++++- .../execution/include/test/execution.hpp | 4 +- 6 files changed, 201 insertions(+), 12 deletions(-) diff --git a/include/beman/execution/detail/prop.hpp b/include/beman/execution/detail/prop.hpp index 3568021f..1b2cdf23 100644 --- a/include/beman/execution/detail/prop.hpp +++ b/include/beman/execution/detail/prop.hpp @@ -11,9 +11,14 @@ namespace beman::execution { template struct prop { - [[no_unique_address]] Query query_; + [[no_unique_address]] Query query_{}; Value value_; + template + prop(Q q, V&& v) : query_(q), value_(v) {} + prop(prop&&) = default; + prop(const prop&) = default; + auto operator=(prop&&) = delete; auto operator=(const prop&) = delete; constexpr auto query(Query) const noexcept -> Value { return this->value_; } diff --git a/include/beman/execution/detail/simple_counting_scope.hpp b/include/beman/execution/detail/simple_counting_scope.hpp index 5f593874..1b1d6d96 100644 --- a/include/beman/execution/detail/simple_counting_scope.hpp +++ b/include/beman/execution/detail/simple_counting_scope.hpp @@ -165,9 +165,9 @@ class beman::execution::simple_counting_scope : ::beman::execution::detail::immo ::std::exchange(current, current->next)->complete(); } } - ::std::mutex mutex; - ::std::size_t count{}; - state_t state{state_t::unused}; + ::std::mutex mutex; + ::std::size_t count{}; + state_t state{state_t::unused}; ::beman::execution::detail::simple_counting_scope_state_base* head{}; }; diff --git a/include/beman/execution/detail/spawn_future.hpp b/include/beman/execution/detail/spawn_future.hpp index e1812bf8..aa1060df 100644 --- a/include/beman/execution/detail/spawn_future.hpp +++ b/include/beman/execution/detail/spawn_future.hpp @@ -6,14 +6,25 @@ #include #include +#include +#include +#include +#include #include #include #include #include #include +#include +#include #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -82,11 +93,91 @@ struct spawn_future_receiver { } }; +template <::beman::execution::sender Sndr, typename Env> +using future_spawned_sender = decltype(::beman::execution::write_env( + ::beman::execution::detail::stop_when(::std::declval(), + ::std::declval<::beman::execution::inplace_stop_token>()), + ::std::declval())); + +template +struct spawn_future_state + : ::beman::execution::detail::spawn_future_state_base<::beman::execution::completion_signatures_of_t< + ::beman::execution::detail::future_spawned_sender>> { + using alloc_t = typename ::std::allocator_traits::template rebind_alloc; + using traits_t = ::std::allocator_traits; + + spawn_future_state(auto a, auto&&, Token tok, auto&&...) : token(tok), alloc(a) { /*-dk:TODO*/ } + auto complete() noexcept -> void override { /*-dk:TODO*/ } + auto abandon() noexcept -> void { + /*-dk:TODO*/ + this->destroy(); + } + auto destroy() noexcept -> void { + Token tok{this->token}; + bool assoc{this->associated}; + { + alloc_t a{this->alloc}; + traits_t::destroy(a, this); + traits_t::deallocate(a, this, 1u); + } + if (assoc) { + tok.disassociate(); + } + } + + ::std::mutex cerberos{}; + Token token; + bool associated{false}; + alloc_t alloc; +}; + +template <::beman::execution::sender Sndr, typename Ev> +auto spawn_future_get_allocator(const Sndr& sndr, const Ev& ev) { + if constexpr (requires { ::beman::execution::get_allocator(ev); }) { + return ::std::pair(::beman::execution::get_allocator(ev), ev); + } else if constexpr (requires { ::beman::execution::get_allocator(::beman::execution::get_env(sndr)); }) { + auto alloc{::beman::execution::get_allocator(::beman::execution::get_env(sndr))}; + return ::std::pair(alloc, + ::beman::execution::detail::join_env( + ::beman::execution::prop(::beman::execution::get_allocator, alloc), ev)); + } else { + return ::std::pair(::std::allocator{}, ev); + } +} + class spawn_future_t { public: - template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok, typename Env> - requires ::beman::execution::detail::queryable<::std::remove_cvref_t> - auto operator()(Sndr&&, Tok&&, Env&&) const {} + template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok, typename Ev> + requires ::beman::execution::detail::queryable<::std::remove_cvref_t> + auto operator()(Sndr&& sndr, Tok&& tok, Ev&& ev) const { + auto make{[&] -> decltype(auto) { //-dk:TODO while decltype(auto) instead of auto? + return tok.wrap(::std::forward(sndr)); + }}; + using sndr_t = decltype(make()); + static_assert(::beman::execution::sender); + + auto [alloc, senv] = spawn_future_get_allocator(sndr, ev); + using state_t = ::beman::execution::detail::spawn_future_state; + using state_alloc_t = typename ::std::allocator_traits::template rebind_alloc; + using state_traits_t = ::std::allocator_traits; + state_alloc_t state_alloc(alloc); + using deleter = decltype([](state_t* p) noexcept { + if (p) + p->abandon(); + }); + state_t* op{state_traits_t::allocate(state_alloc, 1u)}; + try { + state_traits_t::construct(state_alloc, op, alloc, make(), tok, senv); + } catch (...) { + state_traits_t::deallocate(state_alloc, op, 1u); + throw; + } + + return ::beman::execution::detail::make_sender(*this, ::std::unique_ptr{op}); + } template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok> auto operator()(Sndr&& sndr, Tok&& tok) const { return (*this)(::std::forward(sndr), ::std::forward(tok), ::beman::execution::empty_env{}); diff --git a/include/beman/execution/detail/write_env.hpp b/include/beman/execution/detail/write_env.hpp index fe670eef..6da548a4 100644 --- a/include/beman/execution/detail/write_env.hpp +++ b/include/beman/execution/detail/write_env.hpp @@ -45,6 +45,11 @@ struct impls_for : ::beman::execution::detail::default_impls { inline constexpr write_env_t write_env{}; } // namespace beman::execution::detail +namespace beman::execution { +using write_env_t = ::beman::execution::detail::write_env_t; +inline constexpr write_env_t write_env{}; +} // namespace beman::execution + // ---------------------------------------------------------------------------- #endif diff --git a/tests/beman/execution/exec-spawn-future.test.cpp b/tests/beman/execution/exec-spawn-future.test.cpp index d570d3f1..26150b8c 100644 --- a/tests/beman/execution/exec-spawn-future.test.cpp +++ b/tests/beman/execution/exec-spawn-future.test.cpp @@ -6,6 +6,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include @@ -17,14 +22,18 @@ struct non_env { }; static_assert(not test_detail::queryable); -struct env {}; +struct env { + int value{}; + auto operator==(const env&) const -> bool = default; +}; static_assert(test_detail::queryable); struct non_sender {}; static_assert(not test_std::sender); struct sender { - using sender_concept = test_std::sender_t; + using sender_concept = test_std::sender_t; + using completion_signatures = test_std::completion_signatures; }; static_assert(test_std::sender); @@ -55,7 +64,7 @@ auto test_spawn_future_interface(Sender&& sndr, Token&& tok) -> void { static_assert(Expect == requires { test_std::spawn_future(std::forward(sndr), std::forward(tok)); }); } - static_assert(Expect == requires(Env const& e){ + static_assert(Expect == requires(const Env& e) { test_std::spawn_future(std::forward(sndr), std::forward(tok), e); }); } @@ -182,6 +191,81 @@ auto test_receiver() { } } } + +template +auto test_spawn_future(Sndr&& sndr, Tok&& tok, Ev&& ev) { + test_std::spawn_future(std::forward(sndr), std::forward(tok), std::forward(ev)); +} + +struct allocator { + using value_type = int; + int value{}; + + auto allocate(std::size_t n) -> int* { return new int[n]; } + auto deallocate(int* p, std::size_t) -> void { delete[] p; } + + auto operator==(const allocator&) const -> bool = default; +}; +static_assert(test_detail::simple_allocator); + +struct alloc_env { + int value{}; + auto operator==(const alloc_env&) const -> bool = default; + + auto query(const test_std::get_allocator_t&) const noexcept -> allocator { return allocator{this->value}; } +}; + +struct alloc_sender { + using sender_concept = test_std::sender_t; + int value{}; + + auto get_env() const noexcept -> alloc_env { return alloc_env{this->value}; } +}; +static_assert(test_std::sender); + +auto test_get_allocator() { + { + alloc_env ae{87}; + auto [alloc, ev] = test_detail::spawn_future_get_allocator(sender{}, ae); + static_assert(std::same_as); + ASSERT(alloc == allocator{87}); + static_assert(std::same_as); + ASSERT(ev == alloc_env{87}); + } + { + auto [alloc, ev] = test_detail::spawn_future_get_allocator(alloc_sender{53}, env{42}); + static_assert(std::same_as); + ASSERT(alloc == allocator{53}); + ASSERT(test_std::get_allocator(ev) == allocator{53}); + } + { + test_std::inplace_stop_source source; + auto [alloc, ev] = test_detail::spawn_future_get_allocator( + alloc_sender{53}, test_std::prop(test_std::get_stop_token, source.get_token())); + static_assert(std::same_as); + ASSERT(alloc == allocator{53}); + ASSERT(test_std::get_allocator(ev) == allocator{53}); + ASSERT(test_std::get_stop_token(ev) == source.get_token()); + } + { + test_std::inplace_stop_source source; + auto [alloc, ev] = test_detail::spawn_future_get_allocator( + alloc_sender{53}, + test_detail::join_env(test_std::prop(test_std::get_allocator, allocator(101)), + test_std::prop(test_std::get_stop_token, source.get_token()))); + static_assert(std::same_as); + ASSERT(alloc == allocator{101}); + ASSERT(test_std::get_allocator(ev) == allocator{101}); + ASSERT(test_std::get_stop_token(ev) == source.get_token()); + } + { + auto [alloc, ev] = test_detail::spawn_future_get_allocator(sender{}, env{42}); + static_assert(std::same_as>); + static_assert(std::same_as); + ASSERT(ev == env{42}); + } +} + } // namespace TEST(exec_spawn_future) { @@ -194,4 +278,8 @@ TEST(exec_spawn_future) { test_state_base(); test_receiver(); + + test_get_allocator(); + + test_spawn_future(sender{}, token{}, env{}); } diff --git a/tests/beman/execution/include/test/execution.hpp b/tests/beman/execution/include/test/execution.hpp index 382bc8a0..1a5cbb7e 100644 --- a/tests/beman/execution/include/test/execution.hpp +++ b/tests/beman/execution/include/test/execution.hpp @@ -33,8 +33,8 @@ using source_location = ::std::source_location; #else struct source_location { static auto current() -> source_location { return {}; } - auto file_name() const -> char const* { return ""; } - auto line() const -> char const* { return ""; } + auto file_name() const -> const char* { return ""; } + auto line() const -> const char* { return ""; } }; #endif From 909b6a38b5da7fcff00ce5c3dfd18a04f9433b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Thu, 5 Jun 2025 07:48:57 +0100 Subject: [PATCH 12/23] fix an issue with lambda upsetting MSVC++ --- include/beman/execution/detail/spawn_future.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/beman/execution/detail/spawn_future.hpp b/include/beman/execution/detail/spawn_future.hpp index aa1060df..72035168 100644 --- a/include/beman/execution/detail/spawn_future.hpp +++ b/include/beman/execution/detail/spawn_future.hpp @@ -153,7 +153,7 @@ class spawn_future_t { template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok, typename Ev> requires ::beman::execution::detail::queryable<::std::remove_cvref_t> auto operator()(Sndr&& sndr, Tok&& tok, Ev&& ev) const { - auto make{[&] -> decltype(auto) { //-dk:TODO while decltype(auto) instead of auto? + auto make{[&]() -> decltype(auto) { //-dk:TODO while decltype(auto) instead of auto? return tok.wrap(::std::forward(sndr)); }}; using sndr_t = decltype(make()); From 9b8d0dc9941d42a670f9ff976675c0fd8047e3b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sat, 7 Jun 2025 23:59:19 +0100 Subject: [PATCH 13/23] added got a first cut at spawn_future --- .../beman/execution/detail/spawn_future.hpp | 146 +++++++++++++----- .../execution/exec-spawn-future.test.cpp | 54 ++++--- tests/beman/execution/exec-stop-when.test.cpp | 4 + 3 files changed, 151 insertions(+), 53 deletions(-) diff --git a/include/beman/execution/detail/spawn_future.hpp b/include/beman/execution/detail/spawn_future.hpp index 72035168..7600a9af 100644 --- a/include/beman/execution/detail/spawn_future.hpp +++ b/include/beman/execution/detail/spawn_future.hpp @@ -4,32 +4,38 @@ #ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_SPAWN_FUTURE #define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_SPAWN_FUTURE -#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 +#include #include -#include -#include + +#include #include +#include +#include +#include #include #include -#include -#include -#include // ---------------------------------------------------------------------------- @@ -48,14 +54,14 @@ struct spawn_future_state_base; template struct spawn_future_state_base<::beman::execution::completion_signatures> { static constexpr bool has_non_throwing_args_copy = (true && ... && non_throwing_args_copy_v); - using variant_t = ::beman::execution::detail::meta::unique< - ::std::conditional_t...>, - ::std::variant<::std::monostate, - ::std::tuple<::beman::execution::set_error_t, ::std::exception_ptr>, - ::beman::execution::detail::as_tuple_t...>>>; - - variant_t result{}; + using result_t = ::beman::execution::detail::meta::unique< + ::std::conditional_t...>, + ::std::variant<::std::monostate, + ::std::tuple<::beman::execution::set_error_t, ::std::exception_ptr>, + ::beman::execution::detail::as_tuple_t...>>>; + + result_t result{}; virtual ~spawn_future_state_base() = default; virtual auto complete() noexcept -> void = 0; }; @@ -106,15 +112,79 @@ template >> { - using alloc_t = typename ::std::allocator_traits::template rebind_alloc; - using traits_t = ::std::allocator_traits; + using alloc_t = typename ::std::allocator_traits::template rebind_alloc; + using traits_t = ::std::allocator_traits; + using spawned_sender_t = ::beman::execution::detail::future_spawned_sender; + using sigs_t = ::beman::execution::completion_signatures_of_t; + using receiver_t = ::beman::execution::detail::spawn_future_receiver; + static_assert(::beman::execution::sender); + static_assert(::beman::execution::receiver); + using op_t = ::beman::execution::connect_result_t; - spawn_future_state(auto a, auto&&, Token tok, auto&&...) : token(tok), alloc(a) { /*-dk:TODO*/ } - auto complete() noexcept -> void override { /*-dk:TODO*/ } + template <::beman::execution::sender S> + spawn_future_state(auto a, S&& s, Token tok, Env env) + : alloc(::std::move(a)), + op(::beman::execution::write_env( + ::beman::execution::detail::stop_when(::std::forward(s), source.get_token()), env), + receiver_t(this)), + token(::std::move(tok)), + associated(token.try_associate()) { + if (this->associated) { + ::beman::execution::start(this->op); + } else { + ::beman::execution::set_stopped(receiver_t(this)); + } + } + auto complete() noexcept -> void override { + { + ::std::lock_guard cerberos(this->gate); + if (this->receiver == nullptr) { + this->receiver = this; + return; + } + } + this->fun(this->receiver, this->result); + } auto abandon() noexcept -> void { - /*-dk:TODO*/ + { + ::std::lock_guard cerberos(this->gate); + if (this->receiver == nullptr) { + this->receiver = this; + this->fun = [](void*, spawn_future_state::result_t&) noexcept {}; + this->source.request_stop(); + return; + } + } this->destroy(); } + template <::beman::execution::receiver Rcvr> + static auto complete_receiver(Rcvr& rcvr, spawn_future_state::result_t& res) noexcept { + std::visit( + [&rcvr](Tuplish&& tuplish) noexcept { + if constexpr (!::std::same_as<::std::remove_cvref_t, ::std::monostate>) { + ::std::apply( + [&rcvr](auto cpo, Args&&... args) { + cpo(::std::move(rcvr), ::std::forward(args)...); + }, + ::std::forward(tuplish)); + } + }, + ::std::move(res)); + } + template <::beman::execution::receiver Rcvr> + auto consume(Rcvr& rcvr) noexcept -> void { + { + ::std::lock_guard cerberos(this->gate); + if (this->receiver != nullptr) { + this->receiver = &rcvr; + this->fun = [](void* ptr, spawn_future_state::result_t& res) noexcept { + spawn_future_state::complete_receiver(*static_cast(ptr), res); + }; + return; + } + } + spawn_future_state::complete_receiver(rcvr, this->result); + } auto destroy() noexcept -> void { Token tok{this->token}; bool assoc{this->associated}; @@ -128,10 +198,14 @@ struct spawn_future_state } } - ::std::mutex cerberos{}; - Token token; - bool associated{false}; - alloc_t alloc; + ::std::mutex gate{}; + alloc_t alloc; + ::beman::execution::inplace_stop_source source{}; + op_t op; + Token token; + bool associated{false}; + void* receiver{}; + auto (*fun)(void*, spawn_future_state::result_t&) noexcept -> void; }; template <::beman::execution::sender Sndr, typename Ev> @@ -153,7 +227,7 @@ class spawn_future_t { template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok, typename Ev> requires ::beman::execution::detail::queryable<::std::remove_cvref_t> auto operator()(Sndr&& sndr, Tok&& tok, Ev&& ev) const { - auto make{[&]() -> decltype(auto) { //-dk:TODO while decltype(auto) instead of auto? + auto make{[&]() -> decltype(auto) { //-dk:TODO why decltype(auto) instead of auto? return tok.wrap(::std::forward(sndr)); }}; using sndr_t = decltype(make()); @@ -164,11 +238,7 @@ class spawn_future_t { using state_alloc_t = typename ::std::allocator_traits::template rebind_alloc; using state_traits_t = ::std::allocator_traits; state_alloc_t state_alloc(alloc); - using deleter = decltype([](state_t* p) noexcept { - if (p) - p->abandon(); - }); - state_t* op{state_traits_t::allocate(state_alloc, 1u)}; + state_t* op{state_traits_t::allocate(state_alloc, 1u)}; try { state_traits_t::construct(state_alloc, op, alloc, make(), tok, senv); } catch (...) { @@ -176,6 +246,7 @@ class spawn_future_t { throw; } + using deleter = decltype([](state_t* p) noexcept { p->abandon(); }); return ::beman::execution::detail::make_sender(*this, ::std::unique_ptr{op}); } template <::beman::execution::sender Sndr, ::beman::execution::async_scope_token Tok> @@ -183,6 +254,11 @@ class spawn_future_t { return (*this)(::std::forward(sndr), ::std::forward(tok), ::beman::execution::empty_env{}); } }; + +template <> +struct impls_for : ::beman::execution::detail::default_impls { + static constexpr auto start{[](auto& state, auto& rcvr) noexcept -> void { state->consume(rcvr); }}; +}; } // namespace beman::execution::detail namespace beman::execution { diff --git a/tests/beman/execution/exec-spawn-future.test.cpp b/tests/beman/execution/exec-spawn-future.test.cpp index 26150b8c..b0c9339c 100644 --- a/tests/beman/execution/exec-spawn-future.test.cpp +++ b/tests/beman/execution/exec-spawn-future.test.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -33,7 +34,11 @@ static_assert(not test_std::sender); struct sender { using sender_concept = test_std::sender_t; - using completion_signatures = test_std::completion_signatures; + using completion_signatures = test_std::completion_signatures; + template + auto connect(Rcvr&& rcvr) { + return test_std::connect(test_std::just(), ::std::forward(rcvr)); + } }; static_assert(test_std::sender); @@ -79,22 +84,22 @@ auto test_state_base() { noexcept(std::declval>&>().complete())); using state0_t = state_base>; [[maybe_unused]] state0_t b0; - static_assert(std::same_as, state0_t::variant_t>); - static_assert(std::same_as); + static_assert(std::same_as, state0_t::result_t>); + static_assert(std::same_as); using state1_t = state_base>; [[maybe_unused]] state1_t b1; static_assert( - std::same_as>, state1_t::variant_t>); - static_assert(std::same_as); + std::same_as>, state1_t::result_t>); + static_assert(std::same_as); using state2_t = state_base>; [[maybe_unused]] state2_t b2; static_assert(std::same_as, std::tuple>, - typename state2_t::variant_t>); - static_assert(std::same_as); + typename state2_t::result_t>); + static_assert(std::same_as); using state3_t = state_base< test_std::completion_signatures>; @@ -102,8 +107,8 @@ auto test_state_base() { static_assert(std::same_as, std::tuple>, - typename state3_t::variant_t>); - static_assert(std::same_as); + typename state3_t::result_t>); + static_assert(std::same_as); using state4_t = state_base< test_std::completion_signatures>; @@ -111,14 +116,14 @@ auto test_state_base() { static_assert(std::same_as, std::tuple>, - typename state4_t::variant_t>); - static_assert(std::same_as); + typename state4_t::result_t>); + static_assert(std::same_as); using state5_t = state_base>; [[maybe_unused]] state5_t b5; static_assert( - std::same_as>, typename state5_t::variant_t>); - static_assert(std::same_as); + std::same_as>, typename state5_t::result_t>); + static_assert(std::same_as); } auto test_receiver() { @@ -192,11 +197,6 @@ auto test_receiver() { } } -template -auto test_spawn_future(Sndr&& sndr, Tok&& tok, Ev&& ev) { - test_std::spawn_future(std::forward(sndr), std::forward(tok), std::forward(ev)); -} - struct allocator { using value_type = int; int value{}; @@ -266,6 +266,24 @@ auto test_get_allocator() { } } +struct rcvr { + using receiver_concept = test_std::receiver_t; + + auto set_value(auto&&...) && noexcept -> void {} + auto set_error(auto&&) && noexcept -> void {} + auto set_stopped() && noexcept -> void {} +}; +static_assert(test_std::receiver); + +template +auto test_spawn_future(Sndr&& sndr, Tok&& tok, Ev&& ev) { + auto sender{test_std::spawn_future(std::forward(sndr), std::forward(tok), std::forward(ev))}; + static_assert(test_std::sender); + auto state(test_std::connect(std::move(sender), rcvr{})); + static_assert(test_std::operation_state); + test_std::start(state); +} + } // namespace TEST(exec_spawn_future) { diff --git a/tests/beman/execution/exec-stop-when.test.cpp b/tests/beman/execution/exec-stop-when.test.cpp index 9708e0d5..52690c05 100644 --- a/tests/beman/execution/exec-stop-when.test.cpp +++ b/tests/beman/execution/exec-stop-when.test.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -140,4 +142,6 @@ TEST(exec_stop_when) { source2.request_stop(); ASSERT(comp == completion::stopped); } + test_std::inplace_stop_source source; + test_std::sync_wait(test_detail::stop_when(test_std::just(), source.get_token())); } From a7a2bae6c557eb8be9f1ddbab50712abf3292eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 8 Jun 2025 05:00:17 +0100 Subject: [PATCH 14/23] added typenames needed by gcc --- include/beman/execution/detail/spawn_future.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/beman/execution/detail/spawn_future.hpp b/include/beman/execution/detail/spawn_future.hpp index 7600a9af..5ac9740a 100644 --- a/include/beman/execution/detail/spawn_future.hpp +++ b/include/beman/execution/detail/spawn_future.hpp @@ -150,7 +150,7 @@ struct spawn_future_state ::std::lock_guard cerberos(this->gate); if (this->receiver == nullptr) { this->receiver = this; - this->fun = [](void*, spawn_future_state::result_t&) noexcept {}; + this->fun = [](void*, typename spawn_future_state::result_t&) noexcept {}; this->source.request_stop(); return; } @@ -158,7 +158,7 @@ struct spawn_future_state this->destroy(); } template <::beman::execution::receiver Rcvr> - static auto complete_receiver(Rcvr& rcvr, spawn_future_state::result_t& res) noexcept { + static auto complete_receiver(Rcvr& rcvr, typename spawn_future_state::result_t& res) noexcept { std::visit( [&rcvr](Tuplish&& tuplish) noexcept { if constexpr (!::std::same_as<::std::remove_cvref_t, ::std::monostate>) { @@ -177,7 +177,7 @@ struct spawn_future_state ::std::lock_guard cerberos(this->gate); if (this->receiver != nullptr) { this->receiver = &rcvr; - this->fun = [](void* ptr, spawn_future_state::result_t& res) noexcept { + this->fun = [](void* ptr, typename spawn_future_state::result_t& res) noexcept { spawn_future_state::complete_receiver(*static_cast(ptr), res); }; return; @@ -205,7 +205,7 @@ struct spawn_future_state Token token; bool associated{false}; void* receiver{}; - auto (*fun)(void*, spawn_future_state::result_t&) noexcept -> void; + auto (*fun)(void*, typename spawn_future_state::result_t&) noexcept -> void; }; template <::beman::execution::sender Sndr, typename Ev> From 15e99d61e5558aee844dc7b2d37f46cd88dc8acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 8 Jun 2025 15:52:20 +0100 Subject: [PATCH 15/23] fix the spawn_future completion behavior --- .../beman/execution/detail/spawn_future.hpp | 26 ++-- tests/beman/execution/CMakeLists.txt | 8 +- .../exec-scope-simple-counting.test.cpp | 4 +- .../execution/exec-spawn-future.test.cpp | 146 ++++++++++++++++-- .../execution/include/test/execution.hpp | 9 +- 5 files changed, 153 insertions(+), 40 deletions(-) diff --git a/include/beman/execution/detail/spawn_future.hpp b/include/beman/execution/detail/spawn_future.hpp index 5ac9740a..d054ce7c 100644 --- a/include/beman/execution/detail/spawn_future.hpp +++ b/include/beman/execution/detail/spawn_future.hpp @@ -138,24 +138,28 @@ struct spawn_future_state auto complete() noexcept -> void override { { ::std::lock_guard cerberos(this->gate); - if (this->receiver == nullptr) { + if (this->fun == nullptr) { this->receiver = this; return; } } - this->fun(this->receiver, this->result); + this->fun(this->receiver, *this); } auto abandon() noexcept -> void { - { + bool ready{[&] { ::std::lock_guard cerberos(this->gate); if (this->receiver == nullptr) { this->receiver = this; - this->fun = [](void*, typename spawn_future_state::result_t&) noexcept {}; - this->source.request_stop(); - return; + this->fun = [](void*, spawn_future_state& state) noexcept { state.destroy(); }; + return false; } + return true; + }()}; + if (ready) { + this->destroy(); + } else { + this->source.request_stop(); } - this->destroy(); } template <::beman::execution::receiver Rcvr> static auto complete_receiver(Rcvr& rcvr, typename spawn_future_state::result_t& res) noexcept { @@ -175,10 +179,10 @@ struct spawn_future_state auto consume(Rcvr& rcvr) noexcept -> void { { ::std::lock_guard cerberos(this->gate); - if (this->receiver != nullptr) { + if (this->receiver == nullptr) { this->receiver = &rcvr; - this->fun = [](void* ptr, typename spawn_future_state::result_t& res) noexcept { - spawn_future_state::complete_receiver(*static_cast(ptr), res); + this->fun = [](void* ptr, spawn_future_state& state) noexcept { + spawn_future_state::complete_receiver(*static_cast(ptr), state.result); }; return; } @@ -205,7 +209,7 @@ struct spawn_future_state Token token; bool associated{false}; void* receiver{}; - auto (*fun)(void*, typename spawn_future_state::result_t&) noexcept -> void; + auto (*fun)(void*, spawn_future_state&) noexcept -> void = nullptr; }; template <::beman::execution::sender Sndr, typename Ev> diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index 047f0937..c2daf3b7 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -138,9 +138,9 @@ foreach(test ${execution_tests}) endforeach() if(FALSE) -if(NOT PROJECT_IS_TOP_LEVEL AND BEMAN_EXECUTION_ENABLE_TESTING) - # test if the targets are findable from the build directory - # cmake-format: off + if(NOT PROJECT_IS_TOP_LEVEL AND BEMAN_EXECUTION_ENABLE_TESTING) + # test if the targets are findable from the build directory + # cmake-format: off add_test(NAME find-package-test COMMAND ${CMAKE_CTEST_COMMAND} # --verbose @@ -159,5 +159,5 @@ if(NOT PROJECT_IS_TOP_LEVEL AND BEMAN_EXECUTION_ENABLE_TESTING) # TODO(CK): Needed too? "--config $" ) # cmake-format: on -endif() + endif() endif() diff --git a/tests/beman/execution/exec-scope-simple-counting.test.cpp b/tests/beman/execution/exec-scope-simple-counting.test.cpp index bd505c8f..944907aa 100644 --- a/tests/beman/execution/exec-scope-simple-counting.test.cpp +++ b/tests/beman/execution/exec-scope-simple-counting.test.cpp @@ -54,9 +54,7 @@ struct join_receiver { }; auto ctor() -> void { - { - test_std::simple_counting_scope scope; - } + { test_std::simple_counting_scope scope; } test::death([] { test_std::simple_counting_scope scope; scope.get_token().try_associate(); diff --git a/tests/beman/execution/exec-spawn-future.test.cpp b/tests/beman/execution/exec-spawn-future.test.cpp index b0c9339c..f0e0a603 100644 --- a/tests/beman/execution/exec-spawn-future.test.cpp +++ b/tests/beman/execution/exec-spawn-future.test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -32,20 +33,40 @@ static_assert(test_detail::queryable); struct non_sender {}; static_assert(not test_std::sender); +template struct sender { - using sender_concept = test_std::sender_t; - using completion_signatures = test_std::completion_signatures; + using sender_concept = test_std::sender_t; + using completion_signatures = + test_std::completion_signatures; + + struct state_base { + virtual ~state_base() = default; + virtual auto complete(T...) -> void = 0; + }; template - auto connect(Rcvr&& rcvr) { - return test_std::connect(test_std::just(), ::std::forward(rcvr)); + struct state : state_base { + using operation_state_concept = test_std::operation_state_t; + std::remove_cvref_t rcvr; + state_base** handle{}; + auto complete(T... a) -> void override { test_std::set_value(std::move(this->rcvr), a...); } + state(auto&& r, state_base** h) : rcvr(std::forward(r)), handle(h) {} + auto start() & noexcept { *this->handle = this; } + }; + state_base** handle{nullptr}; + template + auto connect(Rcvr&& rcvr) -> state { + return state(std::forward(rcvr), this->handle); } }; -static_assert(test_std::sender); +static_assert(test_std::sender>); +static_assert(test_std::sender>); +static_assert(test_std::sender>); template struct token { - auto try_associate() -> bool { return {}; } - auto disassociate() noexcept(Noexcept) -> void {} + std::size_t* count{nullptr}; + auto try_associate() -> bool { return this->count && bool(++*this->count); } + auto disassociate() noexcept(Noexcept) -> void { --*this->count; } template auto wrap(Sender&& sender) -> Sender { return std::forward(sender); @@ -226,7 +247,7 @@ static_assert(test_std::sender); auto test_get_allocator() { { alloc_env ae{87}; - auto [alloc, ev] = test_detail::spawn_future_get_allocator(sender{}, ae); + auto [alloc, ev] = test_detail::spawn_future_get_allocator(sender<>{}, ae); static_assert(std::same_as); ASSERT(alloc == allocator{87}); static_assert(std::same_as); @@ -259,7 +280,7 @@ auto test_get_allocator() { ASSERT(test_std::get_stop_token(ev) == source.get_token()); } { - auto [alloc, ev] = test_detail::spawn_future_get_allocator(sender{}, env{42}); + auto [alloc, ev] = test_detail::spawn_future_get_allocator(sender<>{}, env{42}); static_assert(std::same_as>); static_assert(std::same_as); ASSERT(ev == env{42}); @@ -275,13 +296,104 @@ struct rcvr { }; static_assert(test_std::receiver); -template -auto test_spawn_future(Sndr&& sndr, Tok&& tok, Ev&& ev) { - auto sender{test_std::spawn_future(std::forward(sndr), std::forward(tok), std::forward(ev))}; - static_assert(test_std::sender); - auto state(test_std::connect(std::move(sender), rcvr{})); - static_assert(test_std::operation_state); - test_std::start(state); +auto test_spawn_future() { + { + std::size_t count{}; + sender::state_base* handle{}; + int result{}; + ASSERT(count == 0u); + ASSERT(handle == nullptr); + ASSERT(result == 0); + + auto sndr{test_std::spawn_future( + sender{&handle} | test_std::then([](int v) { return v; }), token{&count}, env{})}; + ASSERT(count == 1u); + ASSERT(handle != nullptr); + ASSERT(result == 0); + + { + auto state(test_std::connect(std::move(sndr) | test_std::then([&result](int v) { result = v; }), rcvr{})); + ASSERT(result == 0); + + test_std::start(state); + ASSERT(count == 1u); + ASSERT(result == 0); + + handle->complete(42); + ASSERT(result == 42); + ASSERT(count == 1u); + } + ASSERT(count == 0u); + } + { + std::size_t count{}; + sender::state_base* handle{}; + int result{}; + ASSERT(count == 0u); + ASSERT(handle == nullptr); + ASSERT(result == 0); + + auto sndr{test_std::spawn_future( + sender{&handle} | test_std::then([](int v) { return v; }), token{&count}, env{})}; + ASSERT(count == 1u); + ASSERT(handle != nullptr); + ASSERT(result == 0); + + { + auto state(test_std::connect(std::move(sndr) | test_std::then([&result](int v) { result = v; }), rcvr{})); + ASSERT(result == 0); + + handle->complete(42); + ASSERT(result == 0); + + test_std::start(state); + + ASSERT(result == 42); + ASSERT(count == 1u); + } + ASSERT(count == 0u); + } + { + std::size_t count{}; + sender::state_base* handle{}; + int result{}; + ASSERT(count == 0u); + ASSERT(handle == nullptr); + ASSERT(result == 0); + + { + auto sndr{test_std::spawn_future( + sender{&handle} | test_std::then([](int v) { return v; }), token{&count}, env{})}; + ASSERT(count == 1u); + ASSERT(handle != nullptr); + ASSERT(result == 0); + } + ASSERT(count == 1u); + handle->complete(17); + ASSERT(count == 0u); + ASSERT(result == 0); + } + { + std::size_t count{}; + sender::state_base* handle{}; + int result{}; + ASSERT(count == 0u); + ASSERT(handle == nullptr); + ASSERT(result == 0); + + { + auto sndr{test_std::spawn_future( + sender{&handle} | test_std::then([](int v) { return v; }), token{&count}, env{})}; + ASSERT(count == 1u); + ASSERT(handle != nullptr); + ASSERT(result == 0); + + handle->complete(17); + ASSERT(result == 0); + } + ASSERT(count == 0u); + ASSERT(result == 0); + } } } // namespace @@ -299,5 +411,5 @@ TEST(exec_spawn_future) { test_get_allocator(); - test_spawn_future(sender{}, token{}, env{}); + test_spawn_future(); } diff --git a/tests/beman/execution/include/test/execution.hpp b/tests/beman/execution/include/test/execution.hpp index 1a5cbb7e..cb7bc5d4 100644 --- a/tests/beman/execution/include/test/execution.hpp +++ b/tests/beman/execution/include/test/execution.hpp @@ -58,17 +58,16 @@ struct throws { auto operator=(const throws&) noexcept(false) -> throws& = default; }; -inline auto death([[maybe_unused]] auto fun, - [[maybe_unused]] ::std::source_location location = test::source_location::current()) noexcept - -> void { +inline auto +death([[maybe_unused]] auto fun, + [[maybe_unused]] ::std::source_location location = test::source_location::current()) noexcept -> void { #ifndef _MSC_VER switch (::pid_t rc = ::fork()) { default: { int stat{}; ASSERT(rc == ::wait(&stat)); if (stat == EXIT_SUCCESS) { - ::std::cerr << "failed death test at " - << "file=" << location.file_name() << ":" << location.line() << "\n" + ::std::cerr << "failed death test at " << "file=" << location.file_name() << ":" << location.line() << "\n" << std::flush; ASSERT(stat != EXIT_SUCCESS); } From c2ef806935937b32791791256d717b50592e2ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dietmar=20K=C3=BChl?= Date: Sun, 8 Jun 2025 22:02:54 +0100 Subject: [PATCH 16/23] fixed the completion signatures for spawn_future --- .../execution/detail/meta_contain_same.hpp | 27 +++++++++++++++++++ .../beman/execution/detail/spawn_future.hpp | 18 ++++++++++--- src/beman/execution/CMakeLists.txt | 1 + .../execution/exec-spawn-future.test.cpp | 20 +++++++++++--- 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 include/beman/execution/detail/meta_contain_same.hpp diff --git a/include/beman/execution/detail/meta_contain_same.hpp b/include/beman/execution/detail/meta_contain_same.hpp new file mode 100644 index 00000000..c5874e8d --- /dev/null +++ b/include/beman/execution/detail/meta_contain_same.hpp @@ -0,0 +1,27 @@ +// include/beman/execution/detail/meta_contain_same.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_META_CONTAIN_SAME +#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_META_CONTAIN_SAME + +#include + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail::meta { +template +struct contain_same_t; +template