Skip to content

Commit 85e5ce5

Browse files
committed
Add stdexec::spawn
This diff adds `stdexec::spawn` and its tests.
1 parent 87be632 commit 85e5ce5

File tree

6 files changed

+499
-21
lines changed

6 files changed

+499
-21
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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 <type_traits>
32+
#include <utility>
33+
34+
namespace stdexec {
35+
/////////////////////////////////////////////////////////////////////////////
36+
// [exec.spawn]
37+
namespace __spawn {
38+
struct __spawn_state_base {
39+
__spawn_state_base() = default;
40+
41+
__spawn_state_base(__spawn_state_base&&) = delete;
42+
43+
virtual void __complete() noexcept = 0;
44+
45+
protected:
46+
~__spawn_state_base() = default;
47+
};
48+
49+
struct __spawn_receiver {
50+
using receiver_concept = receiver_t;
51+
52+
__spawn_state_base* __state_;
53+
54+
void set_value() && noexcept {
55+
__state_->__complete();
56+
}
57+
58+
void set_stopped() && noexcept {
59+
__state_->__complete();
60+
}
61+
};
62+
63+
template <class _Alloc, scope_token _Token, sender _Sender>
64+
struct __spawn_state final : __spawn_state_base {
65+
using __op_t = connect_result_t<_Sender, __spawn_receiver>;
66+
67+
__spawn_state(_Alloc __alloc, _Sender&& __sndr, _Token __token)
68+
: __alloc_(std::move(__alloc))
69+
, __op_(connect(std::move(__sndr), __spawn_receiver(this)))
70+
, __assoc_(__token.try_associate()) {
71+
}
72+
73+
void __complete() noexcept override {
74+
[[maybe_unused]]
75+
auto assoc = std::move(__assoc_);
76+
77+
{
78+
using traits = std::allocator_traits<_Alloc>::template rebind_traits<__spawn_state>;
79+
typename traits::allocator_type alloc(__alloc_);
80+
traits::destroy(alloc, this);
81+
traits::deallocate(alloc, this, 1);
82+
}
83+
}
84+
85+
void __run() noexcept {
86+
if (__assoc_) {
87+
start(__op_);
88+
} else {
89+
__complete();
90+
}
91+
}
92+
93+
private:
94+
using __assoc_t = std::remove_cvref_t<decltype(__declval<_Token&>().try_associate())>;
95+
96+
_Alloc __alloc_;
97+
__op_t __op_;
98+
__assoc_t __assoc_;
99+
};
100+
101+
struct __choose_alloc_fn {
102+
template <class _Env, class _SenderEnv>
103+
requires __callable<get_allocator_t, _Env>
104+
auto operator()(const _Env& __env, const _SenderEnv&) const {
105+
return get_allocator(__env);
106+
}
107+
108+
template <class _Env, class _SenderEnv>
109+
requires(!__callable<get_allocator_t, const _Env&>)
110+
&& __callable<get_allocator_t, const _SenderEnv&>
111+
auto operator()(const _Env&, const _SenderEnv& __env) const {
112+
return get_allocator(__env);
113+
}
114+
115+
template <class _Env, class _SenderEnv>
116+
requires(!__callable<get_allocator_t, const _Env&>)
117+
&& (!__callable<get_allocator_t, const _SenderEnv&>)
118+
std::allocator<void> operator()(const _Env&, const _SenderEnv&) const {
119+
return std::allocator<void>();
120+
}
121+
};
122+
123+
inline constexpr __choose_alloc_fn __choose_alloc{};
124+
125+
struct __choose_senv_fn {
126+
template <class _Env, class _SenderEnv>
127+
requires __callable<get_allocator_t, const _Env&>
128+
const _Env& operator()(const _Env& __env, const _SenderEnv&) const {
129+
return __env;
130+
}
131+
132+
template <class _Env, class _SenderEnv>
133+
requires(!__callable<get_allocator_t, const _Env&>)
134+
&& __callable<get_allocator_t, const _SenderEnv&>
135+
auto operator()(const _Env& __env, const _SenderEnv& __sndrEnv) const {
136+
return __env::__join(prop(get_allocator, get_allocator(__sndrEnv)), __env);
137+
}
138+
139+
template <class _Env, class _SenderEnv>
140+
requires(!__callable<get_allocator_t, const _Env&>)
141+
&& (!__callable<get_allocator_t, const _SenderEnv&>)
142+
const _Env& operator()(const _Env& __env, const _SenderEnv&) const {
143+
return __env;
144+
}
145+
};
146+
147+
inline constexpr __choose_senv_fn __choose_senv{};
148+
149+
struct spawn_t {
150+
template <sender _Sender, scope_token _Token>
151+
void operator()(_Sender&& __sndr, _Token&& __tkn) const {
152+
return (*this)(static_cast<_Sender&&>(__sndr), static_cast<_Token&&>(__tkn), env<>{});
153+
}
154+
155+
template <sender _Sender, scope_token _Token, class _Env>
156+
void operator()(_Sender&& __sndr, _Token&& __tkn, _Env&& __env) const {
157+
auto wrappedSender = __tkn.wrap(static_cast<_Sender&&>(__sndr));
158+
auto sndrEnv = get_env(wrappedSender);
159+
160+
using raw_alloc = decltype(__choose_alloc(__env, sndrEnv));
161+
162+
auto senderWithEnv = write_env(std::move(wrappedSender), __choose_senv(__env, sndrEnv));
163+
164+
using spawn_state_t =
165+
__spawn_state<raw_alloc, std::remove_cvref_t<_Token>, decltype(senderWithEnv)>;
166+
167+
using traits = std::allocator_traits<raw_alloc>::template rebind_traits<spawn_state_t>;
168+
typename traits::allocator_type alloc(__choose_alloc(__env, sndrEnv));
169+
170+
auto* op = traits::allocate(alloc, 1);
171+
172+
try {
173+
traits::construct(
174+
alloc, op, alloc, std::move(senderWithEnv), static_cast<_Token&&>(__tkn));
175+
} catch (...) {
176+
traits::deallocate(alloc, op, 1);
177+
throw;
178+
}
179+
180+
op->__run();
181+
}
182+
};
183+
} // namespace __spawn
184+
185+
using __spawn::spawn_t;
186+
187+
inline constexpr spawn_t spawn{};
188+
} // 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)