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