100.00% Lines (60/60) 100.00% Functions (23/23)
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 <boost/capy/detail/type_id.hpp> 16   #include <boost/capy/detail/type_id.hpp>
17   #include <concepts> 17   #include <concepts>
18   #include <coroutine> 18   #include <coroutine>
19   #include <memory> 19   #include <memory>
20   #include <type_traits> 20   #include <type_traits>
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()` report the 57   `operator bool()`, `operator==`, and `target()` report the
58   empty state. `context()`, `on_work_started()`, 58   empty state. `context()`, `on_work_started()`,
59   `on_work_finished()`, `dispatch()`, and `post()` are undefined 59   `on_work_finished()`, `dispatch()`, and `post()` are undefined
60   behavior until an executor is assigned. 60   behavior until an 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   32 virtual ~impl_base() = default; 88   32 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 detail::type_info const& target_type() const noexcept = 0; 95   virtual detail::type_info const& target_type() const noexcept = 0;
96   virtual void const* target_ptr() const noexcept = 0; 96   virtual void const* target_ptr() const noexcept = 0;
97   }; 97   };
98   98  
99   template<class Ex> 99   template<class Ex>
100   struct impl final : impl_base 100   struct impl final : impl_base
101   { 101   {
102   Ex ex_; 102   Ex ex_;
103   103  
104   template<class Ex1> 104   template<class Ex1>
HITCBC 105   32 explicit impl(Ex1&& ex) 105   32 explicit impl(Ex1&& ex)
HITCBC 106   32 : ex_(std::forward<Ex1>(ex)) 106   32 : ex_(std::forward<Ex1>(ex))
107   { 107   {
HITCBC 108   32 } 108   32 }
109   109  
HITCBC 110   8 execution_context& context() const noexcept override 110   8 execution_context& context() const noexcept override
111   { 111   {
HITCBC 112   8 return const_cast<Ex&>(ex_).context(); 112   8 return const_cast<Ex&>(ex_).context();
113   } 113   }
114   114  
HITCBC 115   6 void on_work_started() const noexcept override 115   6 void on_work_started() const noexcept override
116   { 116   {
HITCBC 117   6 ex_.on_work_started(); 117   6 ex_.on_work_started();
HITCBC 118   6 } 118   6 }
119   119  
HITCBC 120   6 void on_work_finished() const noexcept override 120   6 void on_work_finished() const noexcept override
121   { 121   {
HITCBC 122   6 ex_.on_work_finished(); 122   6 ex_.on_work_finished();
HITCBC 123   6 } 123   6 }
124   124  
HITCBC 125   6 std::coroutine_handle<> dispatch(continuation& c) const override 125   6 std::coroutine_handle<> dispatch(continuation& c) const override
126   { 126   {
HITCBC 127   6 return ex_.dispatch(c); 127   6 return ex_.dispatch(c);
128   } 128   }
129   129  
HITCBC 130   15 void post(continuation& c) const override 130   15 void post(continuation& c) const override
131   { 131   {
HITCBC 132   15 ex_.post(c); 132   15 ex_.post(c);
HITCBC 133   15 } 133   15 }
134   134  
HITCBC 135   12 bool equals(impl_base const* other) const noexcept override 135   12 bool equals(impl_base const* other) const noexcept override
136   { 136   {
HITCBC 137   12 if(target_type() != other->target_type()) 137   12 if(target_type() != other->target_type())
HITCBC 138   2 return false; 138   2 return false;
HITCBC 139   10 return ex_ == static_cast<impl const*>(other)->ex_; 139   10 return ex_ == static_cast<impl const*>(other)->ex_;
140   } 140   }
141   141  
HITCBC 142   44 detail::type_info const& target_type() const noexcept override 142   44 detail::type_info const& target_type() const noexcept override
143   { 143   {
HITCBC 144   44 return detail::type_id<Ex>(); 144   44 return detail::type_id<Ex>();
145   } 145   }
146   146  
HITCBC 147   15 void const* target_ptr() const noexcept override 147   15 void const* target_ptr() const noexcept override
148   { 148   {
HITCBC 149   15 return &ex_; 149   15 return &ex_;
150   } 150   }
151   }; 151   };
152   152  
153   public: 153   public:
154   /** Construct a default instance. 154   /** Construct a default instance.
155   155  
156   Constructs an empty `any_executor`. `operator bool()` reports 156   Constructs an empty `any_executor`. `operator bool()` reports
157   the empty state; `context()`, `on_work_started()`, 157   the empty state; `context()`, `on_work_started()`,
158   `on_work_finished()`, `dispatch()`, and `post()` are undefined 158   `on_work_finished()`, `dispatch()`, and `post()` are undefined
159   behavior until an executor is assigned. 159   behavior until an executor is assigned.
160   160  
161   @par Postconditions 161   @par Postconditions
162   @li `!*this` 162   @li `!*this`
163   */ 163   */
HITCBC 164   2 any_executor() = default; 164   2 any_executor() = default;
165   165  
166   /** Construct a copy. 166   /** Construct a copy.
167   167  
168   Creates a new `any_executor` sharing ownership of the 168   Creates a new `any_executor` sharing ownership of the
169   underlying executor with `other`. 169   underlying executor with `other`.
170   170  
171   @param other The executor to copy. 171   @param other The executor to copy.
172   172  
173   @par Postconditions 173   @par Postconditions
174   @li `*this == other` 174   @li `*this == other`
175   */ 175   */
HITCBC 176   41 any_executor(any_executor const& other) = default; 176   41 any_executor(any_executor const& other) = default;
177   177  
178   /** Copy assignment operator. 178   /** Copy assignment operator.
179   179  
180   Shares ownership of the underlying executor with `other`. 180   Shares ownership of the underlying executor with `other`.
181   181  
182   @param other The executor to copy. 182   @param other The executor to copy.
183   183  
184   @return A reference to `*this`. 184   @return A reference to `*this`.
185   185  
186   @par Postconditions 186   @par Postconditions
187   @li `*this == other` 187   @li `*this == other`
188   */ 188   */
HITCBC 189   6 any_executor& operator=(any_executor const& other) = default; 189   6 any_executor& operator=(any_executor const& other) = default;
190   190  
191   /** Constructs from any executor type. 191   /** Constructs from any executor type.
192   192  
193   Allocates storage for a copy of the given executor and 193   Allocates storage for a copy of the given executor and
194   stores it internally. The executor must satisfy the 194   stores it internally. The executor must satisfy the
195   `Executor` concept. 195   `Executor` concept.
196   196  
197   @param ex The executor to wrap. A copy is stored internally. 197   @param ex The executor to wrap. A copy is stored internally.
198   198  
199   @par Postconditions 199   @par Postconditions
200   @li `*this` is valid 200   @li `*this` is valid
201   */ 201   */
202   template<class Ex> 202   template<class Ex>
203   requires ( 203   requires (
204   !std::same_as<std::decay_t<Ex>, any_executor> && 204   !std::same_as<std::decay_t<Ex>, any_executor> &&
205   !detail::is_strand_type<std::decay_t<Ex>>::value && 205   !detail::is_strand_type<std::decay_t<Ex>>::value &&
206   std::copy_constructible<std::decay_t<Ex>>) 206   std::copy_constructible<std::decay_t<Ex>>)
HITCBC 207   32 any_executor(Ex&& ex) 207   32 any_executor(Ex&& ex)
HITCBC 208   32 : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex))) 208   32 : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
209   { 209   {
HITCBC 210   32 } 210   32 }
211   211  
212   /** Returns true if this instance holds a valid executor. 212   /** Returns true if this instance holds a valid executor.
213   213  
214   @return `true` if constructed with an executor, `false` if 214   @return `true` if constructed with an executor, `false` if
215   default-constructed. 215   default-constructed.
216   */ 216   */
HITCBC 217   6 explicit operator bool() const noexcept 217   6 explicit operator bool() const noexcept
218   { 218   {
HITCBC 219   6 return p_ != nullptr; 219   6 return p_ != nullptr;
220   } 220   }
221   221  
222   /** Returns a reference to the associated execution context. 222   /** Returns a reference to the associated execution context.
223   223  
224   @return A reference to the execution context. 224   @return A reference to the execution context.
225   225  
226   @pre This instance holds a valid executor. 226   @pre This instance holds a valid executor.
227   */ 227   */
HITCBC 228   8 execution_context& context() const noexcept 228   8 execution_context& context() const noexcept
229   { 229   {
HITCBC 230   8 return p_->context(); 230   8 return p_->context();
231   } 231   }
232   232  
233   /** Informs the executor that work is beginning. 233   /** Informs the executor that work is beginning.
234   234  
235   Must be paired with a subsequent call to `on_work_finished()`. 235   Must be paired with a subsequent call to `on_work_finished()`.
236   236  
237   @pre This instance holds a valid executor. 237   @pre This instance holds a valid executor.
238   */ 238   */
HITCBC 239   6 void on_work_started() const noexcept 239   6 void on_work_started() const noexcept
240   { 240   {
HITCBC 241   6 p_->on_work_started(); 241   6 p_->on_work_started();
HITCBC 242   6 } 242   6 }
243   243  
244   /** Informs the executor that work has completed. 244   /** Informs the executor that work has completed.
245   245  
246   @pre A preceding call to `on_work_started()` was made. 246   @pre A preceding call to `on_work_started()` was made.
247   @pre This instance holds a valid executor. 247   @pre This instance holds a valid executor.
248   */ 248   */
HITCBC 249   6 void on_work_finished() const noexcept 249   6 void on_work_finished() const noexcept
250   { 250   {
HITCBC 251   6 p_->on_work_finished(); 251   6 p_->on_work_finished();
HITCBC 252   6 } 252   6 }
253   253  
254   /** Dispatches a continuation through the wrapped executor. 254   /** Dispatches a continuation through the wrapped executor.
255   255  
256   Returns a handle for symmetric transfer. If running in the 256   Returns a handle for symmetric transfer. If running in the
257   executor's thread, returns `c.h`. Otherwise, posts the 257   executor's thread, returns `c.h`. Otherwise, posts the
258   continuation for later execution and returns 258   continuation for later execution and returns
259   `std::noop_coroutine()`. 259   `std::noop_coroutine()`.
260   260  
261   @param c The continuation to dispatch for resumption. 261   @param c The continuation to dispatch for resumption.
262   Must remain at a stable address until dequeued. 262   Must remain at a stable address until dequeued.
263   263  
264   @return A handle for symmetric transfer or `std::noop_coroutine()`. 264   @return A handle for symmetric transfer or `std::noop_coroutine()`.
265   265  
266   @pre This instance holds a valid executor. 266   @pre This instance holds a valid executor.
267   */ 267   */
HITCBC 268   6 std::coroutine_handle<> dispatch(continuation& c) const 268   6 std::coroutine_handle<> dispatch(continuation& c) const
269   { 269   {
HITCBC 270   6 return p_->dispatch(c); 270   6 return p_->dispatch(c);
271   } 271   }
272   272  
273   /** Posts a continuation to the wrapped executor. 273   /** Posts a continuation to the wrapped executor.
274   274  
275   Posts the continuation to the executor for later execution 275   Posts the continuation to the executor for later execution
276   and returns. The caller should transfer to `std::noop_coroutine()` 276   and returns. The caller should transfer to `std::noop_coroutine()`
277   after calling this. 277   after calling this.
278   278  
279   @param c The continuation to post for resumption. 279   @param c The continuation to post for resumption.
280   Must remain at a stable address until dequeued. 280   Must remain at a stable address until dequeued.
281   281  
282   @pre This instance holds a valid executor. 282   @pre This instance holds a valid executor.
283   */ 283   */
HITCBC 284   15 void post(continuation& c) const 284   15 void post(continuation& c) const
285   { 285   {
HITCBC 286   15 p_->post(c); 286   15 p_->post(c);
HITCBC 287   15 } 287   15 }
288   288  
289   /** Compares two executor wrappers for equality. 289   /** Compares two executor wrappers for equality.
290   290  
291   Two `any_executor` instances are equal if they both hold 291   Two `any_executor` instances are equal if they both hold
292   executors of the same type that compare equal, or if both 292   executors of the same type that compare equal, or if both
293   are empty. 293   are empty.
294   294  
295   @param other The executor to compare against. 295   @param other The executor to compare against.
296   296  
297   @return `true` if both wrap equal executors of the same type, 297   @return `true` if both wrap equal executors of the same type,
298   or both are empty. 298   or both are empty.
299   */ 299   */
HITCBC 300   14 bool operator==(any_executor const& other) const noexcept 300   14 bool operator==(any_executor const& other) const noexcept
301   { 301   {
HITCBC 302   14 if(!p_ && !other.p_) 302   14 if(!p_ && !other.p_)
HITCBC 303   1 return true; 303   1 return true;
HITCBC 304   13 if(!p_ || !other.p_) 304   13 if(!p_ || !other.p_)
HITCBC 305   1 return false; 305   1 return false;
HITCBC 306   12 return p_->equals(other.p_.get()); 306   12 return p_->equals(other.p_.get());
307   } 307   }
308   308  
309   /** Return a pointer to the wrapped executor if it matches 309   /** Return a pointer to the wrapped executor if it matches
310   the requested type. 310   the requested type.
311   311  
312   Analogous to `std::any_cast<Ex>(&a)`. Top-level cv-qualifiers 312   Analogous to `std::any_cast<Ex>(&a)`. Top-level cv-qualifiers
313   on `Ex` are ignored when matching. 313   on `Ex` are ignored when matching.
314   314  
315   Copies of an `any_executor` share one stored executor, so 315   Copies of an `any_executor` share one stored executor, so
316   every copy returns the same address. A modification made 316   every copy returns the same address. A modification made
317   through the non-const overload is visible to all of them. 317   through the non-const overload is visible to all of them.
318   Such modifications are not synchronized with concurrent use 318   Such modifications are not synchronized with concurrent use
319   of any copy. 319   of any copy.
320   320  
321   @tparam Ex The executor type to retrieve. 321   @tparam Ex The executor type to retrieve.
322   322  
323   @return A pointer to the stored executor, or `nullptr` if 323   @return A pointer to the stored executor, or `nullptr` if
324   this instance is empty or holds a different type. 324   this instance is empty or holds a different type.
325   */ 325   */
326   template<class Ex> 326   template<class Ex>
HITCBC 327   5 Ex const* target() const noexcept 327   5 Ex const* target() const noexcept
328   { 328   {
HITCBC 329   5 if(!p_ || p_->target_type() != detail::type_id<Ex>()) 329   5 if(!p_ || p_->target_type() != detail::type_id<Ex>())
HITCBC 330   3 return nullptr; 330   3 return nullptr;
331   // Casting from void const* avoids naming impl<Ex>, which would 331   // Casting from void const* avoids naming impl<Ex>, which would
332   // instantiate its overrides and reject non-executor types 332   // instantiate its overrides and reject non-executor types
HITCBC 333   2 return static_cast<Ex const*>(p_->target_ptr()); 333   2 return static_cast<Ex const*>(p_->target_ptr());
334   } 334   }
335   335  
336   /// @copydoc target() const 336   /// @copydoc target() const
337   template<class Ex> 337   template<class Ex>
HITCBC 338   18 Ex* target() noexcept 338   18 Ex* target() noexcept
339   { 339   {
HITCBC 340   18 if(!p_ || p_->target_type() != detail::type_id<Ex>()) 340   18 if(!p_ || p_->target_type() != detail::type_id<Ex>())
HITCBC 341   5 return nullptr; 341   5 return nullptr;
342   return const_cast<Ex*>( 342   return const_cast<Ex*>(
HITCBC 343   13 static_cast<Ex const*>(p_->target_ptr())); 343   13 static_cast<Ex const*>(p_->target_ptr()));
344   } 344   }
345   }; 345   };
346   346  
347   } // capy 347   } // capy
348   } // boost 348   } // boost
349   349  
350   #endif 350   #endif