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   @code 75   @code
76   any_executor exec = ctx.get_executor(); 76   any_executor exec = ctx.get_executor();
77   if(exec) 77   if(exec)
78   { 78   {
79   auto& context = exec.context(); 79   auto& context = exec.context();
80   exec.post(my_coroutine); 80   exec.post(my_coroutine);
81   } 81   }
82   @endcode 82   @endcode
83   83  
84   @see executor_ref, Executor 84   @see executor_ref, Executor
85   */ 85   */
86   class any_executor 86   class any_executor
87   { 87   {
88   struct impl_base; 88   struct impl_base;
89   89  
90   std::shared_ptr<impl_base> p_; 90   std::shared_ptr<impl_base> p_;
91   91  
92   struct impl_base 92   struct impl_base
93   { 93   {
HITCBC 94   20 virtual ~impl_base() = default; 94   20 virtual ~impl_base() = default;
95   virtual execution_context& context() const noexcept = 0; 95   virtual execution_context& context() const noexcept = 0;
96   virtual void on_work_started() const noexcept = 0; 96   virtual void on_work_started() const noexcept = 0;
97   virtual void on_work_finished() const noexcept = 0; 97   virtual void on_work_finished() const noexcept = 0;
98   virtual std::coroutine_handle<> dispatch(continuation&) const = 0; 98   virtual std::coroutine_handle<> dispatch(continuation&) const = 0;
99   virtual void post(continuation&) const = 0; 99   virtual void post(continuation&) const = 0;
100   virtual bool equals(impl_base const*) const noexcept = 0; 100   virtual bool equals(impl_base const*) const noexcept = 0;
101   virtual std::type_info const& target_type() const noexcept = 0; 101   virtual std::type_info const& target_type() const noexcept = 0;
102   }; 102   };
103   103  
104   template<class Ex> 104   template<class Ex>
105   struct impl final : impl_base 105   struct impl final : impl_base
106   { 106   {
107   Ex ex_; 107   Ex ex_;
108   108  
109   template<class Ex1> 109   template<class Ex1>
HITCBC 110   20 explicit impl(Ex1&& ex) 110   20 explicit impl(Ex1&& ex)
HITCBC 111   20 : ex_(std::forward<Ex1>(ex)) 111   20 : ex_(std::forward<Ex1>(ex))
112   { 112   {
HITCBC 113   20 } 113   20 }
114   114  
HITCBC 115   6 execution_context& context() const noexcept override 115   6 execution_context& context() const noexcept override
116   { 116   {
HITCBC 117   6 return const_cast<Ex&>(ex_).context(); 117   6 return const_cast<Ex&>(ex_).context();
118   } 118   }
119   119  
HITCBC 120   5 void on_work_started() const noexcept override 120   5 void on_work_started() const noexcept override
121   { 121   {
HITCBC 122   5 ex_.on_work_started(); 122   5 ex_.on_work_started();
HITCBC 123   5 } 123   5 }
124   124  
HITCBC 125   5 void on_work_finished() const noexcept override 125   5 void on_work_finished() const noexcept override
126   { 126   {
HITCBC 127   5 ex_.on_work_finished(); 127   5 ex_.on_work_finished();
HITCBC 128   5 } 128   5 }
129   129  
HITCBC 130   5 std::coroutine_handle<> dispatch(continuation& c) const override 130   5 std::coroutine_handle<> dispatch(continuation& c) const override
131   { 131   {
HITCBC 132   5 return ex_.dispatch(c); 132   5 return ex_.dispatch(c);
133   } 133   }
134   134  
HITCBC 135   16 void post(continuation& c) const override 135   15 void post(continuation& c) const override
136   { 136   {
HITCBC 137   16 ex_.post(c); 137   15 ex_.post(c);
HITCBC 138   16 } 138   15 }
139   139  
HITCBC 140   9 bool equals(impl_base const* other) const noexcept override 140   9 bool equals(impl_base const* other) const noexcept override
141   { 141   {
HITCBC 142   9 if(target_type() != other->target_type()) 142   9 if(target_type() != other->target_type())
HITCBC 143   1 return false; 143   1 return false;
HITCBC 144   8 return ex_ == static_cast<impl const*>(other)->ex_; 144   8 return ex_ == static_cast<impl const*>(other)->ex_;
145   } 145   }
146   146  
HITCBC 147   19 std::type_info const& target_type() const noexcept override 147   19 std::type_info const& target_type() const noexcept override
148   { 148   {
HITCBC 149   19 return typeid(Ex); 149   19 return typeid(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   33 any_executor(any_executor const& other) = default; 176   33 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   20 any_executor(Ex&& ex) 207   20 any_executor(Ex&& ex)
HITCBC 208   20 : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex))) 208   20 : p_(std::make_shared<impl<std::decay_t<Ex>>>(std::forward<Ex>(ex)))
209   { 209   {
HITCBC 210   20 } 210   20 }
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   6 execution_context& context() const noexcept 228   6 execution_context& context() const noexcept
229   { 229   {
HITCBC 230   6 return p_->context(); 230   6 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   5 void on_work_started() const noexcept 239   5 void on_work_started() const noexcept
240   { 240   {
HITCBC 241   5 p_->on_work_started(); 241   5 p_->on_work_started();
HITCBC 242   5 } 242   5 }
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   5 void on_work_finished() const noexcept 249   5 void on_work_finished() const noexcept
250   { 250   {
HITCBC 251   5 p_->on_work_finished(); 251   5 p_->on_work_finished();
HITCBC 252   5 } 252   5 }
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   5 std::coroutine_handle<> dispatch(continuation& c) const 268   5 std::coroutine_handle<> dispatch(continuation& c) const
269   { 269   {
HITCBC 270   5 return p_->dispatch(c); 270   5 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   16 void post(continuation& c) const 284   15 void post(continuation& c) const
285   { 285   {
HITCBC 286   16 p_->post(c); 286   15 p_->post(c);
HITCBC 287   16 } 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   11 bool operator==(any_executor const& other) const noexcept 300   11 bool operator==(any_executor const& other) const noexcept
301   { 301   {
HITCBC 302   11 if(!p_ && !other.p_) 302   11 if(!p_ && !other.p_)
HITCBC 303   1 return true; 303   1 return true;
HITCBC 304   10 if(!p_ || !other.p_) 304   10 if(!p_ || !other.p_)
HITCBC 305   1 return false; 305   1 return false;
HITCBC 306   9 return p_->equals(other.p_.get()); 306   9 return p_->equals(other.p_.get());
307   } 307   }
308   308  
309   /** Returns the type_info of the wrapped executor. 309   /** Returns the type_info of the wrapped executor.
310   310  
311   @return The `std::type_info` of the stored executor type, 311   @return The `std::type_info` of the stored executor type,
312   or `typeid(void)` if empty. 312   or `typeid(void)` if empty.
313   */ 313   */
HITCBC 314   2 std::type_info const& target_type() const noexcept 314   2 std::type_info const& target_type() const noexcept
315   { 315   {
HITCBC 316   2 if(!p_) 316   2 if(!p_)
HITCBC 317   1 return typeid(void); 317   1 return typeid(void);
HITCBC 318   1 return p_->target_type(); 318   1 return p_->target_type();
319   } 319   }
320   }; 320   };
321   321  
322   } // capy 322   } // capy
323   } // boost 323   } // boost
324   324  
325   #endif 325   #endif