LCOV - code coverage report
Current view: top level - capy/detail - await_suspend_helper.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 66.7 % 6 4 2
Test Date: 2026-04-23 15:34:17 Functions: 16.1 % 31 5 26

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2025 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/capy
       9                 : //
      10                 : 
      11                 : #ifndef BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP
      12                 : #define BOOST_CAPY_DETAIL_AWAIT_SUSPEND_HELPER_HPP
      13                 : 
      14                 : #include <coroutine>
      15                 : #include <boost/capy/ex/io_env.hpp>
      16                 : 
      17                 : #include <type_traits>
      18                 : 
      19                 : namespace boost {
      20                 : namespace capy {
      21                 : namespace detail {
      22                 : 
      23                 : /** Perform symmetric transfer, working around an MSVC codegen bug.
      24                 : 
      25                 :     MSVC stores the `std::coroutine_handle<>` returned from
      26                 :     `await_suspend` in a hidden `__$ReturnUdt$` variable located
      27                 :     on the coroutine frame. When another thread resumes or destroys
      28                 :     the frame between the store and the read-back for the
      29                 :     symmetric-transfer tail-call, the read hits freed memory.
      30                 : 
      31                 :     This occurs in two scenarios:
      32                 : 
      33                 :     @li `await_suspend` calls `h.destroy()` then returns a handle
      34                 :         (e.g. `when_all_runner` and `when_any_runner` final_suspend).
      35                 :         The return value is written to the now-destroyed frame.
      36                 : 
      37                 :     @li `await_suspend` hands the continuation to another thread
      38                 :         via an executor handoff (e.g. `post()` or `dispatch()`),
      39                 :         which may resume the parent. The parent can destroy this
      40                 :         frame before the runtime reads `__$ReturnUdt$` (e.g.
      41                 :         `boundary_trampoline` final_suspend).
      42                 : 
      43                 :     On MSVC this function calls `h.resume()` on the current stack
      44                 :     and returns `void`, causing unconditional suspension. The
      45                 :     trade-off is O(n) stack growth instead of O(1) tail-calls.
      46                 : 
      47                 :     On other compilers the handle is returned directly for proper
      48                 :     symmetric transfer.
      49                 : 
      50                 :     Callers must use `auto` return type on their `await_suspend`
      51                 :     so the return type adapts per platform.
      52                 : 
      53                 :     @param h The coroutine handle to transfer to.
      54                 : */
      55                 : #if BOOST_CAPY_WORKAROUND(_MSC_VER, >= 1)
      56                 : inline void symmetric_transfer(std::coroutine_handle<> h) noexcept
      57                 : {
      58                 :     // safe_resume is not needed here: the calling coroutine is
      59                 :     // about to suspend unconditionally. When it later resumes,
      60                 :     // await_resume restores TLS from the promise's environment.
      61                 :     h.resume();
      62                 : }
      63                 : #else
      64                 : inline std::coroutine_handle<>
      65 HIT        2971 : symmetric_transfer(std::coroutine_handle<> h) noexcept
      66                 : {
      67            2971 :     return h;
      68                 : }
      69                 : #endif
      70                 : 
      71                 : // Helper to normalize await_suspend return types to std::coroutine_handle<>
      72                 : template<typename Awaitable>
      73               7 : std::coroutine_handle<> call_await_suspend(
      74                 :     Awaitable* a,
      75                 :     std::coroutine_handle<> h,
      76                 :     io_env const* env)
      77                 : {
      78                 :     using R = decltype(a->await_suspend(h, env));
      79                 :     if constexpr (std::is_void_v<R>)
      80                 :     {
      81 MIS           0 :         a->await_suspend(h, env);
      82               0 :         return std::noop_coroutine();
      83                 :     }
      84                 :     else if constexpr (std::is_same_v<R, bool>)
      85                 :     {
      86                 :         if(a->await_suspend(h, env))
      87                 :             return std::noop_coroutine();
      88                 :         return h;
      89                 :     }
      90                 :     else
      91                 :     {
      92 HIT           7 :         return a->await_suspend(h, env);
      93                 :     }
      94                 : }
      95                 : 
      96                 : } // namespace detail
      97                 : } // namespace capy
      98                 : } // namespace boost
      99                 : 
     100                 : #endif
        

Generated by: LCOV version 2.3