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