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