diff --git a/include/beman/execution/detail/allocator_of.hpp b/include/beman/execution/detail/task/allocator_of.hpp similarity index 86% rename from include/beman/execution/detail/allocator_of.hpp rename to include/beman/execution/detail/task/allocator_of.hpp index 4e6d9010..48a1fec7 100644 --- a/include/beman/execution/detail/allocator_of.hpp +++ b/include/beman/execution/detail/task/allocator_of.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/allocator_of.hpp -*-C++-*- +// include/beman/execution/detail/task/allocator_of.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_ALLOCATOR_OF -#define INCLUDED_BEMAN_EXECUTION_DETAIL_ALLOCATOR_OF +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_ALLOCATOR_OF +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_ALLOCATOR_OF #include #include diff --git a/include/beman/execution/detail/task/allocator_support.hpp b/include/beman/execution/detail/task/allocator_support.hpp new file mode 100644 index 00000000..da1ba67a --- /dev/null +++ b/include/beman/execution/detail/task/allocator_support.hpp @@ -0,0 +1,149 @@ +// include/beman/execution/detail/task/allocator_support.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_ALLOCATOR_SUPPORT +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_ALLOCATOR_SUPPORT + +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { +struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) allocator_support_allocation_unit { + std::byte bytes[__STDCPP_DEFAULT_NEW_ALIGNMENT__]; +}; + +static_assert(sizeof(allocator_support_allocation_unit) == __STDCPP_DEFAULT_NEW_ALIGNMENT__); +static_assert(alignof(allocator_support_allocation_unit) == __STDCPP_DEFAULT_NEW_ALIGNMENT__); + +struct allocator_support_header { + void (*deallocate)(void*, std::size_t) noexcept; + std::size_t count; + std::size_t palloc_offset; +}; + +template +using allocator_support_alloc_t = std::remove_cvref_t; + +template +using allocator_support_palloc_t = typename std::allocator_traits< + allocator_support_alloc_t>::template rebind_alloc; + +template +concept allocator_support_allocator_like = requires { typename allocator_support_alloc_t::value_type; }; + +template +concept allocator_support_allocator_arg = + allocator_support_allocator_like && requires(allocator_support_alloc_t& alloc, std::size_t size) { + typename allocator_support_palloc_t; + typename std::allocator_traits>::pointer; + requires std::same_as>::pointer, + allocator_support_allocation_unit*>; + requires(alignof(allocator_support_palloc_t) <= alignof(allocator_support_allocation_unit)); + allocator_support_palloc_t(alloc); + std::allocator_traits>::allocate( + std::declval&>(), size); + }; + +/*! + * \brief Utility adding allocator support to type by embedding the allocator + * \headerfile beman/execution/task.hpp + * + * To add allocator support using this class just publicly inherit from + * allocator_support. This utility is probably + * only useful for coroutine promise types. + * + * This struct is a massive hack, primarily support allocators for coroutines. + * The memory for coroutines is implicitly managed and there isn't a way to + * provide the memory directly. Instead, the promise_type can overload an + * operator new and use a leading std::allocator_arg/allocator pair when it + * is present. Even worse, the operator delete only gets passed a pointer to + * delete and a size. To determine the correct allocator the operator delete + * stores a type-erased deallocation header and the rebound allocator in the + * allocation block. + */ +template +struct allocator_support { + static std::size_t align_up(std::size_t value, std::size_t alignment) { + return ((value + alignment - 1u) / alignment) * alignment; + } + + static std::size_t header_offset(std::size_t size) { + return allocator_support::align_up(size, alignof(allocator_support_header)); + } + + static allocator_support_header* get_header(void* ptr, std::size_t size) { + ptr = static_cast(ptr) + allocator_support::header_offset(size); + return ::std::launder(reinterpret_cast(ptr)); + } + + template + static void deallocate_with(void* ptr, std::size_t size) noexcept { + using palloc_traits = std::allocator_traits; + + allocator_support_header* header{allocator_support::get_header(ptr, size)}; + auto* palloc_ptr{ + ::std::launder(reinterpret_cast(static_cast(ptr) + header->palloc_offset))}; + PAlloc palloc{*palloc_ptr}; + std::size_t count{header->count}; + palloc_ptr->~PAlloc(); + palloc_traits::deallocate(palloc, static_cast(ptr), count); + } + + template + requires allocator_support_allocator_arg + static void* allocate(std::size_t size, Alloc alloc) { + using palloc_t = allocator_support_palloc_t; + using palloc_traits = std::allocator_traits; + + palloc_t palloc{alloc}; + std::size_t header_offset{allocator_support::header_offset(size)}; + std::size_t palloc_offset{ + allocator_support::align_up(header_offset + sizeof(allocator_support_header), alignof(palloc_t))}; + std::size_t count{(palloc_offset + sizeof(palloc_t) + sizeof(allocator_support_allocation_unit) - 1u) / + sizeof(allocator_support_allocation_unit)}; + + allocator_support_allocation_unit* ptr{palloc_traits::allocate(palloc, count)}; + try { + new (static_cast(static_cast(ptr)) + header_offset) + allocator_support_header{&allocator_support::deallocate_with, count, palloc_offset}; + new (static_cast(static_cast(ptr)) + palloc_offset) palloc_t(palloc); + } catch (...) { + palloc_traits::deallocate(palloc, ptr, count); + throw; + } + return ptr; + } + + static void* operator new(std::size_t size) { return allocator_support::allocate(size, Allocator{}); } + + template + requires allocator_support_allocator_arg + static void* operator new(std::size_t size, std::allocator_arg_t, Alloc alloc, A&&...) { + return allocator_support::allocate(size, alloc); + } + + template + requires allocator_support_allocator_arg + static void* operator new(std::size_t size, const This&, std::allocator_arg_t, Alloc alloc, A&&...) { + return allocator_support::allocate(size, alloc); + } + + template + static void operator delete(void* ptr, std::size_t size, const A&...) noexcept { + allocator_support::operator delete(ptr, size); + } + static void operator delete(void* ptr, std::size_t size) noexcept { + allocator_support::get_header(ptr, size)->deallocate(ptr, size); + } +}; +} // namespace beman::execution::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/completion.hpp b/include/beman/execution/detail/task/completion.hpp similarity index 79% rename from include/beman/execution/detail/completion.hpp rename to include/beman/execution/detail/task/completion.hpp index 75d82ebf..aee3ddfc 100644 --- a/include/beman/execution/detail/completion.hpp +++ b/include/beman/execution/detail/task/completion.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/completion.hpp -*-C++-*- +// include/beman/execution/detail/task/completion.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION -#define INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_COMPLETION +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_COMPLETION #include diff --git a/include/beman/execution/detail/error_types_of.hpp b/include/beman/execution/detail/task/error_types_of.hpp similarity index 76% rename from include/beman/execution/detail/error_types_of.hpp rename to include/beman/execution/detail/task/error_types_of.hpp index ea130048..b6902bbe 100644 --- a/include/beman/execution/detail/error_types_of.hpp +++ b/include/beman/execution/detail/task/error_types_of.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/error_types_of.hpp -*-C++-*- +// include/beman/execution/detail/task/error_types_of.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_CONTEXT_ERROR_TYPES_OF -#define INCLUDED_BEMAN_EXECUTION_DETAIL_CONTEXT_ERROR_TYPES_OF +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_ERROR_TYPES_OF +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_ERROR_TYPES_OF #include #include @@ -25,4 +25,4 @@ using error_types_of_t = typename error_types_of::type; // ---------------------------------------------------------------------------- -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_CONTEXT_ERROR_TYPES_OF +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_ERROR_TYPES_OF diff --git a/include/beman/execution/detail/find_allocator.hpp b/include/beman/execution/detail/task/find_allocator.hpp similarity index 88% rename from include/beman/execution/detail/find_allocator.hpp rename to include/beman/execution/detail/task/find_allocator.hpp index 8ace784d..9f4a8862 100644 --- a/include/beman/execution/detail/find_allocator.hpp +++ b/include/beman/execution/detail/task/find_allocator.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/find_allocator.hpp -*-C++-*- +// include/beman/execution/detail/task/find_allocator.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_FIND_ALLOCATOR -#define INCLUDED_BEMAN_EXECUTION_DETAIL_FIND_ALLOCATOR +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_FIND_ALLOCATOR +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_FIND_ALLOCATOR #include #include diff --git a/include/beman/execution/detail/handle.hpp b/include/beman/execution/detail/task/handle.hpp similarity index 88% rename from include/beman/execution/detail/handle.hpp rename to include/beman/execution/detail/task/handle.hpp index 60d8ce5b..9df48499 100644 --- a/include/beman/execution/detail/handle.hpp +++ b/include/beman/execution/detail/task/handle.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/handle.hpp -*-C++-*- +// include/beman/execution/detail/task/handle.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_HANDLE -#define INCLUDED_BEMAN_EXECUTION_DETAIL_HANDLE +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_HANDLE +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_HANDLE #include #include diff --git a/include/beman/execution/detail/task/infallible_scheduler.hpp b/include/beman/execution/detail/task/infallible_scheduler.hpp new file mode 100644 index 00000000..a218fe52 --- /dev/null +++ b/include/beman/execution/detail/task/infallible_scheduler.hpp @@ -0,0 +1,58 @@ +// include/beman/execution/detail/task/infallible_scheduler.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_INFALLIBLE_SCHEDULER +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_INFALLIBLE_SCHEDULER + +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution.detail.completion_signatures; +import beman.execution.detail.completion_signatures_of_t; +import beman.execution.detail.env; +import beman.execution.detail.schedule; +import beman.execution.detail.scheduler; +import beman.execution.detail.set_stopped; +import beman.execution.detail.set_value; +import beman.execution.detail.stop_token_of_t; +import beman.execution.detail.unstoppable_token; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { +template +concept completes_with = + ::std::same_as<::beman::execution::completion_signatures, + ::beman::execution:: + completion_signatures_of_t())), Env>>; + +template +concept infallible_scheduler = + (::beman::execution::scheduler) && + (::beman::execution::detail::completes_with || + (!::beman::execution::unstoppable_token<::beman::execution::stop_token_of_t> && + (::beman::execution::detail:: + completes_with || + ::beman::execution::detail:: + completes_with))); +} // namespace beman::execution::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/detail/logger.hpp b/include/beman/execution/detail/task/logger.hpp similarity index 85% rename from include/beman/execution/detail/logger.hpp rename to include/beman/execution/detail/task/logger.hpp index 4fea369e..b6d40091 100644 --- a/include/beman/execution/detail/logger.hpp +++ b/include/beman/execution/detail/task/logger.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/logger.hpp -*-C++-*- +// include/beman/execution/detail/task/logger.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_LOGGER -#define INCLUDED_BEMAN_EXECUTION_DETAIL_LOGGER +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_LOGGER +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_LOGGER #include #include diff --git a/include/beman/execution/detail/poly.hpp b/include/beman/execution/detail/task/poly.hpp similarity index 90% rename from include/beman/execution/detail/poly.hpp rename to include/beman/execution/detail/task/poly.hpp index 33358bd5..0ff5d18b 100644 --- a/include/beman/execution/detail/poly.hpp +++ b/include/beman/execution/detail/task/poly.hpp @@ -1,14 +1,19 @@ -// include/beman/execution/detail/poly.hpp -*-C++-*- +// include/beman/execution/detail/task/poly.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_POLY -#define INCLUDED_BEMAN_EXECUTION_DETAIL_POLY +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_POLY +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_POLY +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else #include #include #include #include #include +#endif // ---------------------------------------------------------------------------- diff --git a/include/beman/execution/detail/promise_env.hpp b/include/beman/execution/detail/task/promise_env.hpp similarity index 88% rename from include/beman/execution/detail/promise_env.hpp rename to include/beman/execution/detail/task/promise_env.hpp index 9560a0e9..6b2a2e3a 100644 --- a/include/beman/execution/detail/promise_env.hpp +++ b/include/beman/execution/detail/task/promise_env.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/promise_env.hpp -*-C++-*- +// include/beman/execution/detail/task/promise_env.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_PROMISE_ENV -#define INCLUDED_BEMAN_EXECUTION_DETAIL_PROMISE_ENV +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_PROMISE_ENV +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_PROMISE_ENV #include #include diff --git a/include/beman/execution/detail/state_rep.hpp b/include/beman/execution/detail/task/state_rep.hpp similarity index 91% rename from include/beman/execution/detail/state_rep.hpp rename to include/beman/execution/detail/task/state_rep.hpp index 8d369924..c30d8ad6 100644 --- a/include/beman/execution/detail/state_rep.hpp +++ b/include/beman/execution/detail/task/state_rep.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/state_rep.hpp -*-C++-*- +// include/beman/execution/detail/task/state_rep.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_STATE_REP -#define INCLUDED_BEMAN_EXECUTION_DETAIL_STATE_REP +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_STATE_REP +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_STATE_REP #include #include diff --git a/include/beman/execution/detail/stop_source_of.hpp b/include/beman/execution/detail/task/stop_source.hpp similarity index 73% rename from include/beman/execution/detail/stop_source_of.hpp rename to include/beman/execution/detail/task/stop_source.hpp index 85d6e25e..2386859c 100644 --- a/include/beman/execution/detail/stop_source_of.hpp +++ b/include/beman/execution/detail/task/stop_source.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/stop_source_of.hpp -*-C++-*- +// include/beman/execution/detail/task/stop_source.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_STOP_SOURCE_OF -#define INCLUDED_BEMAN_EXECUTION_DETAIL_STOP_SOURCE_OF +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_STOP_SOURCE +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_STOP_SOURCE #include @@ -24,4 +24,4 @@ using stop_source_of_t = typename stop_source_of::type; // ---------------------------------------------------------------------------- -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_STOP_SOURCE_OF +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_STOP_SOURCE diff --git a/include/beman/execution/detail/sub_visit.hpp b/include/beman/execution/detail/task/sub_visit.hpp similarity index 88% rename from include/beman/execution/detail/sub_visit.hpp rename to include/beman/execution/detail/task/sub_visit.hpp index 19b9cb59..6587b25d 100644 --- a/include/beman/execution/detail/sub_visit.hpp +++ b/include/beman/execution/detail/task/sub_visit.hpp @@ -1,10 +1,10 @@ -// include/beman/execution/detail/sub_visit.hpp -*-C++-*- +// include/beman/execution/detail/task/sub_visit.hpp -*-C++-*- // ---------------------------------------------------------------------------- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // ---------------------------------------------------------------------------- -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_SUB_VISIT -#define INCLUDED_BEMAN_EXECUTION_DETAIL_SUB_VISIT +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_SUB_VISIT +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_SUB_VISIT #include #include diff --git a/include/beman/execution/detail/task/task_scheduler.hpp b/include/beman/execution/detail/task/task_scheduler.hpp new file mode 100644 index 00000000..48299a93 --- /dev/null +++ b/include/beman/execution/detail/task/task_scheduler.hpp @@ -0,0 +1,227 @@ +// include/beman/execution/detail/task/task_scheduler.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_SCHEDULER +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TASK_SCHEDULER + +#include +#include +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution.detail.completion_signatures; +import beman.execution.detail.connect; +import beman.execution.detail.env; +import beman.execution.detail.get_completion_scheduler; +import beman.execution.detail.get_env; +import beman.execution.detail.operation_state; +import beman.execution.detail.receiver; +import beman.execution.detail.schedule; +import beman.execution.detail.scheduler; +import beman.execution.detail.scheduler_tag; +import beman.execution.detail.sender; +import beman.execution.detail.set_value; +import beman.execution.detail.start; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +// ---------------------------------------------------------------------------- + +namespace beman::execution::detail { + +/*! + * \brief Type-erasing scheduler + * \headerfile beman/execution/task.hpp + * + * The class `task_scheduler` is used to type-erase any infallible scheduler class. + */ +class task_scheduler { + struct state_base { + virtual ~state_base() = default; + virtual void complete_value() = 0; + }; + + struct inner_state { + struct receiver { + using receiver_concept = ::beman::execution::receiver_tag; + state_base* state; + void set_value() && noexcept { this->state->complete_value(); } + }; + static_assert(::beman::execution::receiver); + + struct base { + virtual ~base() = default; + virtual void start() = 0; + }; + template <::beman::execution::sender Sender> + struct concrete : base { + using state_t = + decltype(::beman::execution::connect(::std::declval(), ::std::declval())); + state_t state; + + template <::beman::execution::sender S> + concrete(S&& s, state_base* b) : state(::beman::execution::connect(::std::forward(s), receiver{b})) {} + void start() override { ::beman::execution::start(state); } + }; + + ::beman::execution::detail::poly state; + + template <::beman::execution::sender S> + inner_state(S&& s, state_base* b) : state(static_cast*>(nullptr), ::std::forward(s), b) {} + + void start() { this->state->start(); } + }; + + template <::beman::execution::receiver Receiver> + struct state : state_base { + using operation_state_concept = ::beman::execution::operation_state_tag; + ::std::remove_cvref_t receiver; + inner_state s; + + template <::beman::execution::receiver R, typename PS> + state(R&& r, PS& ps) : receiver(::std::forward(r)), s(ps->connect(this)) {} + void start() & noexcept { this->s.start(); } + void complete_value() override { ::beman::execution::set_value(::std::move(this->receiver)); } + }; + + class sender; + class env { + friend class sender; + + private: + const sender* sndr; + explicit env(const sender* s) : sndr(s) {} + + public: + task_scheduler + query(const ::beman::execution::get_completion_scheduler_t<::beman::execution::set_value_t>&) const noexcept { + return this->sndr->inner_sender->get_completion_scheduler(); + } + }; + + class sender { + friend class env; + + private: + struct base { + virtual ~base() = default; + virtual base* move(void*) = 0; + virtual base* clone(void*) const = 0; + virtual inner_state connect(state_base*) = 0; + virtual task_scheduler get_completion_scheduler() const = 0; + }; + template <::beman::execution::scheduler Scheduler> + struct concrete : base { + using sender_type = decltype(::beman::execution::schedule(::std::declval())); + sender_type sender; + + template <::beman::execution::scheduler S> + explicit concrete(S&& s) : sender(::beman::execution::schedule(::std::forward(s))) {} + base* move(void* buffer) override { return new (buffer) concrete(::std::move(*this)); } + base* clone(void* buffer) const override { return new (buffer) concrete(*this); } + inner_state connect(state_base* b) override { return inner_state(::std::move(sender), b); } + task_scheduler get_completion_scheduler() const override { + return task_scheduler(::beman::execution::get_completion_scheduler<::beman::execution::set_value_t>( + ::beman::execution::get_env(this->sender))); + } + }; + ::beman::execution::detail::poly inner_sender; + + public: + using sender_concept = ::beman::execution::sender_tag; + using completion_signatures = ::beman::execution::completion_signatures<::beman::execution::set_value_t()>; + + template + static consteval auto get_completion_signatures() noexcept -> completion_signatures { + return {}; + } + + template <::beman::execution::scheduler S> + explicit sender(S&& s) : inner_sender(static_cast*>(nullptr), ::std::forward(s)) {} + sender(sender&&) = default; + sender(const sender&) = default; + + template <::beman::execution::receiver R> + auto connect(R&& r) -> state { + return state(::std::forward(r), this->inner_sender); + } + + auto get_env() const noexcept -> env { return env(this); } + }; + + struct base { + virtual ~base() = default; + virtual sender schedule() = 0; + virtual base* move(void*) = 0; + virtual base* clone(void*) const = 0; + virtual bool equals(const base*) const = 0; + }; + template <::beman::execution::scheduler Scheduler> + struct concrete : base { + Scheduler scheduler; + + template + requires ::beman::execution::scheduler<::std::remove_cvref_t> + explicit concrete(S&& s) : scheduler(::std::forward(s)) {} + sender schedule() override { return sender(this->scheduler); } + base* move(void* buffer) override { return new (buffer) concrete(::std::move(*this)); } + base* clone(void* buffer) const override { return new (buffer) concrete(*this); } + bool equals(const base* o) const override { + auto other{dynamic_cast(o)}; + return other ? this->scheduler == other->scheduler : false; + } + }; + + ::beman::execution::detail::poly scheduler; + + public: + using scheduler_concept = ::beman::execution::scheduler_tag; + + template > + requires(not ::std::same_as>) && + ::beman::execution::scheduler<::std::remove_cvref_t> && + ::beman::execution::detail::infallible_scheduler<::std::remove_cvref_t, ::beman::execution::env<>> + explicit task_scheduler(S&& s, Allocator = {}) + : scheduler(static_cast>*>(nullptr), ::std::forward(s)) {} + task_scheduler(task_scheduler&&) = default; + task_scheduler(const task_scheduler&) = default; + template + task_scheduler(const task_scheduler& other, Allocator) : scheduler(other.scheduler) {} + auto operator=(const task_scheduler&) -> task_scheduler& = default; + ~task_scheduler() = default; + + auto schedule() -> sender { return this->scheduler->schedule(); } + auto operator==(const task_scheduler&) const -> bool = default; + + template + requires(not ::std::same_as) && ::beman::execution::scheduler + auto operator==(const Sched& other) const -> bool { + return *this == task_scheduler(other); + } +}; +static_assert(::beman::execution::scheduler); + +} // namespace beman::execution::detail + +// ---------------------------------------------------------------------------- + +#endif diff --git a/include/beman/execution/task.hpp b/include/beman/execution/task.hpp new file mode 100644 index 00000000..a6a776b5 --- /dev/null +++ b/include/beman/execution/task.hpp @@ -0,0 +1,25 @@ +// include/beman/execution/task.hpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#ifndef INCLUDED_BEMAN_EXECUTION_TASK +#define INCLUDED_BEMAN_EXECUTION_TASK + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- + +#endif // INCLUDED_BEMAN_EXECUTION_TASK diff --git a/src/beman/execution/CMakeLists.txt b/src/beman/execution/CMakeLists.txt index 0a984af6..8c24d76e 100644 --- a/src/beman/execution/CMakeLists.txt +++ b/src/beman/execution/CMakeLists.txt @@ -16,6 +16,7 @@ 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/execution/task.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution26/execution.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution26/stop_token.hpp PUBLIC @@ -25,7 +26,6 @@ target_sources( FILES ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/affine_on.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/allocator_aware_move.hpp - ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/allocator_of.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/almost_scheduler.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/apply_sender.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/as_awaitable.hpp @@ -47,7 +47,6 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/child_type.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/class_type.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/common.hpp - ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/completion.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/completion_domain.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/completion_signature.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/completion_signatures.hpp @@ -78,9 +77,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/env_of_t.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/env_promise.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/env_type.hpp - ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/error_types_of.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/error_types_of_t.hpp - ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/find_allocator.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/forward_like.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/forwarding_query.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/fwd_env.hpp @@ -98,7 +95,6 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/get_forward_progress_guarantee.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/get_scheduler.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/get_stop_token.hpp - ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/handle.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/has_as_awaitable.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/has_completions.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/immovable.hpp @@ -115,7 +111,6 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/join_env.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/just.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/let.hpp - ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/logger.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/make_env.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/make_sender.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/matching_sig.hpp @@ -140,8 +135,6 @@ 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/poly.hpp - ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/promise_env.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 @@ -180,11 +173,9 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/spawn_get_allocator.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/start.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/starts_on.hpp - ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/state_rep.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/state_type.hpp ${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_source_of.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 @@ -192,13 +183,26 @@ target_sources( ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/stopped_as_error.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/stopped_as_optional.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/store_receiver.hpp - ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/sub_visit.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/suppress_pop.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/suppress_push.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/suspend_complete.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/sync_wait.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/sync_wait_with_variant.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/tag_of_t.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/allocator_of.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/allocator_support.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/completion.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/error_types_of.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/find_allocator.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/handle.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/infallible_scheduler.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/logger.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/poly.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/promise_env.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/state_rep.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/stop_source.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/sub_visit.hpp + ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/task/task_scheduler.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/then.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/transform_sender.hpp ${PROJECT_SOURCE_DIR}/include/beman/execution/detail/type_list.hpp diff --git a/tests/beman/execution/CMakeLists.txt b/tests/beman/execution/CMakeLists.txt index 08530378..d887c6a7 100644 --- a/tests/beman/execution/CMakeLists.txt +++ b/tests/beman/execution/CMakeLists.txt @@ -10,6 +10,9 @@ list(APPEND unsupported_execution_tests exec-split.test) list( APPEND execution_tests allocator-requirements-general.test + task-allocator_support.test + task-infallible_scheduler.test + task_scheduler.test exec-affine-on.test exec-associate.test exec-awaitable.test diff --git a/tests/beman/execution/task-allocator_support.test.cpp b/tests/beman/execution/task-allocator_support.test.cpp new file mode 100644 index 00000000..5ce33d4f --- /dev/null +++ b/tests/beman/execution/task-allocator_support.test.cpp @@ -0,0 +1,133 @@ +// tests/beman/execution/task-allocator_support.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable : 4291) +#endif + +// ---------------------------------------------------------------------------- + +namespace { +struct test_resource : std::pmr::memory_resource { + std::size_t outstanding{}; + + auto do_allocate(std::size_t size, std::size_t) -> void* override { + this->outstanding += size; + return ::operator new(size); + } + auto do_deallocate(void* ptr, std::size_t size, std::size_t) -> void override { + ::operator delete(ptr); + this->outstanding -= size; + } + auto do_is_equal(const std::pmr::memory_resource& other) const noexcept -> bool override { + auto* tr{dynamic_cast(&other)}; + return tr == this; + } +}; + +struct some_data { + double data{}; +}; + +struct allocation_counter { + std::size_t outstanding{}; +}; + +template +struct tracking_allocator { + using value_type = T; + + allocation_counter* counter{}; + + explicit tracking_allocator(allocation_counter& c) noexcept : counter(&c) {} + + template + tracking_allocator(const tracking_allocator& other) noexcept : counter(other.counter) {} + + auto allocate(std::size_t count) -> T* { + this->counter->outstanding += count * sizeof(T); + return static_cast(::operator new(count * sizeof(T), std::align_val_t(alignof(T)))); + } + + auto deallocate(T* ptr, std::size_t count) noexcept -> void { + ::operator delete(ptr, std::align_val_t(alignof(T))); + this->counter->outstanding -= count * sizeof(T); + } + + template + auto operator==(const tracking_allocator& other) const noexcept -> bool { + return this->counter == other.counter; + } +}; + +template +struct allocator_aware : some_data, test_detail::allocator_support { + allocator_aware() : some_data() {} +}; + +template +concept supports_misplaced_allocator = requires(test_resource& resource) { + T::operator new(sizeof(T), 0, 1, std::allocator_arg, std::pmr::polymorphic_allocator{&resource}); +}; + +template +concept supports_resource_pointer_allocator = + requires(test_resource& resource) { T::operator new(sizeof(T), std::allocator_arg, &resource); }; + +template +concept constructs_pmr_byte_allocator = requires(Alloc& alloc) { std::pmr::polymorphic_allocator{alloc}; }; +} // namespace + +// ---------------------------------------------------------------------------- + +TEST(allocator_support) { + using type = allocator_aware>; + static_assert(requires { type::operator new(sizeof(type)); }); + static_assert(requires(test_resource& resource) { + type::operator new(sizeof(type), std::allocator_arg, std::pmr::polymorphic_allocator{&resource}); + }); + static_assert(requires(const some_data& object, test_resource& resource) { + type::operator new( + sizeof(type), object, std::allocator_arg, std::pmr::polymorphic_allocator{&resource}); + }); + static_assert(requires(allocation_counter& counter) { + type::operator new(sizeof(type), std::allocator_arg, tracking_allocator{counter}); + }); + static_assert(not constructs_pmr_byte_allocator>); + static_assert(not supports_misplaced_allocator); + static_assert(not supports_resource_pointer_allocator); + + [[maybe_unused]] std::unique_ptr unused(new type{}); + + test_resource resource{}; + std::pmr::polymorphic_allocator allocator{&resource}; + ASSERT(resource.outstanding == 0u); + type* ptr{new (std::allocator_arg, allocator) type{}}; + ASSERT(resource.outstanding != 0u); + ptr->~type(); + ASSERT(resource.outstanding != 0u); +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wmismatched-new-delete" +#endif + type::operator delete(ptr, sizeof(type), std::allocator_arg, allocator); + ASSERT(resource.outstanding == 0u); + + some_data object{}; + void* raw{type::operator new(sizeof(type), object, std::allocator_arg, allocator)}; + ASSERT(resource.outstanding != 0u); + type::operator delete(raw, sizeof(type), object, std::allocator_arg, allocator); + ASSERT(resource.outstanding == 0u); + + allocation_counter counter{}; + void* custom_raw{type::operator new(sizeof(type), std::allocator_arg, tracking_allocator{counter})}; + ASSERT(counter.outstanding != 0u); + type::operator delete(custom_raw, sizeof(type), std::allocator_arg, tracking_allocator{counter}); + ASSERT(counter.outstanding == 0u); +} diff --git a/tests/beman/execution/task-infallible_scheduler.test.cpp b/tests/beman/execution/task-infallible_scheduler.test.cpp new file mode 100644 index 00000000..9a88cbe4 --- /dev/null +++ b/tests/beman/execution/task-infallible_scheduler.test.cpp @@ -0,0 +1,70 @@ +// tests/beman/execution/task-infallible_scheduler.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution; +#else +#include +#endif +#include + +// ---------------------------------------------------------------------------- + +namespace { +template +struct sender { + using sender_concept = test_std::sender_tag; + using completion_signatures = test_std::completion_signatures; + + struct env { + auto query(const test_std::get_completion_scheduler_t&) const noexcept -> Scheduler { + return {}; + } + }; + + template + static consteval auto get_completion_signatures() noexcept -> completion_signatures { + return {}; + } + static constexpr auto get_env() noexcept -> env { return {}; } +}; + +template +struct scheduler { + using scheduler_concept = test_std::scheduler_tag; + + auto schedule() const noexcept -> sender { return {}; } + auto operator==(const scheduler&) const -> bool = default; +}; + +struct non_scheduler {}; + +struct stoppable_env { + auto query(const test_std::get_stop_token_t&) const noexcept -> test_std::inplace_stop_token { return {}; } +}; +} // namespace + +// ---------------------------------------------------------------------------- + +TEST(task_infallible_scheduler) { + using value_scheduler = scheduler; + using stoppable_scheduler = scheduler; + using reversed_scheduler = scheduler; + using error_scheduler = scheduler; + using default_unstoppable_env = test_std::env<>; + + static_assert(test_detail::completes_with); + static_assert(test_detail::infallible_scheduler); + static_assert(!test_detail::infallible_scheduler); + static_assert(test_detail::infallible_scheduler); + static_assert(test_detail::infallible_scheduler); + static_assert(!test_detail::infallible_scheduler); + static_assert(!test_detail::infallible_scheduler); +} diff --git a/tests/beman/execution/task_scheduler.test.cpp b/tests/beman/execution/task_scheduler.test.cpp new file mode 100644 index 00000000..4dc61c8f --- /dev/null +++ b/tests/beman/execution/task_scheduler.test.cpp @@ -0,0 +1,299 @@ +// tests/beman/execution/task_scheduler.test.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#ifdef BEMAN_HAS_IMPORT_STD +import std; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif +#ifdef BEMAN_HAS_MODULES +import beman.execution; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif +#include + +// ---------------------------------------------------------------------------- + +namespace { +void unexpected_call_assert(const char* msg) { ASSERT(nullptr == msg); } + +struct thread_context { + enum class complete : char { success, never }; + struct base { + base* next{}; + base() = default; + base(base&&) = delete; + base(const base&) = delete; + virtual ~base() = default; + auto operator=(base&&) = delete; + auto operator=(const base&) = delete; + virtual void complete() = 0; + }; + + ::std::mutex mutex; + ::std::condition_variable condition; + bool done{false}; + base* work{}; + ::std::thread thread; + + auto get_work() -> base* { + ::std::unique_lock guard(this->mutex); + condition.wait(guard, [this] { return this->done || this->work; }); + base* rc{this->work}; + if (rc) { + this->work = rc->next; + } + return rc; + } + + auto enqueue(base* w) -> void { + { + ::std::lock_guard guard(this->mutex); + w->next = this->work; + this->work = w; + } + this->condition.notify_one(); + } + + thread_context() + : thread([this] { + while (auto w{this->get_work()}) { + w->complete(); + } + }) {} + thread_context(thread_context&&) = delete; + thread_context(const thread_context&) = delete; + ~thread_context() { + this->stop(); + this->thread.join(); + } + auto operator=(thread_context&&) -> thread_context& = delete; + auto operator=(const thread_context&) -> thread_context& = delete; + + struct scheduler { + using scheduler_concept = test_std::scheduler_tag; + thread_context* context; + complete cmpl{complete::success}; + auto operator==(const scheduler&) const -> bool = default; + + template + struct state : base { + struct stopper { + state* st; + auto operator()() noexcept -> void { + auto self{this->st}; + self->callback.reset(); + test_std::set_stopped(::std::move(self->receiver)); + } + }; + using operation_state_concept = test_std::operation_state_tag; + using token_t = decltype(test_std::get_stop_token(test_std::get_env(::std::declval()))); + using callback_t = test_std::stop_callback_for_t; + + thread_context* ctxt; + ::std::remove_cvref_t receiver; + thread_context::complete cmpl; + ::std::optional callback; + + template + state(auto c, R&& r, thread_context::complete cm) : ctxt(c), receiver(::std::forward(r)), cmpl(cm) {} + auto start() & noexcept -> void { + callback.emplace(test_std::get_stop_token(test_std::get_env(this->receiver)), stopper{this}); + if (cmpl != thread_context::complete::never) { + this->ctxt->enqueue(this); + } + } + void complete() override { + this->callback.reset(); + test_std::set_value(::std::move(this->receiver)); + } + }; + + struct env { + thread_context* ctxt; + auto query(const test_std::get_completion_scheduler_t&) const noexcept + -> scheduler { + return scheduler{ctxt}; + } + }; + + struct sender { + using sender_concept = test_std::sender_tag; + using completion_signatures = test_std::completion_signatures; + + template + static consteval auto get_completion_signatures() -> completion_signatures { + return {}; + } + + thread_context* ctxt; + thread_context::complete cmpl; + + template + auto connect(Receiver&& receiver) -> state { + static_assert(test_std::operation_state>); + return state(this->ctxt, ::std::forward(receiver), this->cmpl); + } + auto get_env() const noexcept -> env { return {this->ctxt}; } + }; + static_assert(test_std::sender); + + auto schedule() noexcept -> sender { return sender{this->context, this->cmpl}; } + }; + static_assert(test_std::scheduler); + + auto get_scheduler(complete cmpl = complete::success) -> scheduler { return scheduler{this, cmpl}; } + + auto stop() -> void { + { + ::std::lock_guard guard(this->mutex); + this->done = true; + } + this->condition.notify_one(); + } +}; +static_assert(test_detail::infallible_scheduler>); + +enum class stop_result : char { none, success, failure, stopped }; + +template +struct stop_env { + Token token; + auto query(test_std::get_stop_token_t) const noexcept { return this->token; } +}; +template +stop_env(Token&&) -> stop_env<::std::remove_cvref_t>; + +template +struct stop_receiver { + using receiver_concept = test_std::receiver_tag; + Token token; + stop_result& result; + ::std::latch* completed{}; + + auto get_env() const noexcept { return stop_env{this->token}; } + + auto set_value(auto&&...) && noexcept -> void { + this->result = stop_result::success; + if (this->completed) { + this->completed->count_down(); + } + } + auto set_error(auto&&) && noexcept -> void { + this->result = stop_result::failure; + if (this->completed) { + this->completed->count_down(); + } + } + auto set_stopped() && noexcept -> void { + this->result = stop_result::stopped; + if (this->completed) { + this->completed->count_down(); + } + } +}; +template +stop_receiver(Token&&, stop_result&, ::std::latch* = nullptr) -> stop_receiver<::std::remove_cvref_t>; +static_assert(test_std::receiver>); +} // namespace + +// ---------------------------------------------------------------------------- + +TEST(task_scheduler) { + try { + static_assert(test_std::scheduler); + + thread_context ctxt1; + thread_context ctxt2; + + ASSERT(ctxt1.get_scheduler() == ctxt1.get_scheduler()); + ASSERT(ctxt2.get_scheduler() == ctxt2.get_scheduler()); + ASSERT(ctxt1.get_scheduler() != ctxt2.get_scheduler()); + + test_detail::task_scheduler sched1(ctxt1.get_scheduler()); + test_detail::task_scheduler sched2(ctxt2.get_scheduler()); + ASSERT(sched1 == sched1); + ASSERT(sched2 == sched2); + ASSERT(sched1 != sched2); + + test_detail::task_scheduler copy(sched1); + ASSERT(copy == sched1); + ASSERT(copy != sched2); + test_detail::task_scheduler move(::std::move(copy)); + ASSERT(move == sched1); + ASSERT(move != sched2); + + copy = sched2; + ASSERT(copy == sched2); + ASSERT(copy != sched1); + + move = ::std::move(copy); + ASSERT(move == sched2); + ASSERT(move != sched1); + + ::std::atomic<::std::thread::id> id1{}; + ::std::atomic<::std::thread::id> id2{}; + test_std::sync_wait(test_std::schedule(sched1) | + test_std::then([&id1] { id1 = ::std::this_thread::get_id(); })); + test_std::sync_wait(test_std::schedule(sched2) | + test_std::then([&id2] { id2 = ::std::this_thread::get_id(); })); + ASSERT(id1 != id2); + test_std::sync_wait(test_std::schedule(test_detail::task_scheduler(sched1)) | + test_std::then([&id1] { ASSERT(id1 == ::std::this_thread::get_id()); })); + test_std::sync_wait(test_std::schedule(test_detail::task_scheduler(sched2)) | + test_std::then([&id2] { ASSERT(id2 == ::std::this_thread::get_id()); })); + + { + test_std::inplace_stop_source source; + stop_result result{stop_result::none}; + auto state{test_std::connect(test_std::schedule(ctxt1.get_scheduler(thread_context::complete::never)), + stop_receiver{source.get_token(), result})}; + ASSERT(result == stop_result::none); + test_std::start(state); + ASSERT(result == stop_result::none); + source.request_stop(); + ASSERT(result == stop_result::stopped); + } + { + ::std::latch completed{1}; + stop_result result{stop_result::none}; + auto state{test_std::connect(test_std::schedule(test_detail::task_scheduler( + ctxt1.get_scheduler(thread_context::complete::success))), + stop_receiver{test_std::never_stop_token(), result, &completed})}; + ASSERT(result == stop_result::none); + test_std::start(state); + completed.wait(); + ASSERT(result == stop_result::success); + } + } catch (...) { + unexpected_call_assert("no exception should escape to main"); + } +}