LCOV - code coverage report
Current view: top level - capy/ex - any_executor.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 75.5 % 53 40 13
Test Date: 2026-02-17 18:14:47 Functions: 78.3 % 23 18 5

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

Generated by: LCOV version 2.3