LCOV - code coverage report
Current view: top level - capy/ex - abandon.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 16 16
Test Date: 2026-10-16 22:02:34 Functions: 100.0 % 4 4

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Steve Gerbino
       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_EX_ABANDON_HPP
      11                 : #define BOOST_CAPY_EX_ABANDON_HPP
      12                 : 
      13                 : #include <boost/capy/detail/config.hpp>
      14                 : #include <boost/capy/continuation.hpp>
      15                 : #include <boost/capy/ex/executor_ref.hpp>
      16                 : 
      17                 : #include <atomic>
      18                 : #include <coroutine>
      19                 : 
      20                 : namespace boost {
      21                 : namespace capy {
      22                 : 
      23                 : /** The owner of one segment of a coroutine chain.
      24                 : 
      25                 :     A segment is a run of coroutines started together: the task
      26                 :     `run_async` launches and everything it awaits, a task `run` starts
      27                 :     on another executor, or one branch of `when_all` or `when_any`. Its
      28                 :     owner is the coroutine that started it, suspended above the
      29                 :     segment's top frame until that frame completes. Every coroutine in
      30                 :     the segment reaches the owner through its @ref await_context.
      31                 : 
      32                 :     When the holder of a suspended coroutine in the segment gives it up
      33                 :     with @ref abandon, the owner posts its own continuation to its
      34                 :     executor. There it unwinds the segment from the top: it destroys
      35                 :     the frame it awaits, which destroys the frames below it in turn, and
      36                 :     finishes without delivering a result. An executor that destroys the
      37                 :     posted continuation instead, as a closed one does, unwinds the
      38                 :     segment the same way.
      39                 : 
      40                 :     @par Thread Safety
      41                 :     @ref abandon may be called from any thread. Only one holder can
      42                 :     abandon a segment, since it is suspended at one point; a repeated
      43                 :     call before the unwind runs does nothing.
      44                 : */
      45                 : class chain_owner
      46                 : {
      47                 :     std::atomic<bool> abandoned_{false};
      48                 :     continuation self_;
      49                 :     executor_ref ex_;
      50                 : 
      51                 : public:
      52                 :     /** Bind the owner to its coroutine and executor.
      53                 : 
      54                 :         Called by the owner before it starts the segment.
      55                 : 
      56                 :         @param self The owner's coroutine, resumed or destroyed to
      57                 :             unwind the segment.
      58                 : 
      59                 :         @param ex The executor the segment runs on.
      60                 :     */
      61 HIT      105926 :     void bind(std::coroutine_handle<> self, executor_ref ex) noexcept
      62                 :     {
      63          105926 :         self_.h = self;
      64          105926 :         ex_ = ex;
      65          105926 :     }
      66                 : 
      67                 :     /// Return true once the segment has been abandoned.
      68            7332 :     bool abandoned() const noexcept
      69                 :     {
      70            7332 :         return abandoned_.load(std::memory_order_acquire);
      71                 :     }
      72                 : 
      73                 :     /** Unwind the segment on the owner's executor.
      74                 : 
      75                 :         Does nothing if the segment was already abandoned. Otherwise
      76                 :         posts the owner's continuation; nothing of the owner is touched
      77                 :         afterward, since the post may unwind and free it at once.
      78                 : 
      79                 :         @note A post that throws calls `std::terminate`.
      80                 :     */
      81          101376 :     void abandon() noexcept
      82                 :     {
      83          101376 :         if(abandoned_.exchange(true, std::memory_order_acq_rel))
      84               1 :             return;
      85          101375 :         ex_.post(self_);
      86                 :     }
      87                 : };
      88                 : 
      89                 : /** Give up a suspended coroutine for good.
      90                 : 
      91                 :     Called by the holder of a suspended coroutine that will never resume
      92                 :     it, such as an execution context, strand or primitive being torn
      93                 :     down. The coroutine's segment is unwound from its owner: on the
      94                 :     owner's executor, from the top frame down to this coroutine, without
      95                 :     any frame delivering a result. Ordinary cancellation does not use
      96                 :     this; it resumes the coroutine with an error instead.
      97                 : 
      98                 :     A continuation with no owner, such as an owner's own continuation,
      99                 :     is destroyed directly.
     100                 : 
     101                 :     @par Preconditions
     102                 :     `c` refers to a suspended coroutine that the caller holds and that
     103                 :     nothing else will resume or abandon. If `c` has no owner, nothing
     104                 :     awaits the coroutine: destroying its frame alone would leave any
     105                 :     awaiting frame holding a destroyed one.
     106                 : 
     107                 :     @par Postconditions
     108                 :     The caller must not touch the coroutine, or anything in its frame,
     109                 :     again. The frames may already be destroyed when this returns, if the
     110                 :     owner's executor destroys posted work at once.
     111                 : 
     112                 :     @note The unwind runs on the owner's executor, which may belong to
     113                 :     another execution context than the caller's, and may run after the
     114                 :     caller's context is destroyed. A chain that holds objects of the
     115                 :     caller's context must therefore have finished before that context
     116                 :     is torn down.
     117                 : 
     118                 :     @note Each frame is destroyed from the destructor of the frame
     119                 :     awaiting it, so the unwind uses stack in proportion to how deeply
     120                 :     the chain's coroutines are nested.
     121                 : 
     122                 :     @param c The continuation of the coroutine given up.
     123                 : */
     124                 : inline void
     125          207455 : abandon(continuation& c) noexcept
     126                 : {
     127          207455 :     if(c.owner)
     128          101374 :         c.owner->abandon();
     129          106081 :     else if(c.h)
     130          106081 :         c.h.destroy();
     131          207455 : }
     132                 : 
     133                 : } // namespace capy
     134                 : } // namespace boost
     135                 : 
     136                 : #endif
        

Generated by: LCOV version 2.3