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 io_env. 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   105926 void bind(std::coroutine_handle<> self, executor_ref ex) noexcept
62   { 62   {
HITCBC 63   105915 self_.h = self; 63   105926 self_.h = self;
HITCBC 64   105915 ex_ = ex; 64   105926 ex_ = ex;
HITCBC 65   105915 } 65   105926 }
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   7332 bool abandoned() const noexcept
69   { 69   {
HITCBC 70   7334 return abandoned_.load(std::memory_order_acquire); 70   7332 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   101376 void abandon() noexcept
82   { 82   {
HITCBC 83   101363 if(abandoned_.exchange(true, std::memory_order_acq_rel)) 83   101376 if(abandoned_.exchange(true, std::memory_order_acq_rel))
HITCBC 84   1 return; 84   1 return;
HITCBC 85   101362 ex_.post(self_); 85   101375 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   207539 abandon(continuation& c) noexcept 125   207455 abandon(continuation& c) noexcept
126   { 126   {
HITCBC 127   207539 if(c.owner) 127   207455 if(c.owner)
HITCBC 128   101361 c.owner->abandon(); 128   101374 c.owner->abandon();
HITCBC 129   106178 else if(c.h) 129   106081 else if(c.h)
HITCBC 130   106178 c.h.destroy(); 130   106081 c.h.destroy();
HITCBC 131   207539 } 131   207455 }
132   132  
133   } // namespace capy 133   } // namespace capy
134   } // namespace boost 134   } // namespace boost
135   135  
136   #endif 136   #endif