100.00% Lines (16/16) 100.00% Functions (4/4)
TLA Baseline Branch
Line Hits Code Line Hits 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 io_env.
  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 + */
HITGNC   61 + 105932 void bind(std::coroutine_handle<> self, executor_ref ex) noexcept
  62 + {
HITGNC   63 + 105932 self_.h = self;
HITGNC   64 + 105932 ex_ = ex;
HITGNC   65 + 105932 }
  66 +
  67 + /// Return true once the segment has been abandoned.
HITGNC   68 + 7353 bool abandoned() const noexcept
  69 + {
HITGNC   70 + 7353 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 + */
HITGNC   81 + 101361 void abandon() noexcept
  82 + {
HITGNC   83 + 101361 if(abandoned_.exchange(true, std::memory_order_acq_rel))
HITGNC   84 + 1 return;
HITGNC   85 + 101360 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
HITGNC   125 + 207309 abandon(continuation& c) noexcept
  126 + {
HITGNC   127 + 207309 if(c.owner)
HITGNC   128 + 101359 c.owner->abandon();
HITGNC   129 + 105950 else if(c.h)
HITGNC   130 + 105950 c.h.destroy();
HITGNC   131 + 207309 }
  132 +
  133 + } // namespace capy
  134 + } // namespace boost
  135 +
  136 + #endif