Skip to content

Commit cd89027

Browse files
committed
Add stdexec::spawn
This diff adds `stdexec::spawn` and its tests.
1 parent a3f3195 commit cd89027

File tree

6 files changed

+497
-21
lines changed

6 files changed

+497
-21
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/*
2+
* Copyright (c) 2025 Ian Petersen
3+
* Copyright (c) 2025 NVIDIA Corporation
4+
*
5+
* Licensed under the Apache License Version 2.0 with LLVM Exceptions
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* https://llvm.org/LICENSE.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
#pragma once
18+
19+
#include "__execution_fwd.hpp"
20+
21+
#include "__concepts.hpp"
22+
#include "__env.hpp"
23+
#include "__queries.hpp"
24+
#include "__receivers.hpp"
25+
#include "__scope_concepts.hpp"
26+
#include "__senders_core.hpp"
27+
#include "__type_traits.hpp"
28+
#include "__write_env.hpp"
29+
30+
#include <memory>
31+
#include <utility>
32+
33+
namespace stdexec {
34+
/////////////////////////////////////////////////////////////////////////////
35+
// [exec.spawn]
36+
namespace __spawn {
37+
struct __spawn_state_base {
38+
__spawn_state_base() = default;
39+
40+
__spawn_state_base(__spawn_state_base&&) = delete;
41+
42+
virtual void __complete() noexcept = 0;
43+
protected:
44+
~__spawn_state_base() = default;
45+
};
46+
47+
struct __spawn_receiver {
48+
using receiver_concept = receiver_t;
49+
50+
__spawn_state_base* __state_;
51+
52+
void set_value() && noexcept {
53+
__state_->__complete();
54+
}
55+
56+
void set_stopped() && noexcept {
57+
__state_->__complete();
58+
}
59+
};
60+
61+
template <class _Alloc, scope_token _Token, sender _Sender>
62+
struct __spawn_state final : __spawn_state_base {
63+
using __op_t = connect_result_t<_Sender, __spawn_receiver>;
64+
65+
__spawn_state(_Alloc __alloc, _Sender&& __sndr, _Token __token)
66+
: __alloc_(std::move(__alloc))
67+
, __op_(connect(std::move(__sndr), __spawn_receiver(this)))
68+
, __assoc_(__token.try_associate()) {
69+
}
70+
71+
void __complete() noexcept override {
72+
[[maybe_unused]]
73+
auto assoc = std::move(__assoc_);
74+
75+
{
76+
using traits = std::allocator_traits<_Alloc>::template rebind_traits<__spawn_state>;
77+
typename traits::allocator_type alloc(__alloc_);
78+
traits::destroy(alloc, this);
79+
traits::deallocate(alloc, this, 1);
80+
}
81+
}
82+
83+
void __run() noexcept {
84+
if (__assoc_) {
85+
start(__op_);
86+
} else {
87+
__complete();
88+
}
89+
}
90+
91+
private:
92+
using __assoc_t = std::remove_cvref_t<decltype(__declval<_Token&>().try_associate())>;
93+
94+
_Alloc __alloc_;
95+
__op_t __op_;
96+
__assoc_t __assoc_;
97+
};
98+
99+
struct __choose_alloc_fn {
100+
template <class _Env, class _SenderEnv>
101+
requires __callable<get_allocator_t, _Env>
102+
auto operator()(const _Env& __env, const _SenderEnv&) const {
103+
return get_allocator(__env);
104+
}
105+
106+
template <class _Env, class _SenderEnv>
107+
requires(!__callable<get_allocator_t, const _Env&>)
108+
&& __callable<get_allocator_t, const _SenderEnv&>
109+
auto operator()(const _Env&, const _SenderEnv& __env) const {
110+
return get_allocator(__env);
111+
}
112+
113+
template <class _Env, class _SenderEnv>
114+
requires(!__callable<get_allocator_t, const _Env&>)
115+
&& (!__callable<get_allocator_t, const _SenderEnv&>)
116+
std::allocator<void> operator()(const _Env&, const _SenderEnv&) const {
117+
return std::allocator<void>();
118+
}
119+
};
120+
121+
inline constexpr __choose_alloc_fn __choose_alloc{};
122+
123+
struct __choose_senv_fn {
124+
template <class _Env, class _SenderEnv>
125+
requires __callable<get_allocator_t, const _Env&>
126+
const _Env& operator()(const _Env& __env, const _SenderEnv&) const {
127+
return __env;
128+
}
129+
130+
template <class _Env, class _SenderEnv>
131+
requires(!__callable<get_allocator_t, const _Env&>)
132+
&& __callable<get_allocator_t, const _SenderEnv&>
133+
auto operator()(const _Env& __env, const _SenderEnv& __sndrEnv) const {
134+
return __env::__join(prop(get_allocator, get_allocator(__sndrEnv)), __env);
135+
}
136+
137+
template <class _Env, class _SenderEnv>
138+
requires(!__callable<get_allocator_t, const _Env&>)
139+
&& (!__callable<get_allocator_t, const _SenderEnv&>)
140+
const _Env& operator()(const _Env& __env, const _SenderEnv&) const {
141+
return __env;
142+
}
143+
};
144+
145+
inline constexpr __choose_senv_fn __choose_senv{};
146+
147+
struct spawn_t {
148+
template <sender _Sender, scope_token _Token>
149+
void operator()(_Sender&& __sndr, _Token&& __tkn) const {
150+
return (*this)(static_cast<_Sender&&>(__sndr), static_cast<_Token&&>(__tkn), env<>{});
151+
}
152+
153+
template <sender _Sender, scope_token _Token, class _Env>
154+
void operator()(_Sender&& __sndr, _Token&& __tkn, _Env&& __env) const {
155+
auto wrappedSender = __tkn.wrap(static_cast<_Sender&&>(__sndr));
156+
auto sndrEnv = get_env(wrappedSender);
157+
158+
using raw_alloc = decltype(__choose_alloc(__env, sndrEnv));
159+
160+
auto senderWithEnv = write_env(std::move(wrappedSender), __choose_senv(__env, sndrEnv));
161+
162+
using spawn_state_t =
163+
__spawn_state<raw_alloc, std::remove_cvref_t<_Token>, decltype(senderWithEnv)>;
164+
165+
using traits = std::allocator_traits<raw_alloc>::template rebind_traits<spawn_state_t>;
166+
typename traits::allocator_type alloc(__choose_alloc(__env, sndrEnv));
167+
168+
auto* op = traits::allocate(alloc, 1);
169+
170+
try {
171+
traits::construct(
172+
alloc, op, alloc, std::move(senderWithEnv), static_cast<_Token&&>(__tkn));
173+
} catch (...) {
174+
traits::deallocate(alloc, op, 1);
175+
throw;
176+
}
177+
178+
op->__run();
179+
}
180+
};
181+
} // namespace __spawn
182+
183+
using __spawn::spawn_t;
184+
185+
inline constexpr spawn_t spawn{};
186+
} // namespace stdexec

include/stdexec/execution.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "__detail/__scope_concepts.hpp" // IWYU pragma: export
5252
#include "__detail/__senders.hpp" // IWYU pragma: export
5353
#include "__detail/__sender_adaptor_closure.hpp" // IWYU pragma: export
54+
#include "__detail/__spawn.hpp" // IWYU pragma: export
5455
#include "__detail/__split.hpp" // IWYU pragma: export
5556
#include "__detail/__start_detached.hpp" // IWYU pragma: export
5657
#include "__detail/__starts_on.hpp" // IWYU pragma: export

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ set(stdexec_test_sources
6262
stdexec/algos/adaptors/test_stop_when.cpp
6363
stdexec/algos/consumers/test_start_detached.cpp
6464
stdexec/algos/consumers/test_sync_wait.cpp
65+
stdexec/algos/consumers/test_spawn.cpp
6566
stdexec/algos/other/test_execute.cpp
6667
stdexec/detail/test_completion_signatures.cpp
6768
stdexec/detail/test_utility.cpp

test/stdexec/algos/adaptors/test_associate.cpp

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,14 @@
1818
#include <catch2/catch.hpp>
1919
#include <stdexec/execution.hpp>
2020
#include <test_common/receivers.hpp>
21+
#include <test_common/scope_tokens.hpp>
2122

2223
#include <concepts>
2324
#include <memory>
2425

2526
namespace ex = stdexec;
2627

2728
namespace {
28-
struct null_token {
29-
struct assoc {
30-
constexpr operator bool() const noexcept {
31-
return true;
32-
}
33-
34-
constexpr assoc try_associate() const noexcept {
35-
return {};
36-
}
37-
};
38-
39-
template <ex::sender Sender>
40-
constexpr Sender&& wrap(Sender&& sndr) const noexcept {
41-
return std::forward<Sender>(sndr);
42-
}
43-
44-
constexpr assoc try_associate() const noexcept {
45-
return {};
46-
}
47-
};
48-
4929
TEST_CASE("associate returns a sender", "[adaptors][associate]") {
5030
using snd_t = decltype(ex::associate(ex::just(), null_token{}));
5131

0 commit comments

Comments
 (0)