LCOV - code coverage report
Current view: top level - boost/capy/ex - async_run.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 98.4 % 127 125
Test Date: 2026-01-15 23:24:39 Functions: 80.7 % 300 242

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
       3              : //
       4              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6              : //
       7              : // Official repository: https://github.com/cppalliance/capy
       8              : //
       9              : 
      10              : #ifndef BOOST_CAPY_ASYNC_RUN_HPP
      11              : #define BOOST_CAPY_ASYNC_RUN_HPP
      12              : 
      13              : #include <boost/capy/detail/config.hpp>
      14              : #include <boost/capy/concept/affine_awaitable.hpp>
      15              : #include <boost/capy/ex/detail/recycling_frame_allocator.hpp>
      16              : #include <boost/capy/ex/frame_allocator.hpp>
      17              : #include <boost/capy/task.hpp>
      18              : 
      19              : #include <coroutine>
      20              : #include <exception>
      21              : #include <optional>
      22              : #include <utility>
      23              : 
      24              : namespace boost {
      25              : namespace capy {
      26              : 
      27              : namespace detail {
      28              : 
      29              : // Discards the result on success, rethrows on exception.
      30              : struct default_handler
      31              : {
      32              :     template<typename T>
      33              :     void operator()(T&&) const noexcept
      34              :     {
      35              :     }
      36              : 
      37              :     void operator()() const noexcept
      38              :     {
      39              :     }
      40              : 
      41              :     void operator()(std::exception_ptr ep) const
      42              :     {
      43              :         if(ep)
      44              :             std::rethrow_exception(ep);
      45              :     }
      46              : };
      47              : 
      48              : // Combines two handlers into one: h1 for success, h2 for exception.
      49              : template<typename H1, typename H2>
      50              : struct handler_pair
      51              : {
      52              :     H1 h1_;
      53              :     H2 h2_;
      54              : 
      55              :     template<typename T>
      56           32 :     void operator()(T&& v)
      57              :     {
      58           32 :         h1_(std::forward<T>(v));
      59           32 :     }
      60              : 
      61           12 :     void operator()()
      62              :     {
      63           12 :         h1_();
      64           12 :     }
      65              : 
      66           10 :     void operator()(std::exception_ptr ep)
      67              :     {
      68           10 :         h2_(ep);
      69           10 :     }
      70              : };
      71              : 
      72              : /** Suspended coroutine launcher using the suspended coroutine pattern.
      73              : 
      74              :     This coroutine is created by async_run() and suspends immediately after
      75              :     setting up the frame allocator. Its frame (Frame #2) is allocated BEFORE
      76              :     the user's task (Frame #1), ensuring proper lifetime ordering.
      77              : 
      78              :     The embedder lives on this coroutine's stack, guaranteeing it outlives
      79              :     the embedded wrapper in the user's task frame.
      80              : */
      81              : template<
      82              :     dispatcher Dispatcher,
      83              :     frame_allocator Allocator>
      84              : struct async_run_launcher
      85              : {
      86              :     struct promise_type
      87              :     {
      88              :         std::optional<Dispatcher> d_;
      89              :         detail::embedding_frame_allocator<Allocator> embedder_;
      90              :         std::coroutine_handle<> inner_handle_;
      91              :         std::coroutine_handle<> continuation_;
      92              : 
      93              :         // Constructor that takes dispatcher and allocator from async_run parameters
      94              :         template<typename D, typename A>
      95           61 :         promise_type(D&& d, A&& a)
      96           61 :             : d_(std::forward<D>(d))
      97           61 :             , embedder_(std::forward<A>(a))
      98              :         {
      99              :             // Set TLS immediately so it's available for nested coroutine allocations
     100           61 :             frame_allocating_base::set_frame_allocator(embedder_);
     101           61 :         }
     102              : 
     103              :         // Default constructor (required but should not be used in normal flow)
     104              :         promise_type()
     105              :             : embedder_(Allocator{})
     106              :         {
     107              :         }
     108              : 
     109           61 :         async_run_launcher get_return_object()
     110              :         {
     111              :             return async_run_launcher{
     112              :                 std::coroutine_handle<promise_type>::from_promise(*this)
     113           61 :             };
     114              :         }
     115              : 
     116           61 :         std::suspend_always initial_suspend() noexcept
     117              :         {
     118           61 :             return {};
     119              :         }
     120              : 
     121              :         struct final_awaiter
     122              :         {
     123           61 :             bool await_ready() noexcept { return false; }
     124              : 
     125           61 :             std::coroutine_handle<> await_suspend(
     126              :                 std::coroutine_handle<promise_type> h) noexcept
     127              :             {
     128              :                 // Clear TLS after inner task completes
     129           61 :                 frame_allocating_base::clear_frame_allocator();
     130              : 
     131              :                 // Return continuation (or noop for fire-and-forget).
     132              :                 // In fire-and-forget mode, we return noop and the launcher
     133              :                 // will be destroyed by launch_awaitable's destructor after resume() returns.
     134           61 :                 auto cont = h.promise().continuation_;
     135           61 :                 return cont ? cont : std::noop_coroutine();
     136              :             }
     137              : 
     138            0 :             void await_resume() noexcept {}
     139              :         };
     140              : 
     141           61 :         final_awaiter final_suspend() noexcept { return {}; }
     142              : 
     143              :         void unhandled_exception() { throw; }
     144           61 :         void return_void() {}
     145              : 
     146              :         // Awaitable to transfer control to inner task
     147              :         struct transfer_to_inner
     148              :         {
     149              :             promise_type* p_;
     150              : 
     151           61 :             bool await_ready() noexcept { return false; }
     152              : 
     153           61 :             std::coroutine_handle<> await_suspend(
     154              :                 std::coroutine_handle<>) noexcept
     155              :             {
     156           61 :                 return p_->inner_handle_;
     157              :             }
     158              : 
     159           61 :             void await_resume() noexcept {}
     160              :         };
     161              :     };
     162              : 
     163              :     std::coroutine_handle<promise_type> handle_;
     164              : 
     165              :     // Awaitable to get promise without suspending
     166              :     struct get_promise
     167              :     {
     168              :         promise_type* p_;
     169              : 
     170           61 :         bool await_ready() noexcept { return false; }
     171              : 
     172           61 :         bool await_suspend(std::coroutine_handle<promise_type> h) noexcept
     173              :         {
     174           61 :             p_ = &h.promise();
     175           61 :             return false;  // Don't suspend
     176              :         }
     177              : 
     178           61 :         promise_type& await_resume() noexcept { return *p_; }
     179              :     };
     180              : 
     181              :     template<typename T>
     182              :     struct launch_awaitable
     183              :     {
     184              :         std::coroutine_handle<promise_type> launcher_;
     185              :         std::coroutine_handle<typename task<T>::promise_type> inner_;
     186              :         Dispatcher d_;
     187              :         bool started_ = false;
     188              : 
     189            6 :         launch_awaitable(
     190              :             std::coroutine_handle<promise_type> launcher,
     191              :             std::coroutine_handle<typename task<T>::promise_type> inner,
     192              :             Dispatcher d)
     193            6 :             : launcher_(launcher)
     194            6 :             , inner_(inner)
     195            6 :             , d_(std::move(d))
     196              :         {
     197            6 :         }
     198              : 
     199           11 :         ~launch_awaitable()
     200              :         {
     201              :             // If not awaited, run fire-and-forget style
     202           11 :             if(!started_ && launcher_)
     203              :             {
     204              :                 // Store inner handle in launcher's promise
     205            1 :                 launcher_.promise().inner_handle_ = inner_;
     206              : 
     207              :                 // Fire-and-forget: no continuation
     208            1 :                 launcher_.promise().continuation_ = std::noop_coroutine();
     209            1 :                 inner_.promise().continuation_ = launcher_;
     210            1 :                 inner_.promise().ex_ = d_;
     211            1 :                 inner_.promise().caller_ex_ = d_;
     212            1 :                 inner_.promise().needs_dispatch_ = false;
     213              : 
     214              :                 // Run synchronously
     215            1 :                 d_(any_coro{launcher_}).resume();
     216              : 
     217              :                 // Clean up
     218            1 :                 inner_.destroy();
     219            1 :                 launcher_.destroy();
     220              :             }
     221           11 :         }
     222              : 
     223              :         // Move-only
     224            5 :         launch_awaitable(launch_awaitable&& o) noexcept
     225            5 :             : launcher_(std::exchange(o.launcher_, nullptr))
     226            5 :             , inner_(std::exchange(o.inner_, nullptr))
     227            5 :             , d_(std::move(o.d_))
     228            5 :             , started_(o.started_)
     229              :         {
     230            5 :         }
     231              : 
     232              :         launch_awaitable(launch_awaitable const&) = delete;
     233              :         launch_awaitable& operator=(launch_awaitable const&) = delete;
     234              :         launch_awaitable& operator=(launch_awaitable&&) = delete;
     235              : 
     236            5 :         bool await_ready() noexcept { return false; }
     237              : 
     238              :         // Affine awaitable interface: takes continuation and dispatcher
     239              :         template<typename D>
     240            5 :         std::coroutine_handle<> await_suspend(std::coroutine_handle<> cont, D const&)
     241              :         {
     242            5 :             started_ = true;
     243              : 
     244              :             // Store inner handle in launcher's promise
     245            5 :             launcher_.promise().inner_handle_ = inner_;
     246              : 
     247              :             // Set up continuation chain: cont <- launcher <- inner
     248            5 :             launcher_.promise().continuation_ = cont;
     249            5 :             inner_.promise().continuation_ = launcher_;
     250            5 :             inner_.promise().ex_ = d_;
     251            5 :             inner_.promise().caller_ex_ = d_;
     252            5 :             inner_.promise().needs_dispatch_ = false;  // Direct transfer, no dispatch
     253              : 
     254              :             // Transfer to launcher (which will transfer to inner)
     255            5 :             return launcher_;
     256              :         }
     257              : 
     258            5 :         T await_resume()
     259              :         {
     260              :             // Get result from inner task
     261            5 :             auto& inner_promise = inner_.promise();
     262            5 :             std::exception_ptr ep = inner_promise.ep_;
     263              : 
     264              :             // Clean up handles
     265            5 :             inner_.destroy();
     266            5 :             launcher_.destroy();
     267            5 :             launcher_ = nullptr;  // Prevent destructor from running
     268              : 
     269            5 :             if(ep)
     270            2 :                 std::rethrow_exception(ep);
     271              : 
     272              :             if constexpr (!std::is_void_v<T>)
     273              :             {
     274            3 :                 auto& result_base = static_cast<detail::task_return_base<T>&>(inner_promise);
     275            6 :                 return std::move(*result_base.result_);
     276              :             }
     277            5 :         }
     278              :     };
     279              : 
     280              :     // operator() returning awaitable (can be co_awaited or run fire-and-forget)
     281              :     template<typename T>
     282            6 :     launch_awaitable<T> operator()(task<T> inner) &&
     283              :     {
     284            6 :         auto d = std::move(*handle_.promise().d_);
     285            6 :         auto launcher = handle_;
     286            6 :         handle_ = nullptr;  // Prevent destructor from destroying
     287            6 :         return launch_awaitable<T>{launcher, inner.release(), std::move(d)};
     288              :     }
     289              : 
     290              :     // operator() with handler - runs fire-and-forget and calls handler with result
     291              :     template<typename T, typename Handler>
     292            1 :     void operator()(task<T> inner, Handler h) &&
     293              :     {
     294            1 :         auto d = std::move(*handle_.promise().d_);
     295              : 
     296              :         if constexpr (std::is_invocable_v<Handler, std::exception_ptr>)
     297              :         {
     298              :             // Handler handles exceptions itself
     299            1 :             std::move(*this).run_with_handler(std::move(inner), std::move(h), std::move(d));
     300              :         }
     301              :         else
     302              :         {
     303              :             // Handler only handles success - pair with default exception handler
     304              :             using combined = handler_pair<Handler, default_handler>;
     305              :             std::move(*this).run_with_handler(
     306              :                 std::move(inner),
     307              :                 combined{std::move(h), default_handler{}},
     308              :                 std::move(d));
     309              :         }
     310            1 :     }
     311              : 
     312              :     // operator() with separate success/error handlers
     313              :     template<typename T, typename H1, typename H2>
     314           54 :     void operator()(task<T> inner, H1 h1, H2 h2) &&
     315              :     {
     316           54 :         auto d = std::move(*handle_.promise().d_);
     317              : 
     318              :         using combined = handler_pair<H1, H2>;
     319          108 :         std::move(*this).run_with_handler(
     320           54 :             std::move(inner),
     321           50 :             combined{std::move(h1), std::move(h2)},
     322           54 :             std::move(d));
     323           54 :     }
     324              : 
     325           61 :     ~async_run_launcher()
     326              :     {
     327           61 :         if(handle_)
     328            0 :             handle_.destroy();
     329           61 :     }
     330              : 
     331              :     // Move-only
     332              :     async_run_launcher(async_run_launcher&& o) noexcept
     333              :         : handle_(std::exchange(o.handle_, nullptr))
     334              :     {}
     335              : 
     336              :     async_run_launcher(async_run_launcher const&) = delete;
     337              :     async_run_launcher& operator=(async_run_launcher const&) = delete;
     338              :     async_run_launcher& operator=(async_run_launcher&&) = delete;
     339              : 
     340              : private:
     341           61 :     explicit async_run_launcher(std::coroutine_handle<promise_type> h)
     342           61 :         : handle_(h)
     343           61 :     {}
     344              : 
     345              :     template<dispatcher D, frame_allocator A>
     346              :     friend async_run_launcher<D, A> async_run(D, A);
     347              : 
     348              :     // Run with handler - executes synchronously then invokes handler
     349              :     template<typename T, typename Handler>
     350           55 :     void run_with_handler(task<T> inner, Handler h, Dispatcher d)
     351              :     {
     352           55 :         auto inner_handle = inner.release();
     353              : 
     354              :         // Store inner handle in launcher's promise
     355           55 :         handle_.promise().inner_handle_ = inner_handle;
     356              : 
     357              :         // Fire-and-forget: no continuation
     358           55 :         handle_.promise().continuation_ = std::noop_coroutine();
     359           55 :         inner_handle.promise().continuation_ = handle_;
     360           55 :         inner_handle.promise().ex_ = d;
     361           55 :         inner_handle.promise().caller_ex_ = d;
     362           55 :         inner_handle.promise().needs_dispatch_ = false;
     363              : 
     364              :         // Run synchronously
     365           55 :         auto launcher = handle_;
     366           55 :         handle_ = nullptr;  // Prevent destructor from destroying
     367           55 :         d(any_coro{launcher}).resume();
     368              : 
     369              :         // Get result from inner task and invoke handler
     370           55 :         std::exception_ptr ep = inner_handle.promise().ep_;
     371              : 
     372              :         if constexpr (std::is_void_v<T>)
     373              :         {
     374           13 :             if(ep)
     375            1 :                 h(ep);
     376              :             else
     377           12 :                 h();
     378              :         }
     379              :         else
     380              :         {
     381           42 :             if(ep)
     382            9 :                 h(ep);
     383              :             else
     384              :             {
     385           33 :                 auto& result_base = static_cast<detail::task_return_base<T>&>(
     386           33 :                     inner_handle.promise());
     387           33 :                 h(std::move(*result_base.result_));
     388              :             }
     389              :         }
     390              : 
     391              :         // Clean up
     392           55 :         inner_handle.destroy();
     393           55 :         launcher.destroy();
     394           55 :     }
     395              : };
     396              : 
     397              : } // namespace detail
     398              : 
     399              : /** Creates a launcher coroutine to launch lazy tasks for detached execution.
     400              : 
     401              :     Returns a suspended coroutine launcher whose frame is allocated BEFORE
     402              :     the user's task. This ensures the embedder (which lives on the launcher's
     403              :     stack frame) outlives the embedded wrapper in the user's task frame,
     404              :     preventing use-after-free bugs.
     405              : 
     406              :     This implementation uses the "suspended coroutine launcher" pattern to
     407              :     achieve exactly two coroutine frames with guaranteed allocation order.
     408              : 
     409              :     @par Usage
     410              :     @code
     411              :     io_context ioc;
     412              :     auto ex = ioc.get_executor();
     413              : 
     414              :     // Fire and forget - discards result, rethrows exceptions
     415              :     async_run(ex)(my_coroutine());
     416              : 
     417              :     // With handler - captures result
     418              :     async_run(ex)(compute_value(), [](int result) {
     419              :         std::cout << "Got: " << result << "\n";
     420              :     });
     421              : 
     422              :     // Awaitable mode - co_await to get result
     423              :     task<void> caller(auto ex) {
     424              :         int result = co_await async_run(ex)(compute_value());
     425              :         std::cout << "Got: " << result << "\n";
     426              :     }
     427              : 
     428              :     ioc.run();
     429              :     @endcode
     430              : 
     431              :     @param d The dispatcher that schedules and resumes the task.
     432              :     @param alloc The frame allocator (default: recycling_frame_allocator).
     433              : 
     434              :     @return A suspended async_run_launcher with operator() to launch tasks.
     435              : 
     436              :     @see async_run_launcher
     437              :     @see task
     438              :     @see dispatcher
     439              : */
     440              : template<
     441              :     dispatcher Dispatcher,
     442              :     frame_allocator Allocator = detail::recycling_frame_allocator>
     443              : detail::async_run_launcher<Dispatcher, Allocator>
     444           61 : async_run(Dispatcher, Allocator = {})
     445              : {
     446              :     // Get promise without suspending - TLS was already set in promise constructor
     447              :     auto& promise = co_await typename detail::async_run_launcher<
     448              :         Dispatcher, Allocator>::get_promise{};
     449              : 
     450              :     // Transfer control to inner task (user's task)
     451              :     co_await typename detail::async_run_launcher<
     452              :         Dispatcher, Allocator>::promise_type::transfer_to_inner{&promise};
     453              : 
     454              :     // When we resume here, inner task has completed.
     455              :     // TLS is cleared in final_suspend before returning to continuation.
     456          122 : }
     457              : 
     458              : } // namespace capy
     459              : } // namespace boost
     460              : 
     461              : #endif
        

Generated by: LCOV version 2.3