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