TLA Line data 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 HIT 106425 : void bind(std::coroutine_handle<> self, executor_ref ex) noexcept
62 : {
63 106425 : self_.h = self;
64 106425 : ex_ = ex;
65 106425 : }
66 :
67 : /// Return true once the segment has been abandoned.
68 7826 : bool abandoned() const noexcept
69 : {
70 7826 : 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 101381 : void abandon() noexcept
82 : {
83 101381 : if(abandoned_.exchange(true, std::memory_order_acq_rel))
84 1 : return;
85 101380 : 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 207463 : abandon(continuation& c) noexcept
126 : {
127 207463 : if(c.owner)
128 101379 : c.owner->abandon();
129 106084 : else if(c.h)
130 106084 : c.h.destroy();
131 207463 : }
132 :
133 : } // namespace capy
134 : } // namespace boost
135 :
136 : #endif
|