LCOV - code coverage report
Current view: top level - corosio/detail - dispatch_coro.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 92.6 % 27 25 2
Test Date: 2026-10-08 18:13:32 Functions: 88.9 % 9 8 1

           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
        

Generated by: LCOV version 2.3