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