100.00% Lines (46/46) 100.00% Functions (17/17)
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_EXECUTOR_REF_HPP 11   #ifndef BOOST_CAPY_EXECUTOR_REF_HPP
12   #define BOOST_CAPY_EXECUTOR_REF_HPP 12   #define BOOST_CAPY_EXECUTOR_REF_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/detail/type_id.hpp> 15   #include <boost/capy/detail/type_id.hpp>
16   #include <boost/capy/continuation.hpp> 16   #include <boost/capy/continuation.hpp>
17   #include <concepts> 17   #include <concepts>
18   #include <coroutine> 18   #include <coroutine>
19   #include <type_traits> 19   #include <type_traits>
20   #include <utility> 20   #include <utility>
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   26  
27   namespace detail { 27   namespace detail {
28   28  
29   /** Virtual function table for type-erased executor operations. */ 29   /** Virtual function table for type-erased executor operations. */
30   struct executor_vtable 30   struct executor_vtable
31   { 31   {
32   execution_context& (*context)(void const*) noexcept; 32   execution_context& (*context)(void const*) noexcept;
33   void (*on_work_started)(void const*) noexcept; 33   void (*on_work_started)(void const*) noexcept;
34   void (*on_work_finished)(void const*) noexcept; 34   void (*on_work_finished)(void const*) noexcept;
35   void (*post)(void const*, continuation&); 35   void (*post)(void const*, continuation&);
36   std::coroutine_handle<> (*dispatch)(void const*, continuation&); 36   std::coroutine_handle<> (*dispatch)(void const*, continuation&);
37   bool (*equals)(void const*, void const*) noexcept; 37   bool (*equals)(void const*, void const*) noexcept;
38   detail::type_info const* type_id; 38   detail::type_info const* type_id;
39   }; 39   };
40   40  
41   /** Vtable instance for a specific executor type. */ 41   /** Vtable instance for a specific executor type. */
42   template<class Ex> 42   template<class Ex>
43   inline constexpr executor_vtable vtable_for = { 43   inline constexpr executor_vtable vtable_for = {
44   // context 44   // context
HITCBC 45   1 [](void const* p) noexcept -> execution_context& { 45   1 [](void const* p) noexcept -> execution_context& {
HITCBC 46   1 return const_cast<Ex*>(static_cast<Ex const*>(p))->context(); 46   1 return const_cast<Ex*>(static_cast<Ex const*>(p))->context();
47   }, 47   },
48   // on_work_started 48   // on_work_started
HITCBC 49   2 [](void const* p) noexcept { 49   2 [](void const* p) noexcept {
HITCBC 50   1 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_started(); 50   1 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_started();
51   }, 51   },
52   // on_work_finished 52   // on_work_finished
HITCBC 53   2 [](void const* p) noexcept { 53   2 [](void const* p) noexcept {
HITCBC 54   1 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_finished(); 54   1 const_cast<Ex*>(static_cast<Ex const*>(p))->on_work_finished();
55   }, 55   },
56   // post 56   // post
HITCBC 57   38740 [](void const* p, continuation& c) { 57   34994 [](void const* p, continuation& c) {
HITCBC 58   19370 static_cast<Ex const*>(p)->post(c); 58   17497 static_cast<Ex const*>(p)->post(c);
59   }, 59   },
60   // dispatch 60   // dispatch
HITCBC 61   122 [](void const* p, continuation& c) -> std::coroutine_handle<> { 61   122 [](void const* p, continuation& c) -> std::coroutine_handle<> {
HITCBC 62   122 return static_cast<Ex const*>(p)->dispatch(c); 62   122 return static_cast<Ex const*>(p)->dispatch(c);
63   }, 63   },
64   // equals 64   // equals
HITCBC 65   1 [](void const* a, void const* b) noexcept -> bool { 65   1 [](void const* a, void const* b) noexcept -> bool {
HITCBC 66   1 return *static_cast<Ex const*>(a) == *static_cast<Ex const*>(b); 66   1 return *static_cast<Ex const*>(a) == *static_cast<Ex const*>(b);
67   }, 67   },
68   // type_id 68   // type_id
69   &detail::type_id<Ex>() 69   &detail::type_id<Ex>()
70   }; 70   };
71   71  
72   } // detail 72   } // detail
73   73  
74   /** Forwards `dispatch`/`post`/`context` calls through a non-owning, type-erased executor pointer. 74   /** Forwards `dispatch`/`post`/`context` calls through a non-owning, type-erased executor pointer.
75   75  
76   This class provides type erasure for any executor type, enabling 76   This class provides type erasure for any executor type, enabling
77   runtime polymorphism without virtual functions or allocation. 77   runtime polymorphism without virtual functions or allocation.
78   It stores a pointer to the original executor and a pointer to a 78   It stores a pointer to the original executor and a pointer to a
79   static vtable. Executors of different types are therefore stored 79   static vtable. Executors of different types are therefore stored
80   uniformly, while satisfying the full `Executor` concept. 80   uniformly, while satisfying the full `Executor` concept.
81   81  
82   @par Reference Semantics 82   @par Reference Semantics
83   This class has reference semantics: it does not allocate or own 83   This class has reference semantics: it does not allocate or own
84   the wrapped executor. Copy operations copy the internal 84   the wrapped executor. Copy operations copy the internal
85   pointers. The caller must ensure the referenced executor outlives 85   pointers. The caller must ensure the referenced executor outlives
86   all `executor_ref` instances that wrap it. 86   all `executor_ref` instances that wrap it.
87   87  
88   @par Thread Safety 88   @par Thread Safety
89   The `executor_ref` itself is not thread-safe for concurrent 89   The `executor_ref` itself is not thread-safe for concurrent
90   modification, but its executor operations are safe to call 90   modification, but its executor operations are safe to call
91   concurrently if the underlying executor supports it. 91   concurrently if the underlying executor supports it.
92   92  
93   @par Executor Concept 93   @par Executor Concept
94   This class satisfies the `Executor` concept, making it usable 94   This class satisfies the `Executor` concept, making it usable
95   anywhere a concrete executor is expected. 95   anywhere a concrete executor is expected.
96   96  
97   @par Example 97   @par Example
98   @code 98   @code
99   void store_executor(executor_ref ex) 99   void store_executor(executor_ref ex)
100   { 100   {
101   if(ex) 101   if(ex)
102   ex.post(my_continuation); 102   ex.post(my_continuation);
103   } 103   }
104   104  
105   thread_pool ctx; 105   thread_pool ctx;
106   store_executor(ctx.get_executor()); 106   store_executor(ctx.get_executor());
107   @endcode 107   @endcode
108   108  
109   @see any_executor, Executor 109   @see any_executor, Executor
110   */ 110   */
111   class executor_ref 111   class executor_ref
112   { 112   {
113   void const* ex_ = nullptr; 113   void const* ex_ = nullptr;
114   detail::executor_vtable const* vt_ = nullptr; 114   detail::executor_vtable const* vt_ = nullptr;
115   115  
116   public: 116   public:
117   /** Construct a default instance. 117   /** Construct a default instance.
118   118  
119   Constructs an empty `executor_ref`. `operator bool()` and 119   Constructs an empty `executor_ref`. `operator bool()` and
120   `operator==()` report the empty state; `context()`, 120   `operator==()` report the empty state; `context()`,
121   `on_work_started()`, `on_work_finished()`, `dispatch()`, 121   `on_work_started()`, `on_work_finished()`, `dispatch()`,
122   `post()`, and `target()` are undefined behavior until an 122   `post()`, and `target()` are undefined behavior until an
123   executor is assigned. 123   executor is assigned.
124   */ 124   */
HITCBC 125   3520 executor_ref() = default; 125   3558 executor_ref() = default;
126   126  
127   /** Construct a copy. 127   /** Construct a copy.
128   128  
129   Copies the internal pointers, preserving identity. 129   Copies the internal pointers, preserving identity.
130   This enables the same-executor optimization when passing 130   This enables the same-executor optimization when passing
131   executor_ref through coroutine chains. 131   executor_ref through coroutine chains.
132   132  
133   @param other The reference to copy. 133   @param other The reference to copy.
134   */ 134   */
135   executor_ref(executor_ref const& other) = default; 135   executor_ref(executor_ref const& other) = default;
136   136  
137   /** Copy assignment operator. 137   /** Copy assignment operator.
138   138  
139   @param other The reference to copy. 139   @param other The reference to copy.
140   140  
141   @return A reference to `*this`. 141   @return A reference to `*this`.
142   */ 142   */
143   executor_ref& operator=(executor_ref const& other) = default; 143   executor_ref& operator=(executor_ref const& other) = default;
144   144  
145   /** Constructs from any executor type. 145   /** Constructs from any executor type.
146   146  
147   Captures a reference to the given executor and stores a pointer 147   Captures a reference to the given executor and stores a pointer
148   to the type-specific vtable. The executor must remain valid for 148   to the type-specific vtable. The executor must remain valid for
149   the lifetime of this `executor_ref` instance. 149   the lifetime of this `executor_ref` instance.
150   150  
151   @param ex The executor to wrap. Must satisfy the `Executor` 151   @param ex The executor to wrap. Must satisfy the `Executor`
152   concept. A pointer to this object is stored 152   concept. A pointer to this object is stored
153   internally; the executor must outlive this wrapper. 153   internally; the executor must outlive this wrapper.
154   */ 154   */
155   #if defined(__GNUC__) && !defined(__clang__) 155   #if defined(__GNUC__) && !defined(__clang__)
156   // GCC constraint satisfaction caching bug workaround 156   // GCC constraint satisfaction caching bug workaround
157   template<class Ex, 157   template<class Ex,
158   std::enable_if_t<!std::is_same_v< 158   std::enable_if_t<!std::is_same_v<
159   std::decay_t<Ex>, executor_ref>, int> = 0> 159   std::decay_t<Ex>, executor_ref>, int> = 0>
160   #else 160   #else
161   template<class Ex> 161   template<class Ex>
162   requires (!std::same_as<std::decay_t<Ex>, executor_ref>) 162   requires (!std::same_as<std::decay_t<Ex>, executor_ref>)
163   #endif 163   #endif
HITCBC 164   32426 executor_ref(Ex const& ex) noexcept 164   32445 executor_ref(Ex const& ex) noexcept
HITCBC 165   32426 : ex_(&ex) 165   32445 : ex_(&ex)
HITCBC 166   32426 , vt_(&detail::vtable_for<Ex>) 166   32445 , vt_(&detail::vtable_for<Ex>)
167   { 167   {
HITCBC 168   32426 } 168   32445 }
169   169  
170   /** Returns true if this instance holds a valid executor. 170   /** Returns true if this instance holds a valid executor.
171   171  
172   @return `true` if constructed with an executor, `false` if 172   @return `true` if constructed with an executor, `false` if
173   default-constructed. 173   default-constructed.
174   */ 174   */
HITCBC 175   6 explicit operator bool() const noexcept 175   6 explicit operator bool() const noexcept
176   { 176   {
HITCBC 177   6 return ex_ != nullptr; 177   6 return ex_ != nullptr;
178   } 178   }
179   179  
180   /** Returns a reference to the associated execution context. 180   /** Returns a reference to the associated execution context.
181   181  
182   @return A reference to the execution context. 182   @return A reference to the execution context.
183   183  
184   @pre This instance was constructed with a valid executor. 184   @pre This instance was constructed with a valid executor.
185   */ 185   */
HITCBC 186   1 execution_context& context() const noexcept 186   1 execution_context& context() const noexcept
187   { 187   {
HITCBC 188   1 return vt_->context(ex_); 188   1 return vt_->context(ex_);
189   } 189   }
190   190  
191   /** Informs the executor that work is beginning. 191   /** Informs the executor that work is beginning.
192   192  
193   Must be paired with a subsequent call to `on_work_finished()`. 193   Must be paired with a subsequent call to `on_work_finished()`.
194   194  
195   @pre This instance was constructed with a valid executor. 195   @pre This instance was constructed with a valid executor.
196   */ 196   */
HITCBC 197   1 void on_work_started() const noexcept 197   1 void on_work_started() const noexcept
198   { 198   {
HITCBC 199   1 vt_->on_work_started(ex_); 199   1 vt_->on_work_started(ex_);
HITCBC 200   1 } 200   1 }
201   201  
202   /** Informs the executor that work has completed. 202   /** Informs the executor that work has completed.
203   203  
204   @pre A preceding call to `on_work_started()` was made. 204   @pre A preceding call to `on_work_started()` was made.
205   @pre This instance was constructed with a valid executor. 205   @pre This instance was constructed with a valid executor.
206   */ 206   */
HITCBC 207   1 void on_work_finished() const noexcept 207   1 void on_work_finished() const noexcept
208   { 208   {
HITCBC 209   1 vt_->on_work_finished(ex_); 209   1 vt_->on_work_finished(ex_);
HITCBC 210   1 } 210   1 }
211   211  
212   /** Dispatches a continuation through the wrapped executor. 212   /** Dispatches a continuation through the wrapped executor.
213   213  
214   Returns a handle for symmetric transfer. If running in the 214   Returns a handle for symmetric transfer. If running in the
215   executor's thread, returns `c.h`. Otherwise, posts the 215   executor's thread, returns `c.h`. Otherwise, posts the
216   continuation for later execution and returns 216   continuation for later execution and returns
217   `std::noop_coroutine()`. 217   `std::noop_coroutine()`.
218   218  
219   @param c The continuation to dispatch for resumption. 219   @param c The continuation to dispatch for resumption.
220   Must remain at a stable address until dequeued. 220   Must remain at a stable address until dequeued.
221   221  
222   @return A handle for symmetric transfer or `std::noop_coroutine()`. 222   @return A handle for symmetric transfer or `std::noop_coroutine()`.
223   223  
224   @pre This instance was constructed with a valid executor. 224   @pre This instance was constructed with a valid executor.
225   */ 225   */
HITCBC 226   122 std::coroutine_handle<> dispatch(continuation& c) const 226   122 std::coroutine_handle<> dispatch(continuation& c) const
227   { 227   {
HITCBC 228   122 return vt_->dispatch(ex_, c); 228   122 return vt_->dispatch(ex_, c);
229   } 229   }
230   230  
231   /** Posts a continuation to the wrapped executor. 231   /** Posts a continuation to the wrapped executor.
232   232  
233   Posts the continuation to the executor for later execution 233   Posts the continuation to the executor for later execution
234   and returns. The caller should transfer to `std::noop_coroutine()` 234   and returns. The caller should transfer to `std::noop_coroutine()`
235   after calling this. 235   after calling this.
236   236  
237   @param c The continuation to post for resumption. 237   @param c The continuation to post for resumption.
238   Must remain at a stable address until dequeued. 238   Must remain at a stable address until dequeued.
239   239  
240   @pre This instance was constructed with a valid executor. 240   @pre This instance was constructed with a valid executor.
241   */ 241   */
HITCBC 242   19370 void post(continuation& c) const 242   17497 void post(continuation& c) const
243   { 243   {
HITCBC 244   19370 vt_->post(ex_, c); 244   17497 vt_->post(ex_, c);
HITCBC 245   19370 } 245   17497 }
246   246  
247   /** Compares two executor references for equality. 247   /** Compares two executor references for equality.
248   248  
249   Two `executor_ref` instances are equal if they wrap 249   Two `executor_ref` instances are equal if they wrap
250   executors of the same type that compare equal. 250   executors of the same type that compare equal.
251   251  
252   @param other The executor reference to compare against. 252   @param other The executor reference to compare against.
253   253  
254   @return `true` if both wrap equal executors of the same type. 254   @return `true` if both wrap equal executors of the same type.
255   */ 255   */
HITCBC 256   7 bool operator==(executor_ref const& other) const noexcept 256   7 bool operator==(executor_ref const& other) const noexcept
257   { 257   {
HITCBC 258   7 if (ex_ == other.ex_) 258   7 if (ex_ == other.ex_)
HITCBC 259   5 return true; 259   5 return true;
HITCBC 260   2 if (vt_ != other.vt_) 260   2 if (vt_ != other.vt_)
HITCBC 261   1 return false; 261   1 return false;
HITCBC 262   1 return vt_->equals(ex_, other.ex_); 262   1 return vt_->equals(ex_, other.ex_);
263   } 263   }
264   264  
265   /** Return a pointer to the wrapped executor if it matches 265   /** Return a pointer to the wrapped executor if it matches
266   the requested type. 266   the requested type.
267   267  
268   Performs a type check against the stored executor and 268   Performs a type check against the stored executor and
269   returns a typed pointer when the types match, or 269   returns a typed pointer when the types match, or
270   `nullptr` otherwise. Analogous to 270   `nullptr` otherwise. Analogous to
271   `std::any_cast< Executor >( &a )`. 271   `std::any_cast< Executor >( &a )`.
272   272  
273   @tparam Executor The executor type to retrieve. 273   @tparam Executor The executor type to retrieve.
274   274  
275   @return A pointer to the underlying executor, or 275   @return A pointer to the underlying executor, or
276   `nullptr` if the type does not match. 276   `nullptr` if the type does not match.
277   */ 277   */
278   template< typename Executor > 278   template< typename Executor >
HITCBC 279   2 const Executor* target() const 279   2 const Executor* target() const
280   { 280   {
HITCBC 281   2 if ( *vt_->type_id == detail::type_id< Executor >() ) 281   2 if ( *vt_->type_id == detail::type_id< Executor >() )
HITCBC 282   1 return static_cast< Executor const* >( ex_ ); 282   1 return static_cast< Executor const* >( ex_ );
HITCBC 283   1 return nullptr; 283   1 return nullptr;
284   } 284   }
285   285  
286   /// @copydoc target() const 286   /// @copydoc target() const
287   template< typename Executor> 287   template< typename Executor>
HITCBC 288   2 Executor* target() 288   2 Executor* target()
289   { 289   {
HITCBC 290   2 if ( *vt_->type_id == detail::type_id< Executor >() ) 290   2 if ( *vt_->type_id == detail::type_id< Executor >() )
291   return const_cast< Executor* >( 291   return const_cast< Executor* >(
HITCBC 292   1 static_cast< Executor const* >( ex_ )); 292   1 static_cast< Executor const* >( ex_ ));
HITCBC 293   1 return nullptr; 293   1 return nullptr;
294   } 294   }
295   }; 295   };
296   296  
297   } // capy 297   } // capy
298   } // boost 298   } // boost
299   299  
300   #endif 300   #endif