TLA Line data 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 HIT 2 : resume_trampoline get_return_object()
49 : {
50 : return {
51 2 : std::coroutine_handle<promise_type>::from_promise(*this)};
52 : }
53 2 : std::suspend_always initial_suspend() noexcept
54 : {
55 2 : return {};
56 : }
57 2 : std::suspend_never final_suspend() noexcept
58 : {
59 2 : return {};
60 : }
61 2 : void return_void() noexcept {}
62 MIS 0 : void unhandled_exception()
63 : {
64 0 : std::terminate();
65 : }
66 : };
67 :
68 : std::coroutine_handle<promise_type> h;
69 : };
70 :
71 : inline resume_trampoline
72 HIT 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;
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
98 2 : dispatch_resume(capy::executor_ref ex, std::coroutine_handle<> h)
99 : {
100 2 : auto tramp = make_resume_trampoline(h);
101 2 : tramp.h.promise().cont.h = tramp.h;
102 2 : ex.dispatch(tramp.h.promise().cont).resume();
103 2 : }
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 468696 : is_io_context_executor(capy::executor_ref ex) noexcept
137 : {
138 468696 : if (ex.target<io_context::executor_type>() != nullptr)
139 468465 : return true;
140 231 : if (auto* any = ex.target<capy::any_executor>())
141 229 : return any->target_type() == typeid(io_context::executor_type);
142 2 : return false;
143 : }
144 :
145 : inline std::coroutine_handle<>
146 468696 : dispatch_coro(capy::executor_ref ex, capy::continuation& c)
147 : {
148 468696 : if (is_io_context_executor(ex))
149 468694 : return c.h;
150 2 : dispatch_resume(ex, c.h);
151 2 : return std::noop_coroutine();
152 : }
153 :
154 : } // namespace boost::corosio::detail
155 :
156 : #endif
|