TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/capy
8 : //
9 :
10 : #ifndef BOOST_CAPY_RUN_HPP
11 : #define BOOST_CAPY_RUN_HPP
12 :
13 : #include <boost/capy/detail/config.hpp>
14 : #include <boost/capy/detail/await_suspend_helper.hpp>
15 : #include <boost/capy/detail/run.hpp>
16 : #include <boost/capy/concept/executor.hpp>
17 : #include <boost/capy/concept/io_runnable.hpp>
18 : #include <boost/capy/ex/executor_ref.hpp>
19 : #include <coroutine>
20 : #include <boost/capy/ex/frame_alloc_mixin.hpp>
21 : #include <boost/capy/ex/frame_allocator.hpp>
22 : #include <boost/capy/ex/io_env.hpp>
23 :
24 : #include <memory_resource>
25 : #include <stop_token>
26 : #include <type_traits>
27 : #include <utility>
28 : #include <variant>
29 :
30 : /*
31 : Allocator Lifetime Strategy
32 : ===========================
33 :
34 : When using run() with a custom allocator:
35 :
36 : co_await run(ex, alloc)(my_task());
37 :
38 : The evaluation order is:
39 : 1. run(ex, alloc) creates a temporary wrapper
40 : 2. my_task() allocates its coroutine frame using TLS
41 : 3. operator() returns an awaitable
42 : 4. Wrapper temporary is DESTROYED
43 : 5. co_await suspends caller, resumes task
44 : 6. Task body executes (wrapper is already dead!)
45 :
46 : Problem: The wrapper's frame_memory_resource dies before the task
47 : body runs. When initial_suspend::await_resume() restores TLS from
48 : the saved pointer, it would point to dead memory.
49 :
50 : Solution: Store a COPY of the allocator in the awaitable (not just
51 : the wrapper). The co_await mechanism extends the awaitable's lifetime
52 : until the await completes. In await_suspend, we overwrite the promise's
53 : saved frame_allocator pointer to point to the awaitable's resource.
54 :
55 : This works because standard allocator copies are equivalent - memory
56 : allocated with one copy can be deallocated with another copy. The
57 : task's own frame uses the footer-stored pointer (safe), while nested
58 : task creation uses TLS pointing to the awaitable's resource (also safe).
59 : */
60 :
61 : namespace boost::capy::detail {
62 :
63 : /** Minimal coroutine that dispatches through the caller's executor.
64 :
65 : Sits between the inner task and the parent when executors
66 : diverge. The inner task's `final_suspend` resumes this
67 : trampoline via symmetric transfer. The trampoline's own
68 : `final_suspend` dispatches the parent through the caller's
69 : executor to restore the correct execution context.
70 :
71 : The trampoline never touches the task's result.
72 : */
73 : struct BOOST_CAPY_CORO_DESTROY_WHEN_COMPLETE boundary_trampoline
74 : {
75 : struct promise_type
76 : : frame_alloc_mixin
77 : {
78 : executor_ref caller_ex_;
79 : continuation parent_;
80 :
81 HIT 18 : boundary_trampoline get_return_object() noexcept
82 : {
83 : return boundary_trampoline{
84 18 : std::coroutine_handle<promise_type>::from_promise(*this)};
85 : }
86 :
87 18 : std::suspend_always initial_suspend() noexcept { return {}; }
88 :
89 18 : auto final_suspend() noexcept
90 : {
91 : struct awaiter
92 : {
93 : promise_type* p_;
94 18 : bool await_ready() const noexcept { return false; }
95 :
96 18 : auto await_suspend(
97 : std::coroutine_handle<>) noexcept
98 : {
99 18 : p_->caller_ex_.post(p_->parent_);
100 18 : return detail::symmetric_transfer(
101 36 : std::noop_coroutine());
102 : }
103 :
104 MIS 0 : void await_resume() const noexcept {}
105 : };
106 HIT 18 : return awaiter{this};
107 : }
108 :
109 18 : void return_void() noexcept {}
110 : void unhandled_exception() noexcept {}
111 : };
112 :
113 : std::coroutine_handle<promise_type> h_{nullptr};
114 :
115 18 : boundary_trampoline() noexcept = default;
116 :
117 54 : ~boundary_trampoline()
118 : {
119 54 : if(h_) h_.destroy();
120 54 : }
121 :
122 : boundary_trampoline(boundary_trampoline const&) = delete;
123 : boundary_trampoline& operator=(boundary_trampoline const&) = delete;
124 :
125 18 : boundary_trampoline(boundary_trampoline&& o) noexcept
126 18 : : h_(std::exchange(o.h_, nullptr)) {}
127 :
128 18 : boundary_trampoline& operator=(boundary_trampoline&& o) noexcept
129 : {
130 18 : if(this != &o)
131 : {
132 18 : if(h_) h_.destroy();
133 18 : h_ = std::exchange(o.h_, nullptr);
134 : }
135 18 : return *this;
136 : }
137 :
138 : private:
139 18 : explicit boundary_trampoline(std::coroutine_handle<promise_type> h) noexcept
140 18 : : h_(h) {}
141 : };
142 :
143 18 : inline boundary_trampoline make_boundary_trampoline()
144 : {
145 : co_return;
146 36 : }
147 :
148 : /** Awaitable that binds an IoRunnable to a specific executor.
149 :
150 : Stores the executor and inner task by value. When co_awaited, the
151 : co_await expression's lifetime extension keeps both alive for the
152 : duration of the operation.
153 :
154 : A dispatch trampoline handles the executor switch on completion:
155 : the inner task's `final_suspend` resumes the trampoline, which
156 : dispatches back through the caller's executor.
157 :
158 : The `io_env` is owned by this awaitable and is guaranteed to
159 : outlive the inner task and all awaitables in its chain. Awaitables
160 : may store `io_env const*` without concern for dangling references.
161 :
162 : @tparam Task The IoRunnable type
163 : @tparam Ex The executor type
164 : @tparam InheritStopToken If true, inherit caller's stop token
165 : @tparam Alloc The allocator type (void for no allocator)
166 : */
167 : template<IoRunnable Task, Executor Ex, bool InheritStopToken, class Alloc = void>
168 : struct [[nodiscard]] run_awaitable_ex
169 : {
170 : Ex ex_;
171 : frame_memory_resource<Alloc> resource_;
172 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
173 : io_env env_;
174 : boundary_trampoline tr_;
175 : continuation task_cont_;
176 : Task inner_; // Last: destroyed first, while env_ is still valid
177 :
178 : // void allocator, inherit stop token
179 10 : run_awaitable_ex(Ex ex, Task inner)
180 : requires (InheritStopToken && std::is_void_v<Alloc>)
181 10 : : ex_(std::move(ex))
182 10 : , inner_(std::move(inner))
183 : {
184 10 : }
185 :
186 : // void allocator, explicit stop token
187 4 : run_awaitable_ex(Ex ex, Task inner, std::stop_token st)
188 : requires (!InheritStopToken && std::is_void_v<Alloc>)
189 4 : : ex_(std::move(ex))
190 4 : , st_(std::move(st))
191 4 : , inner_(std::move(inner))
192 : {
193 4 : }
194 :
195 : // with allocator, inherit stop token (use template to avoid void parameter)
196 : template<class A>
197 : requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
198 2 : run_awaitable_ex(Ex ex, A alloc, Task inner)
199 2 : : ex_(std::move(ex))
200 2 : , resource_(std::move(alloc))
201 2 : , inner_(std::move(inner))
202 : {
203 2 : }
204 :
205 : // with allocator, explicit stop token (use template to avoid void parameter)
206 : template<class A>
207 : requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
208 2 : run_awaitable_ex(Ex ex, A alloc, Task inner, std::stop_token st)
209 2 : : ex_(std::move(ex))
210 2 : , resource_(std::move(alloc))
211 2 : , st_(std::move(st))
212 2 : , inner_(std::move(inner))
213 : {
214 2 : }
215 :
216 18 : bool await_ready() const noexcept
217 : {
218 18 : return inner_.await_ready();
219 : }
220 :
221 18 : decltype(auto) await_resume()
222 : {
223 18 : return inner_.await_resume();
224 : }
225 :
226 18 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
227 : {
228 18 : tr_ = make_boundary_trampoline();
229 18 : tr_.h_.promise().caller_ex_ = caller_env->executor;
230 18 : tr_.h_.promise().parent_.h = cont;
231 :
232 18 : auto h = inner_.handle();
233 18 : auto& p = h.promise();
234 18 : p.set_continuation(tr_.h_);
235 :
236 18 : env_.executor = ex_;
237 : if constexpr (InheritStopToken)
238 12 : env_.stop_token = caller_env->stop_token;
239 : else
240 6 : env_.stop_token = st_;
241 :
242 : if constexpr (!std::is_void_v<Alloc>)
243 4 : env_.frame_allocator = resource_.get();
244 : else
245 14 : env_.frame_allocator = caller_env->frame_allocator;
246 :
247 18 : p.set_environment(&env_);
248 18 : task_cont_.h = h;
249 18 : ex_.post(task_cont_);
250 18 : return std::noop_coroutine();
251 : }
252 :
253 : // Non-copyable
254 : run_awaitable_ex(run_awaitable_ex const&) = delete;
255 : run_awaitable_ex& operator=(run_awaitable_ex const&) = delete;
256 :
257 : // Movable (no noexcept - Task may throw)
258 18 : run_awaitable_ex(run_awaitable_ex&&) = default;
259 : run_awaitable_ex& operator=(run_awaitable_ex&&) = default;
260 : };
261 :
262 : /** Awaitable that runs a task with optional stop_token override.
263 :
264 : Does NOT store an executor - the task inherits the caller's executor
265 : directly. Executors always match, so no dispatch trampoline is needed.
266 : The inner task's `final_suspend` resumes the parent directly via
267 : unconditional symmetric transfer.
268 :
269 : @tparam Task The IoRunnable type
270 : @tparam InheritStopToken If true, inherit caller's stop token
271 : @tparam Alloc The allocator type (void for no allocator)
272 : */
273 : template<IoRunnable Task, bool InheritStopToken, class Alloc = void>
274 : struct [[nodiscard]] run_awaitable
275 : {
276 : frame_memory_resource<Alloc> resource_;
277 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
278 : io_env env_;
279 : Task inner_; // Last: destroyed first, while env_ is still valid
280 :
281 : // void allocator, inherit stop token
282 : explicit run_awaitable(Task inner)
283 : requires (InheritStopToken && std::is_void_v<Alloc>)
284 : : inner_(std::move(inner))
285 : {
286 : }
287 :
288 : // void allocator, explicit stop token
289 1 : run_awaitable(Task inner, std::stop_token st)
290 : requires (!InheritStopToken && std::is_void_v<Alloc>)
291 1 : : st_(std::move(st))
292 1 : , inner_(std::move(inner))
293 : {
294 1 : }
295 :
296 : // with allocator, inherit stop token (use template to avoid void parameter)
297 : template<class A>
298 : requires (InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
299 3 : run_awaitable(A alloc, Task inner)
300 3 : : resource_(std::move(alloc))
301 3 : , inner_(std::move(inner))
302 : {
303 3 : }
304 :
305 : // with allocator, explicit stop token (use template to avoid void parameter)
306 : template<class A>
307 : requires (!InheritStopToken && !std::is_void_v<Alloc> && std::same_as<A, Alloc>)
308 2 : run_awaitable(A alloc, Task inner, std::stop_token st)
309 2 : : resource_(std::move(alloc))
310 2 : , st_(std::move(st))
311 2 : , inner_(std::move(inner))
312 : {
313 2 : }
314 :
315 6 : bool await_ready() const noexcept
316 : {
317 6 : return inner_.await_ready();
318 : }
319 :
320 6 : decltype(auto) await_resume()
321 : {
322 6 : return inner_.await_resume();
323 : }
324 :
325 6 : std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, io_env const* caller_env)
326 : {
327 6 : auto h = inner_.handle();
328 6 : auto& p = h.promise();
329 6 : p.set_continuation(cont);
330 :
331 6 : env_.executor = caller_env->executor;
332 : if constexpr (InheritStopToken)
333 3 : env_.stop_token = caller_env->stop_token;
334 : else
335 3 : env_.stop_token = st_;
336 :
337 : if constexpr (!std::is_void_v<Alloc>)
338 5 : env_.frame_allocator = resource_.get();
339 : else
340 1 : env_.frame_allocator = caller_env->frame_allocator;
341 :
342 6 : p.set_environment(&env_);
343 6 : return h;
344 : }
345 :
346 : // Non-copyable
347 : run_awaitable(run_awaitable const&) = delete;
348 : run_awaitable& operator=(run_awaitable const&) = delete;
349 :
350 : // Movable (no noexcept - Task may throw)
351 6 : run_awaitable(run_awaitable&&) = default;
352 : run_awaitable& operator=(run_awaitable&&) = default;
353 : };
354 :
355 : /** Wrapper returned by run(ex, ...) that accepts a task for execution.
356 :
357 : @tparam Ex The executor type.
358 : @tparam InheritStopToken If true, inherit caller's stop token.
359 : @tparam Alloc The allocator type (void for no allocator).
360 : */
361 : template<Executor Ex, bool InheritStopToken, class Alloc>
362 : class [[nodiscard]] run_wrapper_ex
363 : {
364 : Ex ex_;
365 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
366 : frame_memory_resource<Alloc> resource_;
367 : Alloc alloc_; // Copy to pass to awaitable
368 :
369 : public:
370 1 : run_wrapper_ex(Ex ex, Alloc alloc)
371 : requires InheritStopToken
372 1 : : ex_(std::move(ex))
373 1 : , resource_(alloc)
374 1 : , alloc_(std::move(alloc))
375 : {
376 1 : set_current_frame_allocator(&resource_);
377 1 : }
378 :
379 1 : run_wrapper_ex(Ex ex, std::stop_token st, Alloc alloc)
380 : requires (!InheritStopToken)
381 1 : : ex_(std::move(ex))
382 1 : , st_(std::move(st))
383 1 : , resource_(alloc)
384 1 : , alloc_(std::move(alloc))
385 : {
386 1 : set_current_frame_allocator(&resource_);
387 1 : }
388 :
389 : // Non-copyable, non-movable (must be used immediately)
390 : run_wrapper_ex(run_wrapper_ex const&) = delete;
391 : run_wrapper_ex(run_wrapper_ex&&) = delete;
392 : run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
393 : run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
394 :
395 : template<IoRunnable Task>
396 2 : [[nodiscard]] auto operator()(Task t) &&
397 : {
398 : if constexpr (InheritStopToken)
399 : return run_awaitable_ex<Task, Ex, true, Alloc>{
400 1 : std::move(ex_), std::move(alloc_), std::move(t)};
401 : else
402 : return run_awaitable_ex<Task, Ex, false, Alloc>{
403 1 : std::move(ex_), std::move(alloc_), std::move(t), std::move(st_)};
404 : }
405 : };
406 :
407 : /// Specialization for memory_resource* - stores pointer directly.
408 : template<Executor Ex, bool InheritStopToken>
409 : class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, std::pmr::memory_resource*>
410 : {
411 : Ex ex_;
412 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
413 : std::pmr::memory_resource* mr_;
414 :
415 : public:
416 1 : run_wrapper_ex(Ex ex, std::pmr::memory_resource* mr)
417 : requires InheritStopToken
418 1 : : ex_(std::move(ex))
419 1 : , mr_(mr)
420 : {
421 1 : set_current_frame_allocator(mr_);
422 1 : }
423 :
424 1 : run_wrapper_ex(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
425 : requires (!InheritStopToken)
426 1 : : ex_(std::move(ex))
427 1 : , st_(std::move(st))
428 1 : , mr_(mr)
429 : {
430 1 : set_current_frame_allocator(mr_);
431 1 : }
432 :
433 : // Non-copyable, non-movable (must be used immediately)
434 : run_wrapper_ex(run_wrapper_ex const&) = delete;
435 : run_wrapper_ex(run_wrapper_ex&&) = delete;
436 : run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
437 : run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
438 :
439 : template<IoRunnable Task>
440 2 : [[nodiscard]] auto operator()(Task t) &&
441 : {
442 : if constexpr (InheritStopToken)
443 : return run_awaitable_ex<Task, Ex, true, std::pmr::memory_resource*>{
444 1 : std::move(ex_), mr_, std::move(t)};
445 : else
446 : return run_awaitable_ex<Task, Ex, false, std::pmr::memory_resource*>{
447 1 : std::move(ex_), mr_, std::move(t), std::move(st_)};
448 : }
449 : };
450 :
451 : /// Specialization for no allocator (void).
452 : template<Executor Ex, bool InheritStopToken>
453 : class [[nodiscard]] run_wrapper_ex<Ex, InheritStopToken, void>
454 : {
455 : Ex ex_;
456 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
457 :
458 : public:
459 10 : explicit run_wrapper_ex(Ex ex)
460 : requires InheritStopToken
461 10 : : ex_(std::move(ex))
462 : {
463 10 : }
464 :
465 4 : run_wrapper_ex(Ex ex, std::stop_token st)
466 : requires (!InheritStopToken)
467 4 : : ex_(std::move(ex))
468 4 : , st_(std::move(st))
469 : {
470 4 : }
471 :
472 : // Non-copyable, non-movable (must be used immediately)
473 : run_wrapper_ex(run_wrapper_ex const&) = delete;
474 : run_wrapper_ex(run_wrapper_ex&&) = delete;
475 : run_wrapper_ex& operator=(run_wrapper_ex const&) = delete;
476 : run_wrapper_ex& operator=(run_wrapper_ex&&) = delete;
477 :
478 : template<IoRunnable Task>
479 14 : [[nodiscard]] auto operator()(Task t) &&
480 : {
481 : if constexpr (InheritStopToken)
482 : return run_awaitable_ex<Task, Ex, true>{
483 10 : std::move(ex_), std::move(t)};
484 : else
485 : return run_awaitable_ex<Task, Ex, false>{
486 4 : std::move(ex_), std::move(t), std::move(st_)};
487 : }
488 : };
489 :
490 : /** Wrapper returned by run(st) or run(alloc) that accepts a task.
491 :
492 : @tparam InheritStopToken If true, inherit caller's stop token.
493 : @tparam Alloc The allocator type (void for no allocator).
494 : */
495 : template<bool InheritStopToken, class Alloc>
496 : class [[nodiscard]] run_wrapper
497 : {
498 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
499 : frame_memory_resource<Alloc> resource_;
500 : Alloc alloc_; // Copy to pass to awaitable
501 :
502 : public:
503 1 : explicit run_wrapper(Alloc alloc)
504 : requires InheritStopToken
505 1 : : resource_(alloc)
506 1 : , alloc_(std::move(alloc))
507 : {
508 1 : set_current_frame_allocator(&resource_);
509 1 : }
510 :
511 1 : run_wrapper(std::stop_token st, Alloc alloc)
512 : requires (!InheritStopToken)
513 1 : : st_(std::move(st))
514 1 : , resource_(alloc)
515 1 : , alloc_(std::move(alloc))
516 : {
517 1 : set_current_frame_allocator(&resource_);
518 1 : }
519 :
520 : // Non-copyable, non-movable (must be used immediately)
521 : run_wrapper(run_wrapper const&) = delete;
522 : run_wrapper(run_wrapper&&) = delete;
523 : run_wrapper& operator=(run_wrapper const&) = delete;
524 : run_wrapper& operator=(run_wrapper&&) = delete;
525 :
526 : template<IoRunnable Task>
527 2 : [[nodiscard]] auto operator()(Task t) &&
528 : {
529 : if constexpr (InheritStopToken)
530 : return run_awaitable<Task, true, Alloc>{
531 1 : std::move(alloc_), std::move(t)};
532 : else
533 : return run_awaitable<Task, false, Alloc>{
534 1 : std::move(alloc_), std::move(t), std::move(st_)};
535 : }
536 : };
537 :
538 : /// Specialization for memory_resource* - stores pointer directly.
539 : template<bool InheritStopToken>
540 : class [[nodiscard]] run_wrapper<InheritStopToken, std::pmr::memory_resource*>
541 : {
542 : std::conditional_t<InheritStopToken, std::monostate, std::stop_token> st_;
543 : std::pmr::memory_resource* mr_;
544 :
545 : public:
546 2 : explicit run_wrapper(std::pmr::memory_resource* mr)
547 : requires InheritStopToken
548 2 : : mr_(mr)
549 : {
550 2 : set_current_frame_allocator(mr_);
551 2 : }
552 :
553 1 : run_wrapper(std::stop_token st, std::pmr::memory_resource* mr)
554 : requires (!InheritStopToken)
555 1 : : st_(std::move(st))
556 1 : , mr_(mr)
557 : {
558 1 : set_current_frame_allocator(mr_);
559 1 : }
560 :
561 : // Non-copyable, non-movable (must be used immediately)
562 : run_wrapper(run_wrapper const&) = delete;
563 : run_wrapper(run_wrapper&&) = delete;
564 : run_wrapper& operator=(run_wrapper const&) = delete;
565 : run_wrapper& operator=(run_wrapper&&) = delete;
566 :
567 : template<IoRunnable Task>
568 3 : [[nodiscard]] auto operator()(Task t) &&
569 : {
570 : if constexpr (InheritStopToken)
571 : return run_awaitable<Task, true, std::pmr::memory_resource*>{
572 2 : mr_, std::move(t)};
573 : else
574 : return run_awaitable<Task, false, std::pmr::memory_resource*>{
575 1 : mr_, std::move(t), std::move(st_)};
576 : }
577 : };
578 :
579 : /// Specialization for stop_token only (no allocator).
580 : template<>
581 : class [[nodiscard]] run_wrapper<false, void>
582 : {
583 : std::stop_token st_;
584 :
585 : public:
586 1 : explicit run_wrapper(std::stop_token st)
587 1 : : st_(std::move(st))
588 : {
589 1 : }
590 :
591 : // Non-copyable, non-movable (must be used immediately)
592 : run_wrapper(run_wrapper const&) = delete;
593 : run_wrapper(run_wrapper&&) = delete;
594 : run_wrapper& operator=(run_wrapper const&) = delete;
595 : run_wrapper& operator=(run_wrapper&&) = delete;
596 :
597 : template<IoRunnable Task>
598 1 : [[nodiscard]] auto operator()(Task t) &&
599 : {
600 1 : return run_awaitable<Task, false, void>{std::move(t), std::move(st_)};
601 : }
602 : };
603 :
604 : } // namespace boost::capy::detail
605 :
606 : namespace boost::capy {
607 :
608 : /** Bind a task to execute on a specific executor.
609 :
610 : Returns a wrapper that accepts a task and produces an awaitable.
611 : When co_awaited, the task runs on the specified executor.
612 :
613 : @par Example
614 : @code
615 : co_await run(other_executor)(my_task());
616 : @endcode
617 :
618 : @param ex The executor on which the task should run.
619 :
620 : @return A wrapper that accepts a task for execution.
621 :
622 : @see task
623 : @see executor
624 : */
625 : template<Executor Ex>
626 : [[nodiscard]] auto
627 10 : run(Ex ex)
628 : {
629 10 : return detail::run_wrapper_ex<Ex, true, void>{std::move(ex)};
630 : }
631 :
632 : /** Bind a task to an executor with a stop token.
633 :
634 : @param ex The executor on which the task should run.
635 : @param st The stop token for cooperative cancellation.
636 :
637 : @return A wrapper that accepts a task for execution.
638 : */
639 : template<Executor Ex>
640 : [[nodiscard]] auto
641 4 : run(Ex ex, std::stop_token st)
642 : {
643 : return detail::run_wrapper_ex<Ex, false, void>{
644 4 : std::move(ex), std::move(st)};
645 : }
646 :
647 : /** Bind a task to an executor with a memory resource.
648 :
649 : @param ex The executor on which the task should run.
650 : @param mr The memory resource for frame allocation.
651 :
652 : @return A wrapper that accepts a task for execution.
653 : */
654 : template<Executor Ex>
655 : [[nodiscard]] auto
656 1 : run(Ex ex, std::pmr::memory_resource* mr)
657 : {
658 : return detail::run_wrapper_ex<Ex, true, std::pmr::memory_resource*>{
659 1 : std::move(ex), mr};
660 : }
661 :
662 : /** Bind a task to an executor with a standard allocator.
663 :
664 : @param ex The executor on which the task should run.
665 : @param alloc The allocator for frame allocation.
666 :
667 : @return A wrapper that accepts a task for execution.
668 : */
669 : template<Executor Ex, detail::Allocator Alloc>
670 : [[nodiscard]] auto
671 1 : run(Ex ex, Alloc alloc)
672 : {
673 : return detail::run_wrapper_ex<Ex, true, Alloc>{
674 1 : std::move(ex), std::move(alloc)};
675 : }
676 :
677 : /** Bind a task to an executor with stop token and memory resource.
678 :
679 : @param ex The executor on which the task should run.
680 : @param st The stop token for cooperative cancellation.
681 : @param mr The memory resource for frame allocation.
682 :
683 : @return A wrapper that accepts a task for execution.
684 : */
685 : template<Executor Ex>
686 : [[nodiscard]] auto
687 1 : run(Ex ex, std::stop_token st, std::pmr::memory_resource* mr)
688 : {
689 : return detail::run_wrapper_ex<Ex, false, std::pmr::memory_resource*>{
690 1 : std::move(ex), std::move(st), mr};
691 : }
692 :
693 : /** Bind a task to an executor with stop token and standard allocator.
694 :
695 : @param ex The executor on which the task should run.
696 : @param st The stop token for cooperative cancellation.
697 : @param alloc The allocator for frame allocation.
698 :
699 : @return A wrapper that accepts a task for execution.
700 : */
701 : template<Executor Ex, detail::Allocator Alloc>
702 : [[nodiscard]] auto
703 1 : run(Ex ex, std::stop_token st, Alloc alloc)
704 : {
705 : return detail::run_wrapper_ex<Ex, false, Alloc>{
706 1 : std::move(ex), std::move(st), std::move(alloc)};
707 : }
708 :
709 : /** Run a task with a custom stop token.
710 :
711 : The task inherits the caller's executor. Only the stop token
712 : is overridden.
713 :
714 : @par Example
715 : @code
716 : std::stop_source source;
717 : co_await run(source.get_token())(cancellable_task());
718 : @endcode
719 :
720 : @param st The stop token for cooperative cancellation.
721 :
722 : @return A wrapper that accepts a task for execution.
723 : */
724 : [[nodiscard]] inline auto
725 1 : run(std::stop_token st)
726 : {
727 1 : return detail::run_wrapper<false, void>{std::move(st)};
728 : }
729 :
730 : /** Run a task with a custom memory resource.
731 :
732 : The task inherits the caller's executor. The memory resource
733 : is used for nested frame allocations.
734 :
735 : @param mr The memory resource for frame allocation.
736 :
737 : @return A wrapper that accepts a task for execution.
738 : */
739 : [[nodiscard]] inline auto
740 2 : run(std::pmr::memory_resource* mr)
741 : {
742 2 : return detail::run_wrapper<true, std::pmr::memory_resource*>{mr};
743 : }
744 :
745 : /** Run a task with a custom standard allocator.
746 :
747 : The task inherits the caller's executor. The allocator is used
748 : for nested frame allocations.
749 :
750 : @param alloc The allocator for frame allocation.
751 :
752 : @return A wrapper that accepts a task for execution.
753 : */
754 : template<detail::Allocator Alloc>
755 : [[nodiscard]] auto
756 1 : run(Alloc alloc)
757 : {
758 1 : return detail::run_wrapper<true, Alloc>{std::move(alloc)};
759 : }
760 :
761 : /** Run a task with stop token and memory resource.
762 :
763 : The task inherits the caller's executor.
764 :
765 : @param st The stop token for cooperative cancellation.
766 : @param mr The memory resource for frame allocation.
767 :
768 : @return A wrapper that accepts a task for execution.
769 : */
770 : [[nodiscard]] inline auto
771 1 : run(std::stop_token st, std::pmr::memory_resource* mr)
772 : {
773 : return detail::run_wrapper<false, std::pmr::memory_resource*>{
774 1 : std::move(st), mr};
775 : }
776 :
777 : /** Run a task with stop token and standard allocator.
778 :
779 : The task inherits the caller's executor.
780 :
781 : @param st The stop token for cooperative cancellation.
782 : @param alloc The allocator for frame allocation.
783 :
784 : @return A wrapper that accepts a task for execution.
785 : */
786 : template<detail::Allocator Alloc>
787 : [[nodiscard]] auto
788 1 : run(std::stop_token st, Alloc alloc)
789 : {
790 : return detail::run_wrapper<false, Alloc>{
791 1 : std::move(st), std::move(alloc)};
792 : }
793 :
794 : } // namespace boost::capy
795 :
796 : #endif
|