Repository navigation
Implement P3149R11 and P3815R1 #1713
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+4,064
−5
Merged
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
8791891
Add stdexec::scope_association
ispeters 86e22ef
Add stdexec::scope_token
ispeters 2c65dac
Add stdexec::associate
ispeters 08d1308
Add stdexec::__stop_when
ispeters 29c0b28
Add stdexec::spawn
ispeters 401f608
Add stdexec::simple_counting_scope and stdexec::counting_scope
ispeters a63340d
Add stdexec::spawn_future
ispeters 5209ffd
Implement the missing "stop-request forwarding" behaviour
ispeters 94c4874
fixes
ericniebler 21d29e7
Fix MSVC build
ispeters 2775f7c
Conform to coding standard
ispeters 6dbc80a
Use __mapply_q
ispeters 343fc43
Prefer cascading constexpr ifs
ispeters 22fd39d
Devirtualize __spawn_state_base
ispeters fcb41d0
Devirtualize spawn_future
ispeters afd6e39
Update the completion signature computation for __spawn_future_state
ispeters 9199dcc
Remove __monostate from future's completion variant
ispeters 892f971
Fix Clang 16 build
ispeters bdb7f09
Fix MSVC build
ispeters 3303429
`__callback` is magic on msvc
ericniebler File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add stdexec::__stop_when
This diff defines `stdexec::__stop_when` as the implementation of _`stop-when`_ and adds tests to validate the algorithm. Co-authored-by: Eric Niebler <eniebler@nvidia.com>
- Loading branch information
commit 08d1308911a00ab61702529da6c398ebd74f060c
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| /* | ||
| * Copyright (c) 2025 Ian Petersen | ||
| * Copyright (c) 2025 NVIDIA Corporation | ||
| * | ||
| * Licensed under the Apache License Version 2.0 with LLVM Exceptions | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * https://llvm.org/LICENSE.txt | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| #pragma once | ||
|
|
||
| #include "__execution_fwd.hpp" | ||
|
|
||
| #include "__atomic.hpp" | ||
| #include "__basic_sender.hpp" | ||
| #include "__concepts.hpp" | ||
| #include "__env.hpp" | ||
| #include "__sender_concepts.hpp" | ||
| #include "__sender_introspection.hpp" | ||
| #include "__stop_token.hpp" | ||
|
|
||
| #include <type_traits> | ||
| #include <utility> | ||
|
|
||
| namespace STDEXEC { | ||
| ///////////////////////////////////////////////////////////////////////////// | ||
| // [exec.stop.when] | ||
| namespace __stop_when_ { | ||
|
|
||
| //////////////////////////////////////////////////////////////////////////////////////////////// | ||
| template <class _Token, class _Receiver> | ||
| struct __state { | ||
| _Token __token_; | ||
| _Receiver __rcvr_; | ||
| }; | ||
|
|
||
| template <class _Token, class _Receiver> | ||
| __state(_Token, _Receiver) -> __state<_Token, _Receiver>; | ||
|
|
||
| struct __stop_when_t { | ||
| template <sender _Sender, unstoppable_token _Token> | ||
| constexpr _Sender&& operator()(_Sender&& __sndr, _Token&&) const noexcept { | ||
| return static_cast<_Sender&&>(__sndr); | ||
| } | ||
|
|
||
| template <sender _Sender, stoppable_token _Token> | ||
| constexpr auto operator()(_Sender&& __sndr, _Token&& __token) const noexcept( | ||
| __nothrow_constructible_from<std::remove_cvref_t<_Sender>, _Sender> | ||
| && __nothrow_constructible_from<std::remove_cvref_t<_Token>, _Token>) { | ||
| return __make_sexpr<__stop_when_t>( | ||
| static_cast<_Token&&>(__token), static_cast<_Sender&&>(__sndr)); | ||
| } | ||
| }; | ||
|
|
||
| struct __stop_when_impl : __sexpr_defaults { | ||
| template <class _Sender, class... _Env> | ||
| static consteval auto get_completion_signatures() // | ||
| -> __completion_signatures_of_t<__child_of<_Sender>, _Env...> { | ||
| static_assert(sender_expr_for<_Sender, __stop_when_t>); | ||
| return {}; | ||
| }; | ||
|
|
||
| static constexpr auto get_env = [](__ignore, const auto& __state) noexcept { | ||
| return __env::__join( | ||
| prop(get_stop_token, __state.__token_), STDEXEC::get_env(__state.__rcvr_)); | ||
| }; | ||
|
|
||
| template <stoppable_token _Token1, stoppable_token _Token2> | ||
| struct __fused_token { | ||
| _Token1 __tkn1_; | ||
| _Token2 __tkn2_; | ||
|
|
||
| friend constexpr bool operator==(const __fused_token&, const __fused_token&) = default; | ||
|
|
||
| [[nodiscard]] | ||
| bool stop_requested() const noexcept { | ||
| return __tkn1_.stop_requested() || __tkn2_.stop_requested(); | ||
| } | ||
|
|
||
| [[nodiscard]] | ||
| bool stop_possible() const noexcept { | ||
| return __tkn1_.stop_possible() || __tkn2_.stop_possible(); | ||
| } | ||
|
|
||
| template <class _Fn> | ||
| struct callback_type { | ||
| template <class _C> | ||
| requires __std::constructible_from<_Fn, _C> | ||
| explicit callback_type(__fused_token&& __ftkn, _C&& __fn) | ||
| noexcept(__nothrow_constructible_from<_Fn, _C>) | ||
| : __fn_(static_cast<_C&&>(__fn)) | ||
| , __cb1_(std::move(__ftkn.__tkn1_), __cb(this)) | ||
| , __cb2_(std::move(__ftkn.__tkn2_), __cb(this)) { | ||
| } | ||
|
|
||
| template <class _C> | ||
| requires __std::constructible_from<_Fn, _C> | ||
| explicit callback_type(const __fused_token& __ftkn, _C&& __fn) | ||
| noexcept(__nothrow_constructible_from<_Fn, _C>) | ||
| : __fn_(static_cast<_C&&>(__fn)) | ||
| , __cb1_(__ftkn.__tkn1_, __cb(this)) | ||
| , __cb2_(__ftkn.__tkn2_, __cb(this)) { | ||
| } | ||
|
|
||
| callback_type(callback_type&&) = delete; | ||
|
|
||
| private: | ||
| struct __cb { | ||
| callback_type* self; | ||
|
|
||
| void operator()() noexcept { | ||
| (*self)(); | ||
| } | ||
| }; | ||
|
|
||
| using __cb1_t = _Token1::template callback_type<__cb>; | ||
| using __cb2_t = _Token2::template callback_type<__cb>; | ||
|
|
||
| void operator()() noexcept { | ||
| if (!__called_.exchange(true, __std::memory_order_relaxed)) { | ||
| __fn_(); | ||
| } | ||
| } | ||
|
|
||
| _Fn __fn_; | ||
| __std::atomic<bool> __called_{false}; | ||
| __cb1_t __cb1_; | ||
| __cb2_t __cb2_; | ||
| }; | ||
| }; | ||
|
|
||
| struct __make_token_fn { | ||
| template <class _SenderToken, class _ReceiverToken> | ||
| requires stoppable_token<std::remove_cvref_t<_SenderToken>> | ||
| && unstoppable_token<std::remove_cvref_t<_ReceiverToken>> | ||
| [[nodiscard]] | ||
| std::remove_cvref_t<_SenderToken> | ||
| operator()(_SenderToken&& __sndrToken, _ReceiverToken&&) const noexcept { | ||
| // when the receiver's stop token is unstoppable, the net token is just | ||
| // the sender's captured token | ||
| return __sndrToken; | ||
| } | ||
|
|
||
| template <class _SenderToken, class _ReceiverToken> | ||
| requires stoppable_token<std::remove_cvref_t<_SenderToken>> | ||
| && stoppable_token<std::remove_cvref_t<_ReceiverToken>> | ||
| [[nodiscard]] | ||
| __fused_token<std::remove_cvref_t<_SenderToken>, std::remove_cvref_t<_ReceiverToken>> | ||
| operator()(_SenderToken&& __sndrToken, _ReceiverToken&& __rcvrToken) const noexcept { | ||
| // when the receiver's stop token is stoppable, the net token must be | ||
| // a fused token that responds to signals from both the sender's captured | ||
| // token and the receiver's token | ||
| return { | ||
| static_cast<_SenderToken&&>(__sndrToken), static_cast<_ReceiverToken&&>(__rcvrToken)}; | ||
| } | ||
| }; | ||
|
|
||
| static constexpr auto get_state = | ||
| []<class _Self, class _Receiver>(_Self&& __self, _Receiver __rcvr) noexcept { | ||
| auto& [__tag, __token, __child] = __self; | ||
| auto __new_token = __make_token_fn{}( | ||
| __forward_like<_Self>(__token), get_stop_token(STDEXEC::get_env(__rcvr))); | ||
| return __state{std::move(__new_token), std::move(__rcvr)}; | ||
| }; | ||
| }; | ||
| } // namespace __stop_when_ | ||
|
|
||
| using __stop_when_::__stop_when_t; | ||
|
|
||
| /// @brief The stop-when sender adaptor, which fuses an additional stop token | ||
| /// into its child sender such that the sender responds to stop | ||
| /// requests from both the given stop token and the receiver's token | ||
| /// @hideinitializer | ||
| inline constexpr __stop_when_t __stop_when{}; | ||
|
|
||
| template <> | ||
| struct __sexpr_impl<__stop_when_t> : __stop_when_::__stop_when_impl { }; | ||
| } // namespace STDEXEC | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| /* | ||
| * Copyright (c) 2025 Ian Petersen | ||
| * Copyright (c) 2025 NVIDIA Corporation | ||
| * | ||
| * Licensed under the Apache License Version 2.0 with LLVM Exceptions | ||
| * (the "License"); you may not use this file except in compliance with | ||
| * the License. You may obtain a copy of the License at | ||
| * | ||
| * https://llvm.org/LICENSE.txt | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| #include <catch2/catch.hpp> | ||
| #include <stdexec/execution.hpp> | ||
| #include <stdexec/stop_token.hpp> | ||
| #include <stdexec/__detail/__stop_when.hpp> | ||
| #include <test_common/receivers.hpp> | ||
|
|
||
| namespace ex = STDEXEC; | ||
|
|
||
| namespace { | ||
| TEST_CASE("stop-when of an unstoppable token is the identity", "[adaptors][stop-when]") { | ||
| auto snd = ex::just(42); | ||
| auto checkIdentity = [](auto&& snd) { | ||
| auto&& result = ex::__stop_when(std::forward<decltype(snd)>(snd), ex::never_stop_token{}); | ||
|
|
||
| REQUIRE(&snd == &result); | ||
| }; | ||
|
|
||
| checkIdentity(snd); | ||
| checkIdentity(std::as_const(snd)); | ||
| checkIdentity(std::move(snd)); | ||
| } | ||
|
|
||
| TEST_CASE("stop-when(just(), token) returns a sender", "[adaptors][stop-when]") { | ||
| ex::inplace_stop_source source; | ||
| auto snd = ex::__stop_when(ex::just(), source.get_token()); | ||
| STATIC_REQUIRE(ex::sender<decltype(snd)>); | ||
| wait_for_value(snd); | ||
| } | ||
|
|
||
| auto isTokenStoppable() { | ||
| return ex::read_env(ex::get_stop_token) | ||
| | ex::then([](auto token) noexcept { return token.stop_possible(); }); | ||
| } | ||
|
|
||
| TEST_CASE( | ||
| "stop-when substitutes its token when the receiver's token is unstoppable", | ||
| "[adaptors][stop-when]") { | ||
|
|
||
| // check that, by default, wait_for_value provides an unstoppable stop token | ||
| wait_for_value(isTokenStoppable(), false); | ||
|
|
||
| // now, check that stop-when mixes in a stoppable token | ||
|
|
||
| ex::inplace_stop_source source; | ||
|
|
||
| REQUIRE(source.get_token().stop_possible()); | ||
|
|
||
| wait_for_value(ex::__stop_when(isTokenStoppable(), source.get_token()), true); | ||
| } | ||
|
|
||
| TEST_CASE( | ||
| "stop-when fuses its token with the receiver's when both are stoppable", | ||
| "[adaptors][stop-when]") { | ||
| ex::inplace_stop_source source; | ||
| wait_for_value( | ||
| ex::__stop_when(ex::__stop_when(isTokenStoppable(), source.get_token()), source.get_token()), | ||
| true); | ||
| } | ||
|
|
||
| template <std::invocable Fn> | ||
| ex::sender auto make_stop_callback(Fn&& fn) noexcept { | ||
| return ex::read_env(ex::get_stop_token) | ||
| | ex::then([fn = std::forward<Fn>(fn)](auto token) mutable noexcept { | ||
| using cb_t = decltype(token)::template callback_type<std::remove_cvref_t<Fn>>; | ||
| return std::optional<cb_t>(std::in_place, std::move(token), std::move(fn)); | ||
| }); | ||
| } | ||
|
|
||
| TEST_CASE("callbacks registered with stop-when's token can be invoked", "[adaptors][stop-when]") { | ||
| int invokeCount = 0; | ||
| auto snd = make_stop_callback([&invokeCount]() noexcept { invokeCount++; }); | ||
|
|
||
| { | ||
| ex::inplace_stop_source source; | ||
| wait_for_value( | ||
| snd | ex::then([&](auto&& optCallback) noexcept { | ||
| source.request_stop(); | ||
| optCallback.reset(); | ||
| return invokeCount; | ||
| }), | ||
| 0); | ||
| } | ||
|
|
||
| { | ||
| ex::inplace_stop_source source; | ||
|
|
||
| wait_for_value( | ||
| ex::__stop_when(snd, source.get_token()) | ex::then([&](auto&& optCallback) noexcept { | ||
| source.request_stop(); | ||
| optCallback.reset(); | ||
| return invokeCount; | ||
| }), | ||
| 1); | ||
| } | ||
|
|
||
| { | ||
| ex::inplace_stop_source source1; | ||
| ex::inplace_stop_source source2; | ||
|
|
||
| wait_for_value( | ||
| ex::__stop_when(snd, source2.get_token()) | ex::then([&](auto&& optCallback) noexcept { | ||
| source1.request_stop(); | ||
| source2.request_stop(); | ||
| optCallback.reset(); | ||
| return invokeCount; | ||
| }) | ex::write_env(ex::prop(ex::get_stop_token, source1.get_token())), | ||
| 2); | ||
| } | ||
| } | ||
| } // namespace |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And this one?