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 + @par !example example
76 - any_executor exec = ctx.get_executor(); 76 +
77 - if(exec)  
78 - {  
79 - auto& context = exec.context();  
80 - exec.post(my_coroutine);  
81 - }  
82 - @endcode  
83   77  
84   @see executor_ref, Executor 78   @see executor_ref, Executor
85   */ 79   */
86   class any_executor 80   class any_executor
87   { 81   {
88   struct impl_base; 82   struct impl_base;
89   83  
90   std::shared_ptr<impl_base> p_; 84   std::shared_ptr<impl_base> p_;
91   85  
92   struct impl_base 86   struct impl_base
93   { 87   {
HITCBC 94   20 virtual ~impl_base() = default; 88   20 virtual ~impl_base() = default;
95   virtual execution_context& context() const noexcept = 0; 89   virtual execution_context& context() const noexcept = 0;
96   virtual void on_work_started() const noexcept = 0; 90   virtual void on_work_started() const noexcept = 0;
97   virtual void on_work_finished() const noexcept = 0; 91   virtual void on_work_finished() const noexcept = 0;
98   virtual std::coroutine_handle<> dispatch(continuation&) const = 0; 92   virtual std::coroutine_handle<> dispatch(continuation&) const = 0;
99   virtual void post(continuation&) const = 0; 93   virtual void post(continuation&) const = 0;
100   virtual bool equals(impl_base const*) const noexcept = 0; 94   virtual bool equals(impl_base const*) const noexcept = 0;
101   virtual std::type_info const& target_type() const noexcept = 0; 95   virtual std::type_info const& target_type() const noexcept = 0;
102   }; 96   };
103   97  
104   template<class Ex> 98   template<class Ex>
105   struct impl final : impl_base 99   struct impl final : impl_base
106   { 100   {
107   Ex ex_; 101   Ex ex_;
108   102  
109   template<class Ex1> 103   template<class Ex1>
HITCBC 110   20 explicit impl(Ex1&& ex) 104   20 explicit impl(Ex1&& ex)
HITCBC 111   20 : ex_(std::forward<Ex1>(ex)) 105   20 : ex_(std::forward<Ex1>(ex))
112   { 106   {
HITCBC 113   20 } 107   20 }
114   108  
HITCBC 115   6 execution_context& context() const noexcept override 109   6 execution_context& context() const noexcept override
116   { 110   {
HITCBC 117   6 return const_cast<Ex&>(ex_).context(); 111   6 return const_cast<Ex&>(ex_).context();
118   } 112   }
119   113  
HITCBC 120   5 void on_work_started() const noexcept override 114   5 void on_work_started() const noexcept override
121   { 115   {
HITCBC 122   5 ex_.on_work_started(); 116   5 ex_.on_work_started();
HITCBC 123   5 } 117   5 }
124   118  
HITCBC 125   5 void on_work_finished() const noexcept override 119   5 void on_work_finished() const noexcept override
126   { 120   {
HITCBC 127   5 ex_.on_work_finished(); 121   5 ex_.on_work_finished();
HITCBC 128   5 } 122   5 }
129   123  
HITCBC 130   5 std::coroutine_handle<> dispatch(continuation& c) const override 124   5 std::coroutine_handle<> dispatch(continuation& c) const override
131   { 125   {
HITCBC 132   5 return ex_.dispatch(c); 126   5 return ex_.dispatch(c);
133   } 127   }
134   128  
HITCBC 135   15 void post(continuation& c) const override 129   15 void post(continuation& c) const override
136   { 130   {
HITCBC 137   15 ex_.post(c); 131   15 ex_.post(c);
HITCBC 138   15 } 132   15 }
139   133  
HITCBC 140   9 bool equals(impl_base const* other) const noexcept override 134   9 bool equals(impl_base const* other) const noexcept override
141   { 135   {
HITCBC 142   9 if(target_type() != other->target_type()) 136   9 if(target_type() != other->target_type())
HITCBC 143   1 return false; 137   1 return false;
HITCBC 144   8 return ex_ == static_cast<impl const*>(other)->ex_; 138   8 return ex_ == static_cast<impl const*>(other)->ex_;
145   } 139   }
146   140  
HITCBC 147   19 std::type_info const& target_type() const noexcept override 141   19 std::type_info const& target_type() const noexcept override
148   { 142   {
HITCBC 149   19 return typeid(Ex); 143   19 return typeid(Ex);
150   } 144   }
151   }; 145   };
152   146  
153   public: 147   public:
154   /** Construct a default instance. 148   /** Construct a default instance.
155   149  
156   Constructs an empty `any_executor`. `operator bool()` reports 150   Constructs an empty `any_executor`. `operator bool()` reports
157   the empty state; `context()`, `on_work_started()`, 151   the empty state; `context()`, `on_work_started()`,
158   `on_work_finished()`, `dispatch()`, and `post()` are undefined 152   `on_work_finished()`, `dispatch()`, and `post()` are undefined
159   behavior until an executor is assigned. 153   behavior until an executor is assigned.
160   154  
161   @par Postconditions 155   @par Postconditions
162   @li `!*this` 156   @li `!*this`
163   */ 157   */
HITCBC 164   2 any_executor() = default; 158   2 any_executor() = default;
165   159  
166   /** Construct a copy. 160   /** Construct a copy.
167   161  
168   Creates a new `any_executor` sharing ownership of the 162   Creates a new `any_executor` sharing ownership of the
169   underlying executor with `other`. 163   underlying executor with `other`.
170   164  
171   @param other The executor to copy. 165   @param other The executor to copy.
172   166  
173   @par Postconditions 167   @par Postconditions
174   @li `*this == other` 168   @li `*this == other`
175   */ 169   */
HITCBC 176   33 any_executor(any_executor const& other) = default; 170   33 any_executor(any_executor const& other) = default;
177   171  
178   /** Copy assignment operator. 172   /** Copy assignment operator.
179   173  
180   Shares ownership of the underlying executor with `other`. 174   Shares ownership of the underlying executor with `other`.
181   175  
182   @param other The executor to copy. 176   @param other The executor to copy.
183   177  
184   @return A reference to `*this`. 178   @return A reference to `*this`.
185   179  
186   @par Postconditions 180   @par Postconditions
187   @li `*this == other` 181   @li `*this == other`
188   */ 182   */
HITCBC 189   6 any_executor& operator=(any_executor const& other) = default; 183   6 any_executor& operator=(any_executor const& other) = default;
190   184  
191   /** Constructs from any executor type. 185   /** Constructs from any executor type.
192   186  
193   Allocates storage for a copy of the given executor and 187   Allocates storage for a copy of the given executor and
194   stores it internally. The executor must satisfy the 188   stores it internally. The executor must satisfy the
195   `Executor` concept. 189   `Executor` concept.
196   190  
197   @param ex The executor to wrap. A copy is stored internally. 191   @param ex The executor to wrap. A copy is stored internally.
198   192  
199   @par Postconditions 193   @par Postconditions
200   @li `*this` is valid 194   @li `*this` is valid
201   */ 195   */
202   template<class Ex> 196   template<class Ex>
203   requires ( 197   requires (
204   !std::same_as<std::decay_t<Ex>, any_executor> && 198   !std::same_as<std::decay_t<Ex>, any_executor> &&
205   !detail::is_strand_type<std::decay_t<Ex>>::value && 199   !detail::is_strand_type<std::decay_t<Ex>>::value &&
206   std::copy_constructible<std::decay_t<Ex>>) 200   std::copy_constructible<std::decay_t<Ex>>)
HITCBC 207   20 any_executor(Ex&& ex) 201   20 any_executor(Ex&& ex)
HITCBC 208   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)))
209   { 203   {
HITCBC 210   20 } 204   20 }
211   205  
212   /** Returns true if this instance holds a valid executor. 206   /** Returns true if this instance holds a valid executor.
213   207  
214   @return `true` if constructed with an executor, `false` if 208   @return `true` if constructed with an executor, `false` if
215   default-constructed. 209   default-constructed.
216   */ 210   */
HITCBC 217   6 explicit operator bool() const noexcept 211   6 explicit operator bool() const noexcept
218   { 212   {
HITCBC 219   6 return p_ != nullptr; 213   6 return p_ != nullptr;
220   } 214   }
221   215  
222   /** Returns a reference to the associated execution context. 216   /** Returns a reference to the associated execution context.
223   217  
224   @return A reference to the execution context. 218   @return A reference to the execution context.
225   219  
226   @pre This instance holds a valid executor. 220   @pre This instance holds a valid executor.
227   */ 221   */
HITCBC 228   6 execution_context& context() const noexcept 222   6 execution_context& context() const noexcept
229   { 223   {
HITCBC 230   6 return p_->context(); 224   6 return p_->context();
231   } 225   }
232   226  
233   /** Informs the executor that work is beginning. 227   /** Informs the executor that work is beginning.
234   228  
235   Must be paired with a subsequent call to `on_work_finished()`. 229   Must be paired with a subsequent call to `on_work_finished()`.
236   230  
237   @pre This instance holds a valid executor. 231   @pre This instance holds a valid executor.
238   */ 232   */
HITCBC 239   5 void on_work_started() const noexcept 233   5 void on_work_started() const noexcept
240   { 234   {
HITCBC 241   5 p_->on_work_started(); 235   5 p_->on_work_started();
HITCBC 242   5 } 236   5 }
243   237  
244   /** Informs the executor that work has completed. 238   /** Informs the executor that work has completed.
245   239  
246   @pre A preceding call to `on_work_started()` was made. 240   @pre A preceding call to `on_work_started()` was made.
247   @pre This instance holds a valid executor. 241   @pre This instance holds a valid executor.
248   */ 242   */
HITCBC 249   5 void on_work_finished() const noexcept 243   5 void on_work_finished() const noexcept
250   { 244   {
HITCBC 251   5 p_->on_work_finished(); 245   5 p_->on_work_finished();
HITCBC 252   5 } 246   5 }
253   247  
254   /** Dispatches a continuation through the wrapped executor. 248   /** Dispatches a continuation through the wrapped executor.
255   249  
256   Returns a handle for symmetric transfer. If running in the 250   Returns a handle for symmetric transfer. If running in the
257   executor's thread, returns `c.h`. Otherwise, posts the 251   executor's thread, returns `c.h`. Otherwise, posts the
258   continuation for later execution and returns 252   continuation for later execution and returns
259   `std::noop_coroutine()`. 253   `std::noop_coroutine()`.
260   254  
261   @param c The continuation to dispatch for resumption. 255   @param c The continuation to dispatch for resumption.
262   Must remain at a stable address until dequeued. 256   Must remain at a stable address until dequeued.
263   257  
264   @return A handle for symmetric transfer or `std::noop_coroutine()`. 258   @return A handle for symmetric transfer or `std::noop_coroutine()`.
265   259  
266   @pre This instance holds a valid executor. 260   @pre This instance holds a valid executor.
267   */ 261   */
HITCBC 268   5 std::coroutine_handle<> dispatch(continuation& c) const 262   5 std::coroutine_handle<> dispatch(continuation& c) const
269   { 263   {
HITCBC 270   5 return p_->dispatch(c); 264   5 return p_->dispatch(c);
271   } 265   }
272   266  
273   /** Posts a continuation to the wrapped executor. 267   /** Posts a continuation to the wrapped executor.
274   268  
275   Posts the continuation to the executor for later execution 269   Posts the continuation to the executor for later execution
276   and returns. The caller should transfer to `std::noop_coroutine()` 270   and returns. The caller should transfer to `std::noop_coroutine()`
277   after calling this. 271   after calling this.
278   272  
279   @param c The continuation to post for resumption. 273   @param c The continuation to post for resumption.
280   Must remain at a stable address until dequeued. 274   Must remain at a stable address until dequeued.
281   275  
282   @pre This instance holds a valid executor. 276   @pre This instance holds a valid executor.
283   */ 277   */
HITCBC 284   15 void post(continuation& c) const 278   15 void post(continuation& c) const
285   { 279   {
HITCBC 286   15 p_->post(c); 280   15 p_->post(c);
HITCBC 287   15 } 281   15 }
288   282  
289   /** Compares two executor wrappers for equality. 283   /** Compares two executor wrappers for equality.
290   284  
291   Two `any_executor` instances are equal if they both hold 285   Two `any_executor` instances are equal if they both hold
292   executors of the same type that compare equal, or if both 286   executors of the same type that compare equal, or if both
293   are empty. 287   are empty.
294   288  
295   @param other The executor to compare against. 289   @param other The executor to compare against.
296   290  
297   @return `true` if both wrap equal executors of the same type, 291   @return `true` if both wrap equal executors of the same type,
298   or both are empty. 292   or both are empty.
299   */ 293   */
HITCBC 300   11 bool operator==(any_executor const& other) const noexcept 294   11 bool operator==(any_executor const& other) const noexcept
301   { 295   {
HITCBC 302   11 if(!p_ && !other.p_) 296   11 if(!p_ && !other.p_)
HITCBC 303   1 return true; 297   1 return true;
HITCBC 304   10 if(!p_ || !other.p_) 298   10 if(!p_ || !other.p_)
HITCBC 305   1 return false; 299   1 return false;
HITCBC 306   9 return p_->equals(other.p_.get()); 300   9 return p_->equals(other.p_.get());
307   } 301   }
308   302  
309   /** Returns the type_info of the wrapped executor. 303   /** Returns the type_info of the wrapped executor.
310   304  
311   @return The `std::type_info` of the stored executor type, 305   @return The `std::type_info` of the stored executor type,
312   or `typeid(void)` if empty. 306   or `typeid(void)` if empty.
313   */ 307   */
HITCBC 314   2 std::type_info const& target_type() const noexcept 308   2 std::type_info const& target_type() const noexcept
315   { 309   {
HITCBC 316   2 if(!p_) 310   2 if(!p_)
HITCBC 317   1 return typeid(void); 311   1 return typeid(void);
HITCBC 318   1 return p_->target_type(); 312   1 return p_->target_type();
319   } 313   }
320   }; 314   };
321   315  
322   } // capy 316   } // capy
323   } // boost 317   } // boost
324   318  
325   #endif 319   #endif