include/boost/capy/ex/abandon.hpp

100.0% Lines (16 / 16) 100.0% Functions (4 / 4)
abandon.hpp
f(x) Functions (4)
Line TLA Hits 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 105923x void bind(std::coroutine_handle<> self, executor_ref ex) noexcept
62 {
63 105923x self_.h = self;
64 105923x ex_ = ex;
65 105923x }
66
67 /// Return true once the segment has been abandoned.
68 7320x bool abandoned() const noexcept
69 {
70 7320x 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 101385x void abandon() noexcept
82 {
83 101385x if(abandoned_.exchange(true, std::memory_order_acq_rel))
84 1x return;
85 101384x 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 207412x abandon(continuation& c) noexcept
126 {
127 207412x if(c.owner)
128 101383x c.owner->abandon();
129 106029x else if(c.h)
130 106029x c.h.destroy();
131 207412x }
132
133 } // namespace capy
134 } // namespace boost
135
136 #endif
137