diff --git a/docs/create-a-sender.md b/docs/create-a-sender.md new file mode 100644 index 00000000..a3889721 --- /dev/null +++ b/docs/create-a-sender.md @@ -0,0 +1,423 @@ +# Create A Sender + +This page describes how to create senders. The examples used aren't +necessarily super useful but are intended to describe the different +aspects of creating a sender. + +## Asynchronous Stack + +The first example is an asynchronous stack: popping from the stack +is an asynchronous operation which either completes immediately if +there is work or completes when new work becomes available. Pushing +to the asynchronous stack succeeds immediately. It could be an +extension to impose a limit on the stack size and make pushing also +an asynchronous operation, making the two operations somewhat symmetric. + +The interface to the asynchronous stack could look like this: + +```c++ +template +class asynchronous_stack { + std::stack stack; + // TODO +public: + void push(T value); + pop_sender pop(); +}; +``` + +This immediately raises the question what is `pop_sender`? Well, +it is a sender, i.e., `ex::sender::pop_sender>` +is `true` which completes successfully with a `T` value: + +```c++ +template +class asynchronous_stack { +public: + struct pop_sender { + using sender_concept = ex::sender_tag; + template + static consteval auto get_completion_signatures() { + return ex::completion_signatures{}; + } + // TODO + }; + + void push(T); + pop_sender pop() { return pop_sender{/* TODO */}; } +}; + +static_assert(ex::sender::pop_sender>); +static_assert(ex::sender_in::pop_sender>); +``` + +The declaration of `sender_concept` indicates to the `ex::execution` +library that `pop_sender` actually implements (models) the `concept` +`ex::sender`. That is required for various interfaces taking senders +as arguments. + +The function `get_completion_signatures()` is a compile-time function +which provides access to the possible completions of the sender +`pop_sender`. The current declaration states that `pop_sender` +always completes successfully with a value of type `T`. For example, +this sender could be used in a work graph where the result is sent to +a function: + +```c++ +asynchronous_stack st; +auto sender = st.pop() | ex::then([](int v) noexcept { std::cout << "got value=" << v << "\n"; }); +``` + +Of course, `sender` doesn't actually do anything, yet. The above +code is just showing how to create a work graph using this sender. +To actually make it do some work we'll need to implement its logic. +The logic for `pop_sender` will somehow need to look at an +`asynchronous_stack` to either extract a value if one is present +or to register interest in values becoming available. In both cases +it needs a reference to the `asynchronous_stack`: + +```c++ +template +class asynchronous_stack { +public: + struct pop_sender { + using sender_concept = ex::sender_tag; + template + static consteval auto get_completion_signatures() { + return ex::completion_signatures{}; + } + + asynchronous_stack& self; + // TODO + }; + + void push(T); + pop_sender pop() { return pop_sender{*this}; } +}; +``` + +The `pop_sender` itself actually doesn't really do any work: it +wouldn't even know where to send any results to. Instead, it is the +interface used to `ex::connect` to a receiver which is provides a +way to notify the completion. When `ex::connect(sndr, rcvr)` is +invoked (and there are no customizations) it behaves as if +`sndr.connect(rcvr)` was invoked. Thus, `pop_sender` should have a +`connect` member function taking a receiver. The result of invoking +this function is an operation state: + +```c++ +template +class asynchronous_stack { + template + struct state { + using operation_state_concept = ex::operation_state_tag; + /* TODO */ + void start() & noexcept { /* TODO */ } + }; +public: + struct pop_sender { + // ... + + asynchronous_stack& self; + template + auto connect(Rcvr&& rcvr) const { + static_assert(ex::operation_state>); + return state{/* TODO */}; + } + }; + + // ... +}; +``` + +The `state` is identified as an `ex::operation_state` similarly to +a sender: by declaring `operation_state_concept` as an alias for +`ex::operation_state_tag` (or a class derived thereof) it is +identified as an `ex::operation_state`. To implement the `concept` +`ex::operation_state` the `state` class also needs a `start()` +function which never throws (any errors would be reported via +suitable completion signature on the receiver). + +The actual work happens when `state`s `start()` function is +invoked: at that point it is checked whether there is any value +already available in the `asynchronous_stack` and, if that is +the case, it is passed to the receiver's `set_value` function. +Thus, the `state` will need a reference to the `asynchronous_stack` +and the receiver: + +```c++ +template +class asynchronous_stack { + template + struct state { + using operation_state_concept = ex::operation_state_tag; + std::remove_cvref_t rcvr; + asynchronous_stack& self; + void start() & noexcept { /* TODO */ } + }; +public: + struct pop_sender { + // ... + + asynchronous_stack& self; + template + auto connect(Rcvr&& rcvr) const { + return state{std::forward(rcvr), self}; + } + }; + + // ... +}; +``` + +With that we have created enough of the scaffolding to actually give +sender an initial try: for a test we can just use `asynchronous_stack` +and upon `start()`ing the operation state we could just complete with a +known value, e.g.: + +```c++ +template +class asynchronous_stack { + template + struct state { + using operation_state_concept = ex::operation_state_tag; + std::remove_cvref_t rcvr; + asynchronous_stack& self; + void start() & noexcept { ex::set_value(std::move(rcvr), 17); } + }; + // ... +}; + +int main() { + asynchronous_stack st; + auto sender = st.pop() | ex::then([](int v) noexcept { std::cout << "got value=" << v << "\n"; }); + ex::sync_wait(std::move(sender)); +} +``` + +Running the resulting executable (on my Mac that is +`./build/simple-Darwin/stagedir/bin/beman_execution.example.tutorial-create-a-sender`) +results in printing that it got value 17: + +``` +got value=17 +``` + +So, we got something running but it isn't quite the correct logic. +It is easy to implement the necessary logic when there are already +values in the stack: for that the `push(T)` function just adds +values to a `std::stack` and `state::start` checks whether there +is a value and produces a corresponding result if that is the case: + +```c++ +template +class asynchronous_stack { + std::stack stack; + + template + struct state { + // ... + void start() & noexcept { + if (not this->self.stack.empty()) { + T value(std::move(this->self.stack.top())); + this->self.stack.pop(); + ex::set_value(std::move(rcvr), std::move(value)); + } + // TODO + } + }; + void push(T value) { this->stack.push(std::move(value)); } + // ... +}; + +int main() { + asynchronous_stack st; + auto sender = st.pop() | ex::then([](int v) noexcept { std::cout << "got value=" << v << "\n"; }); + + for (int value{1}; value < 4; ++ value) { + st.push(value); + } + + for (int value{1}; value < 4; ++ value) { + ex::sync_wait(sender); + } +} +``` + +The `push` function unconditionally pushes elements into the `stack` +and the `start` function doesn't do anything if there are no values. +If the `stack` is `empty`, the `start` function should register the +operation state with the `asynchronous_stack` and `push` should +verify if there is a registered operation state. Since the operation +state objects live at least until they complete they can be used +for an intrusive list: there is no need to do a separate allocation. +The list is built by deriving from a `struct node` which simply has +a `next` pointer to another `node` and a `virtual` function `complete` +taking a `T` object as argument. The `asynchronous_stack` keeps a +list of `node`s named `awaiting` in the example. This example doesn't +to be fair: the most recently started `pop_sender` is the first one +to complete. Since the `node` becomes a base class of `state`, it +is easiest to give `state` a constructor: + +```c++ +template +class asynchronous_stack { + struct node { + node* next{}; + virtual void complete(T) = 0; + }; + std::stack stack; + node* awaiting{}; + + template + struct state: node { + // ... + state(Rcvr&& r, asynchronous_stack& s): rcvr(std::forward(r)), self(s) {} + void start() & noexcept { + if (not this->self.stack.empty()) { + T value(std::move(this->self.stack.top())); + this->self.stack.pop(); + ex::set_value(std::move(rcvr), std::move(value)); + } + else { + this->next = std::exchange(this->self.awaiting, this); + } + } + void complete(T value) override { + ex::set_value(std::move(rcvr), std::move(value)); + } + }; + void push(T value) { this->stack.push(std::move(value)); } + // ... +}; +``` + +The new logic added is in `start` which now adds itself to the list +of `awaiting` operation state by replacing `awaiting` with itself +an setting its `next` pointer to the previous head of the list. To +take advantage of already `awaiting` operation states the `push` +function needs to check if there is any `node` and, if so, extract +this node `n` and invoke `n->complete(std::move(value))`: + +```c++ +template +class asynchronous_stack { + // ... + void push(T value) { + if (this->awaiting) { + std::exchange(this->awaiting, this->awaiting->next)->complete(std::move(value)); + } + else { + this->stack.push(std::move(value)); + } + } +}; +``` + +To actually demonstrate the functionality it becomes necessary to +`start` some `pop_sender`s before adding more work. To do so, the +`main` function uses a `ex::counting_scope`: invoking `ex::spawn(work, +scope.get_token())` results in `connect`ing `work` to a receiver +and `start`ing the result operation state. So, work is `push`ed in +two groups of three items. After the first three items are `push`ed +six `pop()` requests are `spawn`ed. The first three of them will +complete immediately. The second three will complete once more +work gets `push`ed: + +```c++ +int main() { + ex::counting_scope scope; + asynchronous_stack st; + auto sender = st.pop() | ex::then([](int v) noexcept { std::cout << "got value=" << v << "\n"; }); + + for (int value{1}; value < 4; ++ value) { + st.push(value); + } + std::cout << "pushed 1,2,3\n"; + + for (int value{1}; value < 7; ++ value) { + ex::spawn(st.pop() | ex::then([value](int v) noexcept { + std::cout << "got value=" << v << " for request " << value << "\n"; + }), scope.get_token()); + } + + std::cout << "requested 6 values\n"; + + for (int value{4}; value < 7; ++ value) { + st.push(value); + } + std::cout << "pushed 4,5,6\n"; + + ex::sync_wait(scope.join()); +} +``` + +The `counting_scope` needs to be `join`ed before it can be destroyed. +`scope.join()` returns a sender which completes once all held work +completes. The example is carefully setup to contain no outstanding +work when synchronously awaiting `scope.join()`. However, if more +`pop()` requests get `spawn`ed into the `scope` than there are +elements which are `push`ed, the operation will never complete! To +deal with that situation it is possibly to `scope.request_stop()` +before awaiting completion of `scope.join()`. The `scope.request_stop()` +will stop all outstanding work - assuming it can be stopped. But +`pop_sender` has no support, yet, for getting stopped. + +The way to support stop requests is to set up a stop callback using +the stop token associated with a receiver `r`. To get the stop token +`ex::get_stop_token(ex::get_env(r))` can be used. Such a stop token +support a callback which can then be used to complete with +`set_stopped`: + +```c++ +template +class asynchronous_stack { + // ... + template + struct state: node { + // ... + struct stop_fun { + state& st; + void operator()() noexcept { + this->st.callback.reset(); + for (auto it{&this->st.stack.awaiting}; it; it = &(*it)->next) { + if (*it == &this->st) { + *it = this->st.next; + break; + } + } + ex::set_stopped(std::move(st.rcvr)); + } + }; + using stop_token_t = ex::stop_token_of_t()))>; + using callback_t = ex::stop_callback_for_t; + std::optional callback; + state(Rcvr&& r, asynchronous_stack& s): rcvr(std::forward(r)), self(s) {} + void start() & noexcept { + if (not this->self.stack.empty()) { + T value(std::move(this->self.stack.top())); + this->self.stack.pop(); + ex::set_value(std::move(rcvr), std::move(value)); + } + else { + this->next = std::exchange(this->self.awaiting, this); + this->callback.emplace(ex::get_stop_token(ex::get_env(this->rcvr)), stop_fun{*this}); + } + } + void complete(T value) override { + this->callback.reset(); + ex::set_value(std::move(rcvr), std::move(value)); + } + }; + struct pop_sender { + using sender_concept = ex::sender_tag; + template + static consteval auto get_completion_signatures() { + return ex::completion_signatures{}; + } + // ... + }; + void push(T value) { this->stack.push(std::move(value)); } + // ... +}; +``` diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index aba158af..c3c9c243 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -21,6 +21,7 @@ set(TODO stop_token) #-dk:TODO including that causes a linker error set(TODO suspend_never) #-dk:TODO including that causes ASAN errors set(EXAMPLES + tutorial-create-a-sender allocator doc-just doc-just_error diff --git a/examples/tutorial-create-a-sender.cpp b/examples/tutorial-create-a-sender.cpp new file mode 100644 index 00000000..bfadb9f8 --- /dev/null +++ b/examples/tutorial-create-a-sender.cpp @@ -0,0 +1,124 @@ +// examples/tutorial/create-a-sender.cpp -*-C++-*- +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#ifdef BEMAN_HAS_MODULES +import beman.execution; +#else +#include +#endif + +namespace ex = beman::execution; + +namespace { +template +class asynchronous_stack { + struct node { + node* next{}; + virtual void complete(T) = 0; + }; + std::stack stack; + node* awaiting{}; + + template + struct state : node { + using operation_state_concept = ex::operation_state_tag; + struct stop_fun { + state& st; + void operator()() noexcept { + state& s = this->st; + this->st.callback.reset(); + for (auto it{&this->st.self.awaiting}; it; it = &(*it)->next) { + if (*it == &this->st) { + *it = this->st.next; + break; + } + } + ex::set_stopped(std::move(s.rcvr)); + } + }; + using stop_token_t = ex::stop_token_of_t()))>; + using callback_t = ex::stop_callback_for_t; + std::remove_cvref_t rcvr; + asynchronous_stack& self; + std::optional callback; + state(Rcvr&& r, asynchronous_stack& s) : rcvr(std::forward(r)), self(s) {} + void start() & noexcept { + if (not this->self.stack.empty()) { + T value(std::move(this->self.stack.top())); + this->self.stack.pop(); + ex::set_value(std::move(rcvr), std::move(value)); + } else { + this->next = std::exchange(this->self.awaiting, this); + this->callback.emplace(ex::get_stop_token(ex::get_env(this->rcvr)), stop_fun{*this}); + } + } + void complete(T value) override { + this->callback.reset(); + ex::set_value(std::move(rcvr), std::move(value)); + } + }; + + public: + struct pop_sender { + using sender_concept = ex::sender_tag; + template + static consteval auto get_completion_signatures() { + return ex::completion_signatures{}; + } + + asynchronous_stack& self; + template + auto connect(Rcvr&& rcvr) const { + static_assert(ex::operation_state>); + return state{std::forward(rcvr), self}; + } + }; + + void push(T value) { + if (this->awaiting) { + std::exchange(this->awaiting, this->awaiting->next)->complete(std::move(value)); + } else { + this->stack.push(std::move(value)); + } + } + pop_sender pop() { return pop_sender{*this}; } +}; + +static_assert(ex::sender::pop_sender>); +static_assert(ex::sender_in::pop_sender>); +} // namespace +// ---------------------------------------------------------------------------- + +int main() { + ex::counting_scope scope; + asynchronous_stack st; + [[maybe_unused]] auto sender = st.pop() | ex::then([](int v) { std::cout << "got value=" << v << "\n"; }); + + for (int value{1}; value < 4; ++value) { + st.push(value); + } + std::cout << "pushed 1,2,3\n"; + + int count{8}; + for (int value{1}; value < count; ++value) { + ex::spawn(st.pop() | ex::then([value](int v) noexcept { + std::cout << "got value=" << v << " for request " << value << "\n"; + }) | ex::upon_stopped([value] noexcept { std::cout << "request " << value << " was stopped\n"; }), + scope.get_token()); + } + + std::cout << "requested " << (count - 1) << " values\n"; + + for (int value{4}; value < 7; ++value) { + st.push(value); + } + std::cout << "pushed 4,5,6\n"; + + scope.request_stop(); + std::cout << "requested stop\n"; + ex::sync_wait(scope.join()); + std::cout << "joined\n"; +} diff --git a/include/beman/execution/detail/call_result_t.hpp b/include/beman/execution/detail/call_result_t.hpp index 19aaa2a6..6374bbf3 100644 --- a/include/beman/execution/detail/call_result_t.hpp +++ b/include/beman/execution/detail/call_result_t.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/call_result_t.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_CALL_RESULT -#define INCLUDED_BEMAN_EXECUTION_DETAIL_CALL_RESULT +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_CALL_RESULT_T +#define INCLUDED_BEMAN_EXECUTION_DETAIL_CALL_RESULT_T #include #ifdef BEMAN_HAS_IMPORT_STD @@ -25,4 +25,4 @@ using call_result_t = decltype(::std::declval()(std::declval()...)); // ---------------------------------------------------------------------------- -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_CALL_RESULT +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_CALL_RESULT_T diff --git a/include/beman/execution/detail/completion_signatures_of_t.hpp b/include/beman/execution/detail/completion_signatures_of_t.hpp index 972cf9f5..736abcea 100644 --- a/include/beman/execution/detail/completion_signatures_of_t.hpp +++ b/include/beman/execution/detail/completion_signatures_of_t.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/completion_signaturess_of_t.hpp -*-C++-*- +// include/beman/execution/detail/completion_signatures_of_t.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION_SIGNATURES_OF -#define INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION_SIGNATURES_OF +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION_SIGNATURES_OF_T +#define INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION_SIGNATURES_OF_T #include #ifdef BEMAN_HAS_MODULES @@ -31,4 +31,4 @@ using completion_signatures_of_t = decltype(::beman::execution::get_completion_s // ---------------------------------------------------------------------------- -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION_SIGNATURES_OF +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_COMPLETION_SIGNATURES_OF_T diff --git a/include/beman/execution/detail/connect_result_t.hpp b/include/beman/execution/detail/connect_result_t.hpp index d01cb118..87a095a5 100644 --- a/include/beman/execution/detail/connect_result_t.hpp +++ b/include/beman/execution/detail/connect_result_t.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/connect_result_t.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_CONNECT_RESULT -#define INCLUDED_BEMAN_EXECUTION_DETAIL_CONNECT_RESULT +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_CONNECT_RESULT_T +#define INCLUDED_BEMAN_EXECUTION_DETAIL_CONNECT_RESULT_T #include #ifdef BEMAN_HAS_IMPORT_STD @@ -29,4 +29,4 @@ using connect_result_t = decltype(::beman::execution::connect(::std::declval #ifdef BEMAN_HAS_IMPORT_STD @@ -29,4 +29,4 @@ using env_of_t = decltype(::beman::execution::get_env(::std::declval())); // ---------------------------------------------------------------------------- -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_ENV_OF +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_ENV_OF_T diff --git a/include/beman/execution/detail/error_types_of.hpp b/include/beman/execution/detail/error_types_of.hpp index ea130048..a1304add 100644 --- a/include/beman/execution/detail/error_types_of.hpp +++ b/include/beman/execution/detail/error_types_of.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/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_ERROR_TYPES_OF +#define INCLUDED_BEMAN_EXECUTION_DETAIL_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_ERROR_TYPES_OF diff --git a/include/beman/execution/detail/error_types_of_t.hpp b/include/beman/execution/detail/error_types_of_t.hpp index 5c594320..6da1d0d1 100644 --- a/include/beman/execution/detail/error_types_of_t.hpp +++ b/include/beman/execution/detail/error_types_of_t.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/error_types_of_t.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_ERROR_TYPES_OF -#define INCLUDED_BEMAN_EXECUTION_DETAIL_ERROR_TYPES_OF +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_ERROR_TYPES_OF_T +#define INCLUDED_BEMAN_EXECUTION_DETAIL_ERROR_TYPES_OF_T #include #ifdef BEMAN_HAS_IMPORT_STD @@ -46,4 +46,4 @@ using error_types_of_t = // ---------------------------------------------------------------------------- -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_ERROR_TYPES_OF +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_ERROR_TYPES_OF_T diff --git a/include/beman/execution/detail/get_completion_domain.hpp b/include/beman/execution/detail/get_completion_domain.hpp index a150ba98..4e5cf813 100644 --- a/include/beman/execution/detail/get_completion_domain.hpp +++ b/include/beman/execution/detail/get_completion_domain.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/get_completion_domain.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_GET_COMPLETION_DOMAIN -#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_GET_COMPLETION_DOMAIN +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_GET_COMPLETION_DOMAIN +#define INCLUDED_BEMAN_EXECUTION_DETAIL_GET_COMPLETION_DOMAIN #include #ifdef BEMAN_HAS_IMPORT_STD diff --git a/include/beman/execution/detail/hide_sched.hpp b/include/beman/execution/detail/hide_sched.hpp index 76f38110..e1c6bce9 100644 --- a/include/beman/execution/detail/hide_sched.hpp +++ b/include/beman/execution/detail/hide_sched.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/hide_sched.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_HIDE_SCHED -#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_HIDE_SCHED +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_HIDE_SCHED +#define INCLUDED_BEMAN_EXECUTION_DETAIL_HIDE_SCHED #include #ifdef BEMAN_HAS_IMPORT_STD @@ -48,4 +48,4 @@ auto hide_sched(const Q& q) noexcept { // ---------------------------------------------------------------------------- -#endif // INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_HIDE_SCHED +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_HIDE_SCHED diff --git a/include/beman/execution/detail/indeterminate_domain.hpp b/include/beman/execution/detail/indeterminate_domain.hpp index 6bfde2d5..bc35ae59 100644 --- a/include/beman/execution/detail/indeterminate_domain.hpp +++ b/include/beman/execution/detail/indeterminate_domain.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/indeterminate_domain.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_INDETERMINATE_DOMAIN -#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_INDETERMINATE_DOMAIN +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_INDETERMINATE_DOMAIN +#define INCLUDED_BEMAN_EXECUTION_DETAIL_INDETERMINATE_DOMAIN #include #ifdef BEMAN_HAS_IMPORT_STD diff --git a/include/beman/execution/detail/inplace_stop_source.hpp b/include/beman/execution/detail/inplace_stop_source.hpp index 053fa64e..4bc389ce 100644 --- a/include/beman/execution/detail/inplace_stop_source.hpp +++ b/include/beman/execution/detail/inplace_stop_source.hpp @@ -152,12 +152,14 @@ inline auto beman::execution::inplace_stop_source::request_stop() -> bool { } inline auto beman::execution::inplace_stop_source::add(callback_base* cb) -> void { - if (this->stopped) { - cb->call(); - } else { + { ::std::lock_guard guard(this->lock); - cb->next = ::std::exchange(this->callbacks, cb); + if (!this->stopped) { + cb->next = ::std::exchange(this->callbacks, cb); + return; + } } + cb->call(); } inline auto beman::execution::inplace_stop_source::deregister(callback_base* cb) -> void { diff --git a/include/beman/execution/detail/intrusive_stack.hpp b/include/beman/execution/detail/intrusive_stack.hpp index cc258fb8..024932f5 100644 --- a/include/beman/execution/detail/intrusive_stack.hpp +++ b/include/beman/execution/detail/intrusive_stack.hpp @@ -1,8 +1,8 @@ -// include/beman/execution/detail/intrusive_queue.hpp -*-C++-*- +// include/beman/execution/detail/intrusive_stack.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_INTRUSIVE_QUEUE -#define INCLUDED_BEMAN_EXECUTION_DETAIL_INTRUSIVE_QUEUE +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_INTRUSIVE_STACK +#define INCLUDED_BEMAN_EXECUTION_DETAIL_INTRUSIVE_STACK #include #ifdef BEMAN_HAS_IMPORT_STD @@ -17,7 +17,7 @@ namespace beman::execution::detail { template class intrusive_stack; -//! @brief This data structure is an intrusive queue that is not thread-safe. +//! @brief This data structure is an intrusive stack that is not thread-safe. template class intrusive_stack { public: @@ -25,12 +25,12 @@ class intrusive_stack { explicit intrusive_stack(Item* head) noexcept : head_{head} {} - //! @brief Pushes an item to the queue. + //! @brief Pushes an item to the stack. auto push(Item* item) noexcept -> void { item->*Next = std::exchange(head_, item); } - //! @brief Pops one item from the queue. + //! @brief Pops one item from the stack. //! - //! @return The item that was popped from the queue, or nullptr if the queue is empty. + //! @return The item that was popped from the stack, or nullptr if the stack is empty. auto pop() noexcept -> Item* { if (head_) { auto item = head_; @@ -40,7 +40,7 @@ class intrusive_stack { return nullptr; } - //! @brief Tests if the queue is empty. + //! @brief Tests if the stack is empty. auto empty() const noexcept -> bool { return !head_; } private: @@ -49,4 +49,4 @@ class intrusive_stack { } // namespace beman::execution::detail -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_INTRUSIVE_QUEUE +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_INTRUSIVE_STACK diff --git a/include/beman/execution/detail/is_constant.hpp b/include/beman/execution/detail/is_constant.hpp index 1dad781f..8f78a947 100644 --- a/include/beman/execution/detail/is_constant.hpp +++ b/include/beman/execution/detail/is_constant.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/is_constant.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_IS_CONSTANT -#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_IS_CONSTANT +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_IS_CONSTANT +#define INCLUDED_BEMAN_EXECUTION_DETAIL_IS_CONSTANT // ---------------------------------------------------------------------------- diff --git a/include/beman/execution/detail/schedule_result_t.hpp b/include/beman/execution/detail/schedule_result_t.hpp index 846b478d..9272b519 100644 --- a/include/beman/execution/detail/schedule_result_t.hpp +++ b/include/beman/execution/detail/schedule_result_t.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/schedule_result_t.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_SCHEDULE_RESULT -#define INCLUDED_BEMAN_EXECUTION_DETAIL_SCHEDULE_RESULT +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_SCHEDULE_RESULT_T +#define INCLUDED_BEMAN_EXECUTION_DETAIL_SCHEDULE_RESULT_T #include #ifdef BEMAN_HAS_IMPORT_STD @@ -27,4 +27,4 @@ using schedule_result_t = decltype(::beman::execution::schedule(::std::declval #ifdef BEMAN_HAS_IMPORT_STD @@ -36,4 +36,4 @@ concept stoppable_callback_for = // ---------------------------------------------------------------------------- -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_STOP_CALLBACK_FOR +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_STOP_CALLBACK_FOR_T diff --git a/include/beman/execution/detail/stop_token_of_t.hpp b/include/beman/execution/detail/stop_token_of_t.hpp index 60247695..de5caf5a 100644 --- a/include/beman/execution/detail/stop_token_of_t.hpp +++ b/include/beman/execution/detail/stop_token_of_t.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/stop_token_of_t.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_STOP_TOKEN_OF -#define INCLUDED_BEMAN_EXECUTION_DETAIL_STOP_TOKEN_OF +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_STOP_TOKEN_OF_T +#define INCLUDED_BEMAN_EXECUTION_DETAIL_STOP_TOKEN_OF_T #include #ifdef BEMAN_HAS_IMPORT_STD @@ -25,4 +25,4 @@ using stop_token_of_t = ::std::remove_cvref_t #include #include #include @@ -74,13 +75,20 @@ struct beman::execution::detail::stop_when_t::sender { 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{}; + std::atomic run_stop{true}; + }; + struct cb_t { + base_state* st; + auto operator()() const noexcept { + this->st->run_stop = false; + this->st->source.request_stop(); + if (this->st->run_stop.exchange(true)) { + ::beman::execution::set_stopped(::std::move(this->st->rcvr)); + } + } }; struct env { base_state* st; @@ -103,13 +111,21 @@ struct beman::execution::detail::stop_when_t::sender { 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)...); + if (this->st->run_stop.exchange(true)) { + ::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)); + if (this->st->run_stop.exchange(true)) { + ::beman::execution::set_error(::std::move(this->st->rcvr), ::std::forward(e)); + } + } + auto set_stopped() const noexcept -> void { + if (this->st->run_stop.exchange(true)) { + ::beman::execution::set_stopped(::std::move(this->st->rcvr)); + } } - 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())); @@ -127,9 +143,9 @@ struct beman::execution::detail::stop_when_t::sender { 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->cb1.emplace(this->tok, cb_t{&this->base}); this->cb2.emplace(::beman::execution::get_stop_token(::beman::execution::get_env(this->base.rcvr)), - cb_t{this->base.source}); + cb_t{&this->base}); ::beman::execution::start(this->inner_state); } }; diff --git a/include/beman/execution/detail/store_receiver.hpp b/include/beman/execution/detail/store_receiver.hpp index d14b71cd..dd5ff8d2 100644 --- a/include/beman/execution/detail/store_receiver.hpp +++ b/include/beman/execution/detail/store_receiver.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/store_receiver.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_STORE_RECEIVER -#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_STORE_RECEIVER +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_STORE_RECEIVER +#define INCLUDED_BEMAN_EXECUTION_DETAIL_STORE_RECEIVER #include #ifdef BEMAN_HAS_IMPORT_STD diff --git a/include/beman/execution/detail/tag_of_t.hpp b/include/beman/execution/detail/tag_of_t.hpp index a368ff82..0c556cd3 100644 --- a/include/beman/execution/detail/tag_of_t.hpp +++ b/include/beman/execution/detail/tag_of_t.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/tag_of_t.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TAG_OF -#define INCLUDED_BEMAN_EXECUTION_DETAIL_TAG_OF +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TAG_OF_T +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TAG_OF_T #include #ifdef BEMAN_HAS_IMPORT_STD @@ -25,4 +25,4 @@ using tag_of_t = typename decltype(::beman::execution::detail::get_sender_meta(: // ---------------------------------------------------------------------------- -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_TAG_OF +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_TAG_OF_T diff --git a/include/beman/execution/detail/try_query.hpp b/include/beman/execution/detail/try_query.hpp index 60bff1ae..436aeb23 100644 --- a/include/beman/execution/detail/try_query.hpp +++ b/include/beman/execution/detail/try_query.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/try_query.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_TRY_QUERY -#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_TRY_QUERY +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_TRY_QUERY +#define INCLUDED_BEMAN_EXECUTION_DETAIL_TRY_QUERY #include #ifdef BEMAN_HAS_IMPORT_STD diff --git a/include/beman/execution/detail/unstoppable.hpp b/include/beman/execution/detail/unstoppable.hpp index 32b87fa6..f82ff8e8 100644 --- a/include/beman/execution/detail/unstoppable.hpp +++ b/include/beman/execution/detail/unstoppable.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/unstoppable.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE -#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE +#define INCLUDED_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE #include #ifdef BEMAN_HAS_IMPORT_STD diff --git a/include/beman/execution/detail/unstoppable_scheduler.hpp b/include/beman/execution/detail/unstoppable_scheduler.hpp index 9d4a3e37..a27fcdaf 100644 --- a/include/beman/execution/detail/unstoppable_scheduler.hpp +++ b/include/beman/execution/detail/unstoppable_scheduler.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/unstoppable_scheduler.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE_SCHEDULER -#define INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE_SCHEDULER +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE_SCHEDULER +#define INCLUDED_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE_SCHEDULER #ifdef BEMAN_HAS_IMPORT_STD import std; @@ -47,4 +47,4 @@ struct unstoppable_scheduler { // ---------------------------------------------------------------------------- -#endif // INCLUDED_INCLUDE_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE_SCHEDULER +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_UNSTOPPABLE_SCHEDULER diff --git a/include/beman/execution/detail/value_types_of_t.hpp b/include/beman/execution/detail/value_types_of_t.hpp index 95272891..0061b879 100644 --- a/include/beman/execution/detail/value_types_of_t.hpp +++ b/include/beman/execution/detail/value_types_of_t.hpp @@ -1,8 +1,8 @@ // include/beman/execution/detail/value_types_of_t.hpp -*-C++-*- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_VALUE_TYPE_OF -#define INCLUDED_BEMAN_EXECUTION_DETAIL_VALUE_TYPE_OF +#ifndef INCLUDED_BEMAN_EXECUTION_DETAIL_VALUE_TYPE_OF_T +#define INCLUDED_BEMAN_EXECUTION_DETAIL_VALUE_TYPE_OF_T #include #ifdef BEMAN_HAS_MODULES @@ -38,4 +38,4 @@ using value_types_of_t = } // ---------------------------------------------------------------------------- -#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_VALUE_TYPE_OF +#endif // INCLUDED_BEMAN_EXECUTION_DETAIL_VALUE_TYPE_OF_T diff --git a/include/beman/execution/execution.hpp b/include/beman/execution/execution.hpp index 70a5f5c5..91adb712 100644 --- a/include/beman/execution/execution.hpp +++ b/include/beman/execution/execution.hpp @@ -12,7 +12,6 @@ import beman.execution.detail.affine; import beman.execution.detail.as_except_ptr; import beman.execution.detail.associate; import beman.execution.detail.bulk; -import beman.execution.detail.execution_policy; import beman.execution.detail.completion_signature; import beman.execution.detail.completion_signatures; import beman.execution.detail.connect; diff --git a/src/beman/execution/execution.cppm b/src/beman/execution/execution.cppm index 9d0c91d7..289b695b 100644 --- a/src/beman/execution/execution.cppm +++ b/src/beman/execution/execution.cppm @@ -260,7 +260,6 @@ export using ::beman::execution::write_env; export using ::beman::execution::affine_t; export using ::beman::execution::affine; export using ::beman::execution::read_env_t; -export using ::beman::execution::read_env; export using ::beman::execution::simple_counting_scope; export using ::beman::execution::counting_scope;