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