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 | ||||||