include/boost/capy/ex/abandon.hpp
100.0% Lines (25 / 25)
100.0% Functions (7 / 7)
Functions (7)
Function
Calls
Lines
Blocks
boost::capy::chain_owner::abandon_as(unsigned char)
:63
126211x
100.0%
73.0%
boost::capy::chain_owner::bind(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref)
:84
136552x
100.0%
100.0%
boost::capy::chain_owner::abandoned() const
:91
32947x
100.0%
68.0%
boost::capy::chain_owner::stopped() const
:101
30802x
100.0%
68.0%
boost::capy::chain_owner::abandon()
:114
111375x
100.0%
100.0%
boost::capy::chain_owner::abandon_for_stop()
:130
14836x
100.0%
100.0%
boost::capy::abandon(boost::capy::continuation&)
:174
203389x
100.0%
100.0%
| Line | TLA | Hits | Source Code |
|---|---|---|---|
| 1 | // | ||
| 2 | // Copyright (c) 2026 Steve Gerbino | ||
| 3 | // Copyright (c) 2026 Michael Vandeberg | ||
| 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_EX_ABANDON_HPP | ||
| 12 | #define BOOST_CAPY_EX_ABANDON_HPP | ||
| 13 | |||
| 14 | #include <boost/capy/detail/config.hpp> | ||
| 15 | #include <boost/capy/continuation.hpp> | ||
| 16 | #include <boost/capy/ex/executor_ref.hpp> | ||
| 17 | |||
| 18 | #include <atomic> | ||
| 19 | #include <coroutine> | ||
| 20 | |||
| 21 | namespace boost { | ||
| 22 | namespace capy { | ||
| 23 | |||
| 24 | /** The owner of one segment of a coroutine chain. | ||
| 25 | |||
| 26 | A segment is a run of coroutines started together: the task | ||
| 27 | `run_async` launches and everything it awaits, a task `run` starts | ||
| 28 | on another executor, one branch of `when_all` or `when_any`, or a | ||
| 29 | @ref quitter awaited by a coroutine that is not a quitter. Its | ||
| 30 | owner is the coroutine that started it, suspended above the | ||
| 31 | segment's top frame until that frame completes. Every coroutine in | ||
| 32 | the segment reaches the owner through its @ref await_context. | ||
| 33 | |||
| 34 | When the holder of a suspended coroutine in the segment gives it up | ||
| 35 | with @ref abandon, the owner posts its own continuation to its | ||
| 36 | executor. There it unwinds the segment from the top: it destroys | ||
| 37 | the frame it awaits, which destroys the frames below it in turn. | ||
| 38 | After this teardown, the owner delivers no result. An executor that | ||
| 39 | destroys the posted continuation instead, as a closed one does, | ||
| 40 | unwinds the segment the same way. | ||
| 41 | |||
| 42 | The owner also records why the segment was abandoned. Teardown | ||
| 43 | calls @ref abandon. A stopped @ref quitter calls | ||
| 44 | @ref abandon_for_stop instead. Its owner unwinds the segment the | ||
| 45 | same way, then reports the stop to its parent. An owner that never | ||
| 46 | asks @ref stopped treats a stop as teardown: it delivers nothing. A | ||
| 47 | launcher written by a user must therefore check @ref stopped if it | ||
| 48 | runs quitters and has to report their stops. | ||
| 49 | |||
| 50 | @par Thread Safety | ||
| 51 | @ref abandon and @ref abandon_for_stop may be called from any | ||
| 52 | thread. Only the first call takes effect, and decides the cause; a | ||
| 53 | later call of either before the unwind runs does nothing. | ||
| 54 | */ | ||
| 55 | class chain_owner | ||
| 56 | { | ||
| 57 | enum : unsigned char { not_abandoned, by_teardown, by_stop }; | ||
| 58 | |||
| 59 | std::atomic<unsigned char> cause_{not_abandoned}; | ||
| 60 | continuation self_; | ||
| 61 | executor_ref ex_; | ||
| 62 | |||
| 63 | 126211x | void abandon_as(unsigned char cause) noexcept | |
| 64 | { | ||
| 65 | 126211x | unsigned char expected = not_abandoned; | |
| 66 | 252422x | if(!cause_.compare_exchange_strong( | |
| 67 | expected, cause, | ||
| 68 | std::memory_order_acq_rel, | ||
| 69 | std::memory_order_acquire)) | ||
| 70 | 10002x | return; | |
| 71 | 116209x | ex_.post(self_); | |
| 72 | } | ||
| 73 | |||
| 74 | public: | ||
| 75 | /** Bind the owner to its coroutine and executor. | ||
| 76 | |||
| 77 | Called by the owner before it starts the segment. | ||
| 78 | |||
| 79 | @param self The owner's coroutine, resumed or destroyed to | ||
| 80 | unwind the segment. | ||
| 81 | |||
| 82 | @param ex The executor the segment runs on. | ||
| 83 | */ | ||
| 84 | 136552x | void bind(std::coroutine_handle<> self, executor_ref ex) noexcept | |
| 85 | { | ||
| 86 | 136552x | self_.h = self; | |
| 87 | 136552x | ex_ = ex; | |
| 88 | 136552x | } | |
| 89 | |||
| 90 | /// Return true once the segment has been abandoned. | ||
| 91 | 32947x | bool abandoned() const noexcept | |
| 92 | { | ||
| 93 | 65894x | return cause_.load(std::memory_order_acquire) != not_abandoned; | |
| 94 | } | ||
| 95 | |||
| 96 | /** Return true if the segment was abandoned because of a stop. | ||
| 97 | |||
| 98 | @return `true` only if the first abandon of the segment was | ||
| 99 | @ref abandon_for_stop. | ||
| 100 | */ | ||
| 101 | 30802x | bool stopped() const noexcept | |
| 102 | { | ||
| 103 | 61604x | return cause_.load(std::memory_order_acquire) == by_stop; | |
| 104 | } | ||
| 105 | |||
| 106 | /** Unwind the segment on the owner's executor. | ||
| 107 | |||
| 108 | Does nothing if the segment was already abandoned. Otherwise | ||
| 109 | posts the owner's continuation; nothing of the owner is touched | ||
| 110 | afterward, since the post may unwind and free it at once. | ||
| 111 | |||
| 112 | @note A post that throws calls `std::terminate`. | ||
| 113 | */ | ||
| 114 | 111375x | void abandon() noexcept | |
| 115 | { | ||
| 116 | 111375x | abandon_as(by_teardown); | |
| 117 | 111375x | } | |
| 118 | |||
| 119 | /** Unwind the segment on the owner's executor, as stopped. | ||
| 120 | |||
| 121 | The same as @ref abandon, but records the cause as a stop, so | ||
| 122 | @ref stopped returns `true` if this call was the first. Called | ||
| 123 | when a @ref quitter in the segment is stopped: at a `co_await` | ||
| 124 | in its body, when an operation completes into its segment's | ||
| 125 | gate, or when the gate through which `run_async` or `run` | ||
| 126 | starts a quitter does not start it. | ||
| 127 | |||
| 128 | @note A post that throws calls `std::terminate`. | ||
| 129 | */ | ||
| 130 | 14836x | void abandon_for_stop() noexcept | |
| 131 | { | ||
| 132 | 14836x | abandon_as(by_stop); | |
| 133 | 14836x | } | |
| 134 | }; | ||
| 135 | |||
| 136 | /** Give up a suspended coroutine for good. | ||
| 137 | |||
| 138 | Called by the holder of a suspended coroutine that will never resume | ||
| 139 | it, such as an execution context, strand or primitive being torn | ||
| 140 | down. The coroutine's segment is unwound from its owner: on the | ||
| 141 | owner's executor, from the top frame down to this coroutine, without | ||
| 142 | any frame delivering a result. Ordinary cancellation of a @ref task | ||
| 143 | does not use this; it resumes the coroutine with an error instead. | ||
| 144 | A @ref quitter's stop does use abandonment, through | ||
| 145 | @ref chain_owner::abandon_for_stop. | ||
| 146 | |||
| 147 | A continuation with no owner, such as an owner's own continuation, | ||
| 148 | is destroyed directly. | ||
| 149 | |||
| 150 | @par Preconditions | ||
| 151 | `c` refers to a suspended coroutine that the caller holds and that | ||
| 152 | nothing else will resume or abandon. If `c` has no owner, nothing | ||
| 153 | awaits the coroutine: destroying its frame alone would leave any | ||
| 154 | awaiting frame holding a destroyed one. | ||
| 155 | |||
| 156 | @par Postconditions | ||
| 157 | The caller must not touch the coroutine, or anything in its frame, | ||
| 158 | again. The frames may already be destroyed when this returns, if the | ||
| 159 | owner's executor destroys posted work at once. | ||
| 160 | |||
| 161 | @note The unwind runs on the owner's executor, which may belong to | ||
| 162 | another execution context than the caller's, and may run after the | ||
| 163 | caller's context is destroyed. A chain that holds objects of the | ||
| 164 | caller's context must therefore have finished before that context | ||
| 165 | is torn down. | ||
| 166 | |||
| 167 | @note Each frame is destroyed from the destructor of the frame | ||
| 168 | awaiting it, so the unwind uses stack in proportion to how deeply | ||
| 169 | the chain's coroutines are nested. | ||
| 170 | |||
| 171 | @param c The continuation of the coroutine given up. | ||
| 172 | */ | ||
| 173 | inline void | ||
| 174 | 203389x | abandon(continuation& c) noexcept | |
| 175 | { | ||
| 176 | 203389x | if(c.owner) | |
| 177 | 101372x | c.owner->abandon(); | |
| 178 | 102017x | else if(c.h) | |
| 179 | 102017x | c.h.destroy(); | |
| 180 | 203389x | } | |
| 181 | |||
| 182 | } // namespace capy | ||
| 183 | } // namespace boost | ||
| 184 | |||
| 185 | #endif | ||
| 186 |