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