92.59% Lines (25/27)
88.89% Functions (8/9)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | // | 1 | // | |||||
| 2 | // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) | 2 | // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) | |||||
| 3 | // Copyright (c) 2026 Steve Gerbino | 3 | // Copyright (c) 2026 Steve Gerbino | |||||
| 4 | // | 4 | // | |||||
| 5 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | 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) | 6 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||||
| 7 | // | 7 | // | |||||
| 8 | // Official repository: https://github.com/cppalliance/corosio | 8 | // Official repository: https://github.com/cppalliance/corosio | |||||
| 9 | // | 9 | // | |||||
| 10 | 10 | |||||||
| 11 | #ifndef BOOST_COROSIO_DETAIL_DISPATCH_CORO_HPP | 11 | #ifndef BOOST_COROSIO_DETAIL_DISPATCH_CORO_HPP | |||||
| 12 | #define BOOST_COROSIO_DETAIL_DISPATCH_CORO_HPP | 12 | #define BOOST_COROSIO_DETAIL_DISPATCH_CORO_HPP | |||||
| 13 | 13 | |||||||
| 14 | #include <boost/corosio/io_context.hpp> | 14 | #include <boost/corosio/io_context.hpp> | |||||
| 15 | #include <boost/capy/continuation.hpp> | 15 | #include <boost/capy/continuation.hpp> | |||||
| 16 | #include <boost/capy/ex/executor_ref.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> | ||||||
| 17 | #include <boost/capy/detail/type_id.hpp> | 19 | #include <boost/capy/detail/type_id.hpp> | |||||
| 18 | #include <coroutine> | 20 | #include <coroutine> | |||||
| 21 | + | #include <typeinfo> | ||||||
| 19 | 22 | |||||||
| 20 | namespace boost::corosio::detail { | 23 | namespace boost::corosio::detail { | |||||
| 21 | 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 | + | |||||||
| HITGNC | 48 | + | 2 | resume_trampoline get_return_object() | ||||
| 49 | + | { | ||||||
| 50 | + | return { | ||||||
| HITGNC | 51 | + | 2 | std::coroutine_handle<promise_type>::from_promise(*this)}; | ||||
| 52 | + | } | ||||||
| HITGNC | 53 | + | 2 | std::suspend_always initial_suspend() noexcept | ||||
| 54 | + | { | ||||||
| HITGNC | 55 | + | 2 | return {}; | ||||
| 56 | + | } | ||||||
| HITGNC | 57 | + | 2 | std::suspend_never final_suspend() noexcept | ||||
| 58 | + | { | ||||||
| HITGNC | 59 | + | 2 | return {}; | ||||
| 60 | + | } | ||||||
| HITGNC | 61 | + | 2 | void return_void() noexcept {} | ||||
| MISUNC | 62 | + | ✗ | void unhandled_exception() | ||||
| 63 | + | { | ||||||
| MISUNC | 64 | + | ✗ | std::terminate(); | ||||
| 65 | + | } | ||||||
| 66 | + | }; | ||||||
| 67 | + | |||||||
| 68 | + | std::coroutine_handle<promise_type> h; | ||||||
| 69 | + | }; | ||||||
| 70 | + | |||||||
| 71 | + | inline resume_trampoline | ||||||
| HITGNC | 72 | + | 2 | 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; | ||||||
| HITGNC | 81 | + | 4 | } | ||||
| 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 | ||||||
| HITGNC | 98 | + | 2 | dispatch_resume(capy::executor_ref ex, std::coroutine_handle<> h) | ||||
| 99 | + | { | ||||||
| HITGNC | 100 | + | 2 | auto tramp = make_resume_trampoline(h); | ||||
| HITGNC | 101 | + | 2 | tramp.h.promise().cont.h = tramp.h; | ||||
| HITGNC | 102 | + | 2 | ex.dispatch(tramp.h.promise().cont).resume(); | ||||
| HITGNC | 103 | + | 2 | } | ||||
| 104 | + | |||||||
| 22 | /** Returns a handle for symmetric transfer on I/O completion. | 105 | /** Returns a handle for symmetric transfer on I/O completion. | |||||
| 23 | 106 | |||||||
| 24 | If the executor is io_context::executor_type, returns `c.h` | 107 | If the executor is io_context::executor_type, returns `c.h` | |||||
| 25 | - | directly (fast path). Otherwise dispatches through the | 108 | + | directly (fast path). Otherwise the handle is dispatched through | |||
| 26 | - | executor, which returns `c.h` or `noop_coroutine()`. | 109 | + | the executor via a trampoline frame and `noop_coroutine()` is | |||
| 110 | + | returned. | ||||||
| 27 | 111 | |||||||
| 28 | Callers in coroutine machinery should return the result | 112 | Callers in coroutine machinery should return the result | |||||
| 29 | for symmetric transfer. Callers at the scheduler pump | 113 | for symmetric transfer. Callers at the scheduler pump | |||||
| 30 | level should call `.resume()` on the result. | 114 | level should call `.resume()` on the result. | |||||
| 31 | 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 | + | |||||||
| 32 | @param ex The executor to dispatch through. | 124 | @param ex The executor to dispatch through. | |||||
| 33 | - | @param c The continuation to dispatch. Must remain at a | 125 | + | @param c Carries the handle to resume; not retained. | |||
| 34 | - | stable address until dequeued by the executor. | ||||||
| 35 | 126 | |||||||
| 36 | @return A handle for symmetric transfer or `std::noop_coroutine()`. | 127 | @return A handle for symmetric transfer or `std::noop_coroutine()`. | |||||
| 37 | */ | 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 | ||||||
| HITGNC | 136 | + | 468696 | is_io_context_executor(capy::executor_ref ex) noexcept | ||||
| 137 | + | { | ||||||
| HITGNC | 138 | + | 468696 | if (ex.target<io_context::executor_type>() != nullptr) | ||||
| HITGNC | 139 | + | 468465 | return true; | ||||
| HITGNC | 140 | + | 231 | if (auto* any = ex.target<capy::any_executor>()) | ||||
| HITGNC | 141 | + | 229 | return any->target_type() == typeid(io_context::executor_type); | ||||
| HITGNC | 142 | + | 2 | return false; | ||||
| 143 | + | } | ||||||
| 144 | + | |||||||
| 38 | inline std::coroutine_handle<> | 145 | inline std::coroutine_handle<> | |||||
| HITCBC | 39 | 422290 | dispatch_coro(capy::executor_ref ex, capy::continuation& c) | 146 | 468696 | dispatch_coro(capy::executor_ref ex, capy::continuation& c) | ||
| 40 | { | 147 | { | |||||
| HITCBC | 41 | - | 422290 | if (ex.target<io_context::executor_type>() != nullptr) | 148 | + | 468696 | if (is_io_context_executor(ex)) |
| HITCBC | 42 | 422137 | return c.h; | 149 | 468694 | return c.h; | ||
| HITCBC | 43 | - | 153 | return ex.dispatch(c); | 150 | + | 2 | dispatch_resume(ex, c.h); |
| HITGNC | 151 | + | 2 | return std::noop_coroutine(); | ||||
| 44 | } | 152 | } | |||||
| 45 | 153 | |||||||
| 46 | } // namespace boost::corosio::detail | 154 | } // namespace boost::corosio::detail | |||||
| 47 | 155 | |||||||
| 48 | #endif | 156 | #endif | |||||