100.00% Lines (54/54) 100.00% Functions (21/21)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
9   // 9   //
10   10  
11   #ifndef BOOST_CAPY_ANY_EXECUTOR_HPP 11   #ifndef BOOST_CAPY_ANY_EXECUTOR_HPP
12   #define BOOST_CAPY_ANY_EXECUTOR_HPP 12   #define BOOST_CAPY_ANY_EXECUTOR_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/continuation.hpp> 15   #include <boost/capy/continuation.hpp>
16   #include <concepts> 16   #include <concepts>
17   #include <coroutine> 17   #include <coroutine>
18   #include <memory> 18   #include <memory>
19   #include <type_traits> 19   #include <type_traits>
20   #include <typeinfo> 20   #include <typeinfo>
21   21  
22   namespace boost { 22   namespace boost {
23   namespace capy { 23   namespace capy {
24   24  
25   class execution_context; 25   class execution_context;
26   template<typename> class strand; 26   template<typename> class strand;
27   27  
28   namespace detail { 28   namespace detail {
29   29  
30   template<typename T> 30   template<typename T>
31   struct is_strand_type : std::false_type {}; 31   struct is_strand_type : std::false_type {};
32   32  
33   template<typename E> 33   template<typename E>
34   struct is_strand_type<strand<E>> : std::true_type {}; 34   struct is_strand_type<strand<E>> : std::true_type {};
35   35  
36   } // detail 36   } // detail
37   37  
38   /** Forwards `dispatch`/`post`/`context` calls through a shared, type-erased executor pointer. 38   /** Forwards `dispatch`/`post`/`context` calls through a shared, type-erased executor pointer.
39   39  
40   This class provides type erasure for any executor type, enabling 40   This class provides type erasure for any executor type, enabling
41   runtime polymorphism with automatic memory management via shared 41   runtime polymorphism with automatic memory management via shared
42   ownership. It stores a shared pointer to a polymorphic wrapper, 42   ownership. It stores a shared pointer to a polymorphic wrapper,
43   allowing executors of different types to be stored uniformly 43   allowing executors of different types to be stored uniformly
44   while satisfying the full `Executor` concept. 44   while satisfying the full `Executor` concept.
45   45  
46   @par Value Semantics 46   @par Value Semantics
47   47  
48   This class has value semantics with shared ownership. Copy and 48   This class has value semantics with shared ownership. Copy and
49   move operations are cheap, copying the internal shared 49   move operations are cheap, copying the internal shared
50   pointer. Multiple `any_executor` instances may share the same 50   pointer. Multiple `any_executor` instances may share the same
51   underlying executor. Move operations do not invalidate the 51   underlying executor. Move operations do not invalidate the
52   source; there is no moved-from state. 52   source; there is no moved-from state.
53   53  
54   @par Default State 54   @par Default State
55   55  
56   A default-constructed `any_executor` holds no executor. 56   A default-constructed `any_executor` holds no executor.
57   `operator bool()`, `operator==`, and `target_type()` report the 57   `operator bool()`, `operator==`, and `target_type()` report the
58   empty state. `context()`, `on_work_started()`, `on_work_finished()`, 58   empty state. `context()`, `on_work_started()`, `on_work_finished()`,
59   `dispatch()`, and `post()` are undefined behavior until an 59   `dispatch()`, and `post()` are undefined behavior until an
60   executor is assigned. 60   executor is assigned.
61   61  
62   @par Thread Safety 62   @par Thread Safety
63   63  
64   The `any_executor` itself is thread-safe for concurrent reads. 64   The `any_executor` itself is thread-safe for concurrent reads.
65   Concurrent modification requires external synchronization. 65   Concurrent modification requires external synchronization.
66   Executor operations are safe to call concurrently if the 66   Executor operations are safe to call concurrently if the
67   underlying executor supports it. 67   underlying executor supports it.
68   68  
69   @par Executor Concept 69   @par Executor Concept
70   70  
71   This class satisfies the `Executor` concept, making it usable 71   This class satisfies the `Executor` concept, making it usable
72   anywhere a concrete executor is expected. 72   anywhere a concrete executor is expected.
73   73  
74   @par Example 74   @par Example
75   @par !example example 75   @par !example example
76   76  
77   77  
78   @see executor_ref, Executor 78   @see executor_ref, Executor
79   */ 79   */
80   class any_executor 80   class any_executor
81   { 81   {
82   struct impl_base; 82   struct impl_base;
83   83  
84   std::shared_ptr<impl_base> p_; 84   std::shared_ptr<impl_base> p_;
85   85  
86   struct impl_base 86   struct impl_base
87   { 87   {
HITCBC 88   20 virtual ~impl_base() = default; 88   20 virtual ~impl_base() = default;
89   virtual execution_context& context() const noexcept = 0; 89   virtual execution_context& context() const noexcept = 0;
90   virtual void on_work_started() const noexcept = 0; 90   virtual void on_work_started() const noexcept = 0;
91   virtual void on_work_finished() const noexcept = 0; 91   virtual void on_work_finished() const noexcept = 0;
92   virtual std::coroutine_handle<> dispatch(continuation&) const = 0; 92   virtual std::coroutine_handle<> dispatch(continuation&) const = 0;
93   virtual void post(continuation&) const = 0; 93   virtual void post(continuation&) const = 0;
94   virtual bool equals(impl_base const*) const noexcept = 0; 94   virtual bool equals(impl_base const*) const noexcept = 0;
95   virtual std::type_info const& target_type() const noexcept = 0; 95   virtual std::type_info const& target_type() const noexcept = 0;
96   }; 96   };
97   97  
98   template<class Ex> 98   template<class Ex>
99   struct impl final : impl_base 99   struct impl final : impl_base
100   { 100   {
101   Ex ex_; 101   Ex ex_;
102   102  
103   template<class Ex1> 103   template<class Ex1>
HITCBC 104   20 explicit impl(Ex1&& ex) 104   20 explicit impl(Ex1&& ex)
HITCBC 105   20 : ex_(std::forward<Ex1>(ex)) 105   20 : ex_(std::forward<Ex1>(ex))
106   { 106   {
HITCBC 107   20 } 107   20 }
108   108  
HITCBC 109   6 execution_context& context() const noexcept override 109   6 execution_context& context() const noexcept override
110   { 110   {
HITCBC 111   6 return const_cast<Ex&>(ex_).context(); 111   6 return const_cast<Ex&>(ex_).context();
112   } 112   }
113   113  
HITCBC 114   5 void on_work_started() const noexcept override 114   5 void on_work_started() const noexcept override
115   { 115   {
HITCBC 116   5 ex_.on_work_started(); 116   5 ex_.on_work_started();
HITCBC 117   5 } 117   5 }
118   118  
HITCBC 119   5 void on_work_finished() const noexcept override 119   5 void on_work_finished() const noexcept override
120   { 120   {
HITCBC 121   5 ex_.on_work_finished(); 121   5 ex_.on_work_finished();
HITCBC 122   5 } 122   5 }
123   123  
HITCBC 124   5 std::coroutine_handle<> dispatch(continuation& c) const override 124   5 std::coroutine_handle<> dispatch(continuation& c) const override
125   { 125   {
HITCBC 126   5 return ex_.dispatch(c); 126   5 return ex_.dispatch(c);
127   } 127   }
128   128  
HITCBC 129   15 void post(continuation& c) const override 129   15 void post(continuation& c) const override
130   { 130   {
HITCBC 131   15 ex_.post(c); 131   15 ex_.post(c);
HITCBC 132   15 } 132   15 }
133   133  
HITCBC 134   9 bool equals(impl_base const* other) const noexcept override 134   9 bool equals(impl_base const* other) const noexcept override
135   { 135   {
HITCBC 136   9 if(target_type() != other->target_type()) 136   9 if(target_type() != other->target_type())
HITCBC 137   1 return false; 137   1 return false;
HITCBC 138   8 return ex_ == static_cast<impl const*>(other)->ex_; 138   8 return ex_ == static_cast<impl const*>(other)->ex_;
139   } 139   }
140   140  
HITCBC 141   19 std::type_info const& target_type() const noexcept override 141   19 std::type_info const& target_type() const noexcept override
142   { 142   {
HITCBC 143   19 return typeid(Ex); 143   19 return typeid(Ex);
144   } 144   }
145   }; 145   };
146   146  
147   public: 147   public:
148   /** Construct a default instance. 148   /** Construct a default instance.
149   149  
150   Constructs an empty `any_executor`. `operator bool()` reports 150   Constructs an empty `any_executor`. `operator bool()` reports
151   the empty state; `context()`, `on_work_started()`, 151   the empty state; `context()`, `on_work_started()`,
152   `on_work_finished()`, `dispatch()`, and `post()` are undefined 152   `on_work_finished()`, `dispatch()`, and `post()` are undefined
153   behavior until an executor is assigned. 153   behavior until an executor is assigned.
154   154  
155   @par Postconditions 155   @par Postconditions
156   @li `!*this` 156   @li `!*this`
157   */ 157   */
HITCBC 158   2 any_executor() = default; 158   2 any_executor() = default;
159   159  
160   /** Construct a copy. 160   /** Construct a copy.
161   161  
162   Creates a new `any_executor` sharing ownership of the 162   Creates a new `any_executor` sharing ownership of the
163   underlying executor with `other`. 163   underlying executor with `other`.
164   164  
165   @param other The executor to copy. 165   @param other The executor to copy.
166   166  
167   @par Postconditions 167   @par Postconditions
168   @li `*this == other` 168   @li `*this == other`
169   */ 169   */
HITCBC 170   33 any_executor(any_executor const& other) = default; 170   33 any_executor(any_executor const& other) = default;
171   171  
172   /** Copy assignment operator. 172   /** Copy assignment operator.
173   173  
174   Shares ownership of the underlying executor with `other`. 174   Shares ownership of the underlying executor with `other`.
175   175  
176   @param other The executor to copy. 176   @param other The executor to copy.
177   177  
178   @return A reference to `*this`. 178   @return A reference to `*this`.
179   179  
180   @par Postconditions 180   @par Postconditions
181   @li `*this == other` 181   @li `*this == other`
182   */ 182   */
HITCBC 183   6 any_executor& operator=(any_executor const& other) = default; 183   6 any_executor& operator=(any_executor const& other) = default;
184   184  
185   /** Constructs from any executor type. 185   /** Constructs from any executor type.
186   186  
187   Allocates storage for a copy of the given executor and 187   Allocates storage for a copy of the given executor and
188   stores it internally. The executor must satisfy the 188   stores it internally. The executor must satisfy the
189   `Executor` concept. 189   `Executor` concept.
190   190  
191   @param ex The executor to wrap. A copy is stored internally. 191   @param ex The executor to wrap. A copy is stored internally.
192   192  
193   @par Postconditions 193   @par Postconditions
194   @li `*this` is valid 194   @li `*this` is valid
195   */ 195   */
196   template<class Ex> 196   template<class Ex>
197   requires ( 197   requires (
198   !std::same_as<std::decay_t<Ex>, any_executor> && 198   !std::same_as<std::decay_t<Ex>, any_executor> &&
199   !detail::is_strand_type<std::decay_t<Ex>>::value && 199   !detail::is_strand_type<std::decay_t<Ex>>::value &&
200   std::copy_constructible<std::decay_t<Ex>>) 200   std::copy_constructible<std::decay_t<Ex>>)
HITCBC 201   20 any_executor(Ex&& ex) 201   20 any_executor(Ex&& ex)
HITCBC 202   20 : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex))) 202   20 : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
203   { 203   {
HITCBC 204   20 } 204   20 }
205   205  
206   /** Returns true if this instance holds a valid executor. 206   /** Returns true if this instance holds a valid executor.
207   207  
208   @return `true` if constructed with an executor, `false` if 208   @return `true` if constructed with an executor, `false` if
209   default-constructed. 209   default-constructed.
210   */ 210   */
HITCBC 211   6 explicit operator bool() const noexcept 211   6 explicit operator bool() const noexcept
212   { 212   {
HITCBC 213   6 return p_ != nullptr; 213   6 return p_ != nullptr;
214   } 214   }
215   215  
216   /** Returns a reference to the associated execution context. 216   /** Returns a reference to the associated execution context.
217   217  
218   @return A reference to the execution context. 218   @return A reference to the execution context.
219   219  
220   @pre This instance holds a valid executor. 220   @pre This instance holds a valid executor.
221   */ 221   */
HITCBC 222   6 execution_context& context() const noexcept 222   6 execution_context& context() const noexcept
223   { 223   {
HITCBC 224   6 return p_->context(); 224   6 return p_->context();
225   } 225   }
226   226  
227   /** Informs the executor that work is beginning. 227   /** Informs the executor that work is beginning.
228   228  
229   Must be paired with a subsequent call to `on_work_finished()`. 229   Must be paired with a subsequent call to `on_work_finished()`.
230   230  
231   @pre This instance holds a valid executor. 231   @pre This instance holds a valid executor.
232   */ 232   */
HITCBC 233   5 void on_work_started() const noexcept 233   5 void on_work_started() const noexcept
234   { 234   {
HITCBC 235   5 p_->on_work_started(); 235   5 p_->on_work_started();
HITCBC 236   5 } 236   5 }
237   237  
238   /** Informs the executor that work has completed. 238   /** Informs the executor that work has completed.
239   239  
240   @pre A preceding call to `on_work_started()` was made. 240   @pre A preceding call to `on_work_started()` was made.
241   @pre This instance holds a valid executor. 241   @pre This instance holds a valid executor.
242   */ 242   */
HITCBC 243   5 void on_work_finished() const noexcept 243   5 void on_work_finished() const noexcept
244   { 244   {
HITCBC 245   5 p_->on_work_finished(); 245   5 p_->on_work_finished();
HITCBC 246   5 } 246   5 }
247   247  
248   /** Dispatches a continuation through the wrapped executor. 248   /** Dispatches a continuation through the wrapped executor.
249   249  
250   Returns a handle for symmetric transfer. If running in the 250   Returns a handle for symmetric transfer. If running in the
251   executor's thread, returns `c.h`. Otherwise, posts the 251   executor's thread, returns `c.h`. Otherwise, posts the
252   continuation for later execution and returns 252   continuation for later execution and returns
253   `std::noop_coroutine()`. 253   `std::noop_coroutine()`.
254   254  
255   @param c The continuation to dispatch for resumption. 255   @param c The continuation to dispatch for resumption.
256   Must remain at a stable address until dequeued. 256   Must remain at a stable address until dequeued.
257   257  
258   @return A handle for symmetric transfer or `std::noop_coroutine()`. 258   @return A handle for symmetric transfer or `std::noop_coroutine()`.
259   259  
260   @pre This instance holds a valid executor. 260   @pre This instance holds a valid executor.
261   */ 261   */
HITCBC 262   5 std::coroutine_handle<> dispatch(continuation& c) const 262   5 std::coroutine_handle<> dispatch(continuation& c) const
263   { 263   {
HITCBC 264   5 return p_->dispatch(c); 264   5 return p_->dispatch(c);
265   } 265   }
266   266  
267   /** Posts a continuation to the wrapped executor. 267   /** Posts a continuation to the wrapped executor.
268   268  
269   Posts the continuation to the executor for later execution 269   Posts the continuation to the executor for later execution
270   and returns. The caller should transfer to `std::noop_coroutine()` 270   and returns. The caller should transfer to `std::noop_coroutine()`
271   after calling this. 271   after calling this.
272   272  
273   @param c The continuation to post for resumption. 273   @param c The continuation to post for resumption.
274   Must remain at a stable address until dequeued. 274   Must remain at a stable address until dequeued.
275   275  
276   @pre This instance holds a valid executor. 276   @pre This instance holds a valid executor.
277   */ 277   */
HITCBC 278   15 void post(continuation& c) const 278   15 void post(continuation& c) const
279   { 279   {
HITCBC 280   15 p_->post(c); 280   15 p_->post(c);
HITCBC 281   15 } 281   15 }
282   282  
283   /** Compares two executor wrappers for equality. 283   /** Compares two executor wrappers for equality.
284   284  
285   Two `any_executor` instances are equal if they both hold 285   Two `any_executor` instances are equal if they both hold
286   executors of the same type that compare equal, or if both 286   executors of the same type that compare equal, or if both
287   are empty. 287   are empty.
288   288  
289   @param other The executor to compare against. 289   @param other The executor to compare against.
290   290  
291   @return `true` if both wrap equal executors of the same type, 291   @return `true` if both wrap equal executors of the same type,
292   or both are empty. 292   or both are empty.
293   */ 293   */
HITCBC 294   11 bool operator==(any_executor const& other) const noexcept 294   11 bool operator==(any_executor const& other) const noexcept
295   { 295   {
HITCBC 296   11 if(!p_ && !other.p_) 296   11 if(!p_ && !other.p_)
HITCBC 297   1 return true; 297   1 return true;
HITCBC 298   10 if(!p_ || !other.p_) 298   10 if(!p_ || !other.p_)
HITCBC 299   1 return false; 299   1 return false;
HITCBC 300   9 return p_->equals(other.p_.get()); 300   9 return p_->equals(other.p_.get());
301   } 301   }
302   302  
303   /** Returns the type_info of the wrapped executor. 303   /** Returns the type_info of the wrapped executor.
304   304  
305   @return The `std::type_info` of the stored executor type, 305   @return The `std::type_info` of the stored executor type,
306   or `typeid(void)` if empty. 306   or `typeid(void)` if empty.
307   */ 307   */
HITCBC 308   2 std::type_info const& target_type() const noexcept 308   2 std::type_info const& target_type() const noexcept
309   { 309   {
HITCBC 310   2 if(!p_) 310   2 if(!p_)
HITCBC 311   1 return typeid(void); 311   1 return typeid(void);
HITCBC 312   1 return p_->target_type(); 312   1 return p_->target_type();
313   } 313   }
314   }; 314   };
315   315  
316   } // capy 316   } // capy
317   } // boost 317   } // boost
318   318  
319   #endif 319   #endif