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