include/boost/corosio/detail/dispatch_coro.hpp
92.6% Lines (25 / 27)
88.9% Functions (8 / 9)
Functions (9)
Function
Calls
Lines
Blocks
boost::corosio::detail::resume_trampoline::promise_type::get_return_object()
:48
2x
100.0%
100.0%
boost::corosio::detail::resume_trampoline::promise_type::initial_suspend()
:53
2x
100.0%
100.0%
boost::corosio::detail::resume_trampoline::promise_type::final_suspend()
:57
2x
100.0%
100.0%
boost::corosio::detail::resume_trampoline::promise_type::return_void()
:61
2x
100.0%
100.0%
boost::corosio::detail::resume_trampoline::promise_type::unhandled_exception()
:62
0
0.0%
0.0%
boost::corosio::detail::make_resume_trampoline(std::__n4861::coroutine_handle<void>)
:72
2x
100.0%
50.0%
boost::corosio::detail::dispatch_resume(boost::capy::executor_ref, std::__n4861::coroutine_handle<void>)
:98
2x
100.0%
89.0%
boost::corosio::detail::is_io_context_executor(boost::capy::executor_ref)
:136
468696x
100.0%
100.0%
boost::corosio::detail::dispatch_coro(boost::capy::executor_ref, boost::capy::continuation&)
:146
468696x
100.0%
100.0%
| Line | TLA | Hits | Source Code |
|---|---|---|---|
| 1 | // | ||
| 2 | // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) | ||
| 3 | // Copyright (c) 2026 Steve Gerbino | ||
| 4 | // | ||
| 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | ||
| 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||
| 7 | // | ||
| 8 | // Official repository: https://github.com/cppalliance/corosio | ||
| 9 | // | ||
| 10 | |||
| 11 | #ifndef BOOST_COROSIO_DETAIL_DISPATCH_CORO_HPP | ||
| 12 | #define BOOST_COROSIO_DETAIL_DISPATCH_CORO_HPP | ||
| 13 | |||
| 14 | #include <boost/corosio/io_context.hpp> | ||
| 15 | #include <boost/capy/continuation.hpp> | ||
| 16 | #include <boost/capy/ex/executor_ref.hpp> | ||
| 17 | #include <boost/capy/ex/any_executor.hpp> | ||
| 18 | #include <boost/capy/ex/frame_alloc_mixin.hpp> | ||
| 19 | #include <boost/capy/detail/type_id.hpp> | ||
| 20 | #include <coroutine> | ||
| 21 | #include <typeinfo> | ||
| 22 | |||
| 23 | namespace boost::corosio::detail { | ||
| 24 | |||
| 25 | /** Trampoline frame that lends an executor a stable continuation. | ||
| 26 | |||
| 27 | An executor's queue holds a dispatched continuation by reference | ||
| 28 | until dequeue (its stable-address contract) — storage a | ||
| 29 | completion about to recycle, reuse, or delete its op cannot | ||
| 30 | provide. The trampoline's frame owns the continuation for exactly | ||
| 31 | the queue-residency window; when the executor resumes it, it | ||
| 32 | resumes the real handle and self-destroys. | ||
| 33 | |||
| 34 | run_async cannot serve here: its launch wrapper calls | ||
| 35 | `ex.on_work_finished()` after the task completes, but a | ||
| 36 | completion's `executor_ref` points into the awaiting coroutine's | ||
| 37 | environment, which the resume destroys — completions must never | ||
| 38 | touch the executor after resuming the handle. This trampoline | ||
| 39 | touches nothing after the resume (same accounting as the plain | ||
| 40 | `ex.dispatch` it replaces). | ||
| 41 | */ | ||
| 42 | struct resume_trampoline | ||
| 43 | { | ||
| 44 | struct promise_type : capy::frame_alloc_mixin | ||
| 45 | { | ||
| 46 | capy::continuation cont; | ||
| 47 | |||
| 48 | 2x | resume_trampoline get_return_object() | |
| 49 | { | ||
| 50 | return { | ||
| 51 | 2x | std::coroutine_handle<promise_type>::from_promise(*this)}; | |
| 52 | } | ||
| 53 | 2x | std::suspend_always initial_suspend() noexcept | |
| 54 | { | ||
| 55 | 2x | return {}; | |
| 56 | } | ||
| 57 | 2x | std::suspend_never final_suspend() noexcept | |
| 58 | { | ||
| 59 | 2x | return {}; | |
| 60 | } | ||
| 61 | 2x | void return_void() noexcept {} | |
| 62 | ✗ | void unhandled_exception() | |
| 63 | { | ||
| 64 | ✗ | std::terminate(); | |
| 65 | } | ||
| 66 | }; | ||
| 67 | |||
| 68 | std::coroutine_handle<promise_type> h; | ||
| 69 | }; | ||
| 70 | |||
| 71 | inline resume_trampoline | ||
| 72 | 2x | make_resume_trampoline(std::coroutine_handle<> target) | |
| 73 | { | ||
| 74 | // A nested resume, not a symmetric transfer that destroys this | ||
| 75 | // frame inside await_suspend: that idiom is legal but miscompiled | ||
| 76 | // by cl 19.44 and Apple Clang 16 (access violations on exactly | ||
| 77 | // this path). The cost is one stack frame per executor hop, the | ||
| 78 | // only path that reaches here. | ||
| 79 | target.resume(); | ||
| 80 | co_return; | ||
| 81 | 4x | } | |
| 82 | |||
| 83 | /** Dispatch a handle to an executor without lending it caller storage. | ||
| 84 | |||
| 85 | Follows the executor's dispatch semantics: the resume may run | ||
| 86 | inline or be queued, at the executor's discretion. | ||
| 87 | |||
| 88 | Allocates one trampoline frame (capy's recycling frame pool); | ||
| 89 | intended for the cold cross-executor completion path only. An | ||
| 90 | allocation failure propagates out of the (noexcept-adjacent) | ||
| 91 | completion path and terminates, matching the initiation paths' | ||
| 92 | OOM policy. The caller may dispose its op before calling. | ||
| 93 | |||
| 94 | @param ex The executor the resume must run on. | ||
| 95 | @param h The coroutine to resume there. | ||
| 96 | */ | ||
| 97 | inline void | ||
| 98 | 2x | dispatch_resume(capy::executor_ref ex, std::coroutine_handle<> h) | |
| 99 | { | ||
| 100 | 2x | auto tramp = make_resume_trampoline(h); | |
| 101 | 2x | tramp.h.promise().cont.h = tramp.h; | |
| 102 | 2x | ex.dispatch(tramp.h.promise().cont).resume(); | |
| 103 | 2x | } | |
| 104 | |||
| 105 | /** Returns a handle for symmetric transfer on I/O completion. | ||
| 106 | |||
| 107 | If the executor is io_context::executor_type, returns `c.h` | ||
| 108 | directly (fast path). Otherwise the handle is dispatched through | ||
| 109 | the executor via a trampoline frame and `noop_coroutine()` is | ||
| 110 | returned. | ||
| 111 | |||
| 112 | Callers in coroutine machinery should return the result | ||
| 113 | for symmetric transfer. Callers at the scheduler pump | ||
| 114 | level should call `.resume()` on the result. | ||
| 115 | |||
| 116 | @p c is NOT retained past this call: completion ops embed their | ||
| 117 | continuation in storage that is recycled, reused, or freed the | ||
| 118 | moment the completion is consumed, so handing a deferring | ||
| 119 | executor that storage lets a concurrent operation rewrite a | ||
| 120 | queued node. The deferring branch therefore lends the executor a | ||
| 121 | frame-owned continuation instead — see `dispatch_resume` — at the | ||
| 122 | cost of one pooled frame on that cold path. | ||
| 123 | |||
| 124 | @param ex The executor to dispatch through. | ||
| 125 | @param c Carries the handle to resume; not retained. | ||
| 126 | |||
| 127 | @return A handle for symmetric transfer or `std::noop_coroutine()`. | ||
| 128 | */ | ||
| 129 | /// Check whether @p ex is the io_context executor, directly or | ||
| 130 | /// type-erased through `capy::any_executor` (run_async callers and | ||
| 131 | /// tcp_server erase it that way). The held TYPE is enough: | ||
| 132 | /// completions only run on the run thread, where | ||
| 133 | /// `io_context::executor_type::dispatch` returns `c.h` inline — | ||
| 134 | /// exactly what the fast path reproduces. | ||
| 135 | inline bool | ||
| 136 | 468696x | is_io_context_executor(capy::executor_ref ex) noexcept | |
| 137 | { | ||
| 138 | 468696x | if (ex.target<io_context::executor_type>() != nullptr) | |
| 139 | 468465x | return true; | |
| 140 | 231x | if (auto* any = ex.target<capy::any_executor>()) | |
| 141 | 229x | return any->target_type() == typeid(io_context::executor_type); | |
| 142 | 2x | return false; | |
| 143 | } | ||
| 144 | |||
| 145 | inline std::coroutine_handle<> | ||
| 146 | 468696x | dispatch_coro(capy::executor_ref ex, capy::continuation& c) | |
| 147 | { | ||
| 148 | 468696x | if (is_io_context_executor(ex)) | |
| 149 | 468694x | return c.h; | |
| 150 | 2x | dispatch_resume(ex, c.h); | |
| 151 | 2x | return std::noop_coroutine(); | |
| 152 | } | ||
| 153 | |||
| 154 | } // namespace boost::corosio::detail | ||
| 155 | |||
| 156 | #endif | ||
| 157 |