LCOV - code coverage report
Current view: top level - capy/ex - execution_context.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 40 40
Test Date: 2026-02-12 16:53:28 Functions: 94.7 % 19 18

            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_EXECUTION_CONTEXT_HPP
      11              : #define BOOST_CAPY_EXECUTION_CONTEXT_HPP
      12              : 
      13              : #include <boost/capy/detail/config.hpp>
      14              : #include <boost/capy/detail/frame_memory_resource.hpp>
      15              : #include <boost/capy/detail/type_id.hpp>
      16              : #include <boost/capy/concept/executor.hpp>
      17              : #include <concepts>
      18              : #include <memory>
      19              : #include <memory_resource>
      20              : #include <mutex>
      21              : #include <tuple>
      22              : #include <type_traits>
      23              : #include <utility>
      24              : 
      25              : namespace boost {
      26              : namespace capy {
      27              : 
      28              : /** Base class for I/O object containers providing service management.
      29              : 
      30              :     An execution context represents a place where function objects are
      31              :     executed. It provides a service registry where polymorphic services
      32              :     can be stored and retrieved by type. Each service type may be stored
      33              :     at most once. Services may specify a nested `key_type` to enable
      34              :     lookup by a base class type.
      35              : 
      36              :     Derived classes such as `io_context` extend this to provide
      37              :     execution facilities like event loops and thread pools. Derived
      38              :     class destructors must call `shutdown()` and `destroy()` to ensure
      39              :     proper service cleanup before member destruction.
      40              : 
      41              :     @par Service Lifecycle
      42              :     Services are created on first use via `use_service()` or explicitly
      43              :     via `make_service()`. During destruction, `shutdown()` is called on
      44              :     each service in reverse order of creation, then `destroy()` deletes
      45              :     them. Both functions are idempotent.
      46              : 
      47              :     @par Thread Safety
      48              :     Service registration and lookup functions are thread-safe.
      49              :     The `shutdown()` and `destroy()` functions are not thread-safe
      50              :     and must only be called during destruction.
      51              : 
      52              :     @par Example
      53              :     @code
      54              :     struct file_service : execution_context::service
      55              :     {
      56              :     protected:
      57              :         void shutdown() override {}
      58              :     };
      59              : 
      60              :     struct posix_file_service : file_service
      61              :     {
      62              :         using key_type = file_service;
      63              : 
      64              :         explicit posix_file_service(execution_context&) {}
      65              :     };
      66              : 
      67              :     class io_context : public execution_context
      68              :     {
      69              :     public:
      70              :         ~io_context()
      71              :         {
      72              :             shutdown();
      73              :             destroy();
      74              :         }
      75              :     };
      76              : 
      77              :     io_context ctx;
      78              :     ctx.make_service<posix_file_service>();
      79              :     ctx.find_service<file_service>();       // returns posix_file_service*
      80              :     ctx.find_service<posix_file_service>(); // also works
      81              :     @endcode
      82              : 
      83              :     @see service, is_execution_context
      84              : */
      85              : class BOOST_CAPY_DECL
      86              :     execution_context
      87              : {
      88              :     detail::type_info const* ti_ = nullptr;
      89              : 
      90              :     template<class T, class = void>
      91              :     struct get_key : std::false_type
      92              :     {};
      93              : 
      94              :     template<class T>
      95              :     struct get_key<T, std::void_t<typename T::key_type>> : std::true_type
      96              :     {
      97              :         using type = typename T::key_type;
      98              :     };
      99              : protected:
     100              :     template< typename Derived >
     101              :     explicit execution_context( Derived* ) noexcept;
     102              : 
     103              : public:
     104              :     //------------------------------------------------
     105              : 
     106              :     /** Abstract base class for services owned by an execution context.
     107              : 
     108              :         Services provide extensible functionality to an execution context.
     109              :         Each service type can be registered at most once. Services are
     110              :         created via `use_service()` or `make_service()` and are owned by
     111              :         the execution context for their lifetime.
     112              : 
     113              :         Derived classes must implement the pure virtual `shutdown()` member
     114              :         function, which is called when the owning execution context is
     115              :         being destroyed. The `shutdown()` function should release resources
     116              :         and cancel outstanding operations without blocking.
     117              : 
     118              :         @par Deriving from service
     119              :         @li Implement `shutdown()` to perform cleanup.
     120              :         @li Accept `execution_context&` as the first constructor parameter.
     121              :         @li Optionally define `key_type` to enable base-class lookup.
     122              : 
     123              :         @par Example
     124              :         @code
     125              :         struct my_service : execution_context::service
     126              :         {
     127              :             explicit my_service(execution_context&) {}
     128              : 
     129              :         protected:
     130              :             void shutdown() override
     131              :             {
     132              :                 // Cancel pending operations, release resources
     133              :             }
     134              :         };
     135              :         @endcode
     136              : 
     137              :         @see execution_context
     138              :     */
     139              :     class BOOST_CAPY_DECL
     140              :         service
     141              :     {
     142              :     public:
     143           22 :         virtual ~service() = default;
     144              : 
     145              :     protected:
     146           22 :         service() = default;
     147              : 
     148              :         /** Called when the owning execution context shuts down.
     149              : 
     150              :             Implementations should release resources and cancel any
     151              :             outstanding asynchronous operations. This function must
     152              :             not block and must not throw exceptions. Services are
     153              :             shut down in reverse order of creation.
     154              : 
     155              :             @par Exception Safety
     156              :             No-throw guarantee.
     157              :         */
     158              :         virtual void shutdown() = 0;
     159              : 
     160              :     private:
     161              :         friend class execution_context;
     162              : 
     163              :         service* next_ = nullptr;
     164              : 
     165              : // warning C4251: 'std::type_index' needs to have dll-interface
     166              : #ifdef _MSC_VER
     167              : # pragma warning(push)
     168              : # pragma warning(disable: 4251)
     169              : #endif
     170              :         detail::type_index t0_{detail::type_id<void>()};
     171              :         detail::type_index t1_{detail::type_id<void>()};
     172              : #ifdef _MSC_VER
     173              : # pragma warning(pop)
     174              : #endif
     175              :     };
     176              : 
     177              :     //------------------------------------------------
     178              : 
     179              :     execution_context(execution_context const&) = delete;
     180              : 
     181              :     execution_context& operator=(execution_context const&) = delete;
     182              : 
     183              :     /** Destructor.
     184              : 
     185              :         Calls `shutdown()` then `destroy()` to clean up all services.
     186              : 
     187              :         @par Effects
     188              :         All services are shut down and deleted in reverse order
     189              :         of creation.
     190              : 
     191              :         @par Exception Safety
     192              :         No-throw guarantee.
     193              :     */
     194              :     ~execution_context();
     195              : 
     196              :     /** Default constructor.
     197              : 
     198              :         @par Exception Safety
     199              :         Strong guarantee.
     200              :     */
     201              :     execution_context();
     202              : 
     203              :     /** Return true if a service of type T exists.
     204              : 
     205              :         @par Thread Safety
     206              :         Thread-safe.
     207              : 
     208              :         @tparam T The type of service to check.
     209              : 
     210              :         @return `true` if the service exists.
     211              :     */
     212              :     template<class T>
     213            3 :     bool has_service() const noexcept
     214              :     {
     215            3 :         return find_service<T>() != nullptr;
     216              :     }
     217              : 
     218              :     /** Return a pointer to the service of type T, or nullptr.
     219              : 
     220              :         @par Thread Safety
     221              :         Thread-safe.
     222              : 
     223              :         @tparam T The type of service to find.
     224              : 
     225              :         @return A pointer to the service, or `nullptr` if not present.
     226              :     */
     227              :     template<class T>
     228            5 :     T* find_service() const noexcept
     229              :     {
     230            5 :         std::lock_guard<std::mutex> lock(mutex_);
     231            5 :         return static_cast<T*>(find_impl(detail::type_id<T>()));
     232            5 :     }
     233              : 
     234              :     /** Return a reference to the service of type T, creating it if needed.
     235              : 
     236              :         If no service of type T exists, one is created by calling
     237              :         `T(execution_context&)`. If T has a nested `key_type`, the
     238              :         service is also indexed under that type.
     239              : 
     240              :         @par Constraints
     241              :         @li `T` must derive from `service`.
     242              :         @li `T` must be constructible from `execution_context&`.
     243              : 
     244              :         @par Exception Safety
     245              :         Strong guarantee. If service creation throws, the container
     246              :         is unchanged.
     247              : 
     248              :         @par Thread Safety
     249              :         Thread-safe.
     250              : 
     251              :         @tparam T The type of service to retrieve or create.
     252              : 
     253              :         @return A reference to the service.
     254              :     */
     255              :     template<class T>
     256           27 :     T& use_service()
     257              :     {
     258              :         static_assert(std::is_base_of<service, T>::value,
     259              :             "T must derive from service");
     260              :         static_assert(std::is_constructible<T, execution_context&>::value,
     261              :             "T must be constructible from execution_context&");
     262              : 
     263              :         struct impl : factory
     264              :         {
     265           27 :             impl()
     266              :                 : factory(
     267              :                     detail::type_id<T>(),
     268              :                     get_key<T>::value
     269              :                         ? detail::type_id<typename get_key<T>::type>()
     270           27 :                         : detail::type_id<T>())
     271              :             {
     272           27 :             }
     273              : 
     274           21 :             service* create(execution_context& ctx) override
     275              :             {
     276           21 :                 return new T(ctx);
     277              :             }
     278              :         };
     279              : 
     280           27 :         impl f;
     281           54 :         return static_cast<T&>(use_service_impl(f));
     282              :     }
     283              : 
     284              :     /** Construct and add a service.
     285              : 
     286              :         A new service of type T is constructed using the provided
     287              :         arguments and added to the container. If T has a nested
     288              :         `key_type`, the service is also indexed under that type.
     289              : 
     290              :         @par Constraints
     291              :         @li `T` must derive from `service`.
     292              :         @li `T` must be constructible from `execution_context&, Args...`.
     293              :         @li If `T::key_type` exists, `T&` must be convertible to `key_type&`.
     294              : 
     295              :         @par Exception Safety
     296              :         Strong guarantee. If service creation throws, the container
     297              :         is unchanged.
     298              : 
     299              :         @par Thread Safety
     300              :         Thread-safe.
     301              : 
     302              :         @throws std::invalid_argument if a service of the same type
     303              :             or `key_type` already exists.
     304              : 
     305              :         @tparam T The type of service to create.
     306              : 
     307              :         @param args Arguments forwarded to the constructor of T.
     308              : 
     309              :         @return A reference to the created service.
     310              :     */
     311              :     template<class T, class... Args>
     312            2 :     T& make_service(Args&&... args)
     313              :     {
     314              :         static_assert(std::is_base_of<service, T>::value,
     315              :             "T must derive from service");
     316              :         if constexpr(get_key<T>::value)
     317              :         {
     318              :             static_assert(
     319              :                 std::is_convertible<T&, typename get_key<T>::type&>::value,
     320              :                 "T& must be convertible to key_type&");
     321              :         }
     322              : 
     323              :         struct impl : factory
     324              :         {
     325              :             std::tuple<Args&&...> args_;
     326              : 
     327            2 :             explicit impl(Args&&... a)
     328              :                 : factory(
     329              :                     detail::type_id<T>(),
     330              :                     get_key<T>::value
     331              :                         ? detail::type_id<typename get_key<T>::type>()
     332              :                         : detail::type_id<T>())
     333            2 :                 , args_(std::forward<Args>(a)...)
     334              :             {
     335            2 :             }
     336              : 
     337            1 :             service* create(execution_context& ctx) override
     338              :             {
     339            2 :                 return std::apply([&ctx](auto&&... a) {
     340            1 :                     return new T(ctx, std::forward<decltype(a)>(a)...);
     341            3 :                 }, std::move(args_));
     342              :             }
     343              :         };
     344              : 
     345            2 :         impl f(std::forward<Args>(args)...);
     346            3 :         return static_cast<T&>(make_service_impl(f));
     347              :     }
     348              : 
     349              :     //------------------------------------------------
     350              : 
     351              :     /** Return the memory resource used for coroutine frame allocation.
     352              : 
     353              :         The returned pointer is valid for the lifetime of this context.
     354              :         By default, this returns a pointer to the recycling memory
     355              :         resource which pools frame allocations for reuse.
     356              : 
     357              :         @return Pointer to the frame allocator.
     358              : 
     359              :         @see set_frame_allocator
     360              :     */
     361              :     std::pmr::memory_resource*
     362         2015 :     get_frame_allocator() const noexcept
     363              :     {
     364         2015 :         return frame_alloc_;
     365              :     }
     366              : 
     367              :     /** Set the memory resource used for coroutine frame allocation.
     368              : 
     369              :         The caller is responsible for ensuring the memory resource
     370              :         remains valid for the lifetime of all coroutines launched
     371              :         using this context's executor.
     372              : 
     373              :         @par Thread Safety
     374              :         Not thread-safe. Must not be called while any thread may
     375              :         be referencing this execution context or its executor.
     376              : 
     377              :         @param mr Pointer to the memory resource.
     378              : 
     379              :         @see get_frame_allocator
     380              :     */
     381              :     void
     382              :     set_frame_allocator(std::pmr::memory_resource* mr) noexcept
     383              :     {
     384              :         owned_.reset();
     385              :         frame_alloc_ = mr;
     386              :     }
     387              : 
     388              :     /** Set the frame allocator from a standard Allocator.
     389              : 
     390              :         The allocator is wrapped in an internal memory resource
     391              :         adapter owned by this context. The wrapper remains valid
     392              :         for the lifetime of this context or until a subsequent
     393              :         call to set_frame_allocator.
     394              : 
     395              :         @par Thread Safety
     396              :         Not thread-safe. Must not be called while any thread may
     397              :         be referencing this execution context or its executor.
     398              : 
     399              :         @tparam Allocator The allocator type satisfying the
     400              :             standard Allocator requirements.
     401              : 
     402              :         @param a The allocator to use.
     403              : 
     404              :         @see get_frame_allocator
     405              :     */
     406              :     template<class Allocator>
     407              :         requires (!std::is_pointer_v<Allocator>)
     408              :     void
     409           61 :     set_frame_allocator(Allocator const& a)
     410              :     {
     411              :         static_assert(
     412              :             requires { typename std::allocator_traits<Allocator>::value_type; },
     413              :             "Allocator must satisfy allocator requirements");
     414              :         static_assert(
     415              :             std::is_copy_constructible_v<Allocator>,
     416              :             "Allocator must be copy constructible");
     417              : 
     418           61 :         auto p = std::make_shared<
     419              :             detail::frame_memory_resource<Allocator>>(a);
     420           61 :         frame_alloc_ = p.get();
     421           61 :         owned_ = std::move(p);
     422           61 :     }
     423              : 
     424              :     /** Return a pointer to this context if it matches the
     425              :         requested type.
     426              : 
     427              :         Performs a type check and downcasts `this` when the
     428              :         types match, or returns `nullptr` otherwise. Analogous
     429              :         to `std::any_cast< ExecutionContext >( &a )`.
     430              : 
     431              :         @tparam ExecutionContext The derived context type to
     432              :             retrieve.
     433              : 
     434              :         @return A pointer to this context as the requested
     435              :             type, or `nullptr` if the type does not match.
     436              :     */
     437              :     template< typename ExecutionContext >
     438              :     const ExecutionContext* target() const
     439              :     {
     440              :         if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
     441              :            return static_cast< ExecutionContext const* >( this );
     442              :         return nullptr;
     443              :     }
     444              : 
     445              :     /// @copydoc target() const
     446              :     template< typename ExecutionContext >
     447              :     ExecutionContext* target()
     448              :     {
     449              :         if ( ti_ && *ti_ == detail::type_id< ExecutionContext >() )
     450              :            return static_cast< ExecutionContext* >( this );
     451              :         return nullptr;
     452              :     }
     453              : 
     454              : protected:
     455              :     /** Shut down all services.
     456              : 
     457              :         Calls `shutdown()` on each service in reverse order of creation.
     458              :         After this call, services remain allocated but are in a stopped
     459              :         state. Derived classes should call this in their destructor
     460              :         before any members are destroyed. This function is idempotent;
     461              :         subsequent calls have no effect.
     462              : 
     463              :         @par Effects
     464              :         Each service's `shutdown()` member function is invoked once.
     465              : 
     466              :         @par Postconditions
     467              :         @li All services are in a stopped state.
     468              : 
     469              :         @par Exception Safety
     470              :         No-throw guarantee.
     471              : 
     472              :         @par Thread Safety
     473              :         Not thread-safe. Must not be called concurrently with other
     474              :         operations on this execution_context.
     475              :     */
     476              :     void shutdown() noexcept;
     477              : 
     478              :     /** Destroy all services.
     479              : 
     480              :         Deletes all services in reverse order of creation. Derived
     481              :         classes should call this as the final step of destruction.
     482              :         This function is idempotent; subsequent calls have no effect.
     483              : 
     484              :         @par Preconditions
     485              :         @li `shutdown()` has been called.
     486              : 
     487              :         @par Effects
     488              :         All services are deleted and removed from the container.
     489              : 
     490              :         @par Postconditions
     491              :         @li The service container is empty.
     492              : 
     493              :         @par Exception Safety
     494              :         No-throw guarantee.
     495              : 
     496              :         @par Thread Safety
     497              :         Not thread-safe. Must not be called concurrently with other
     498              :         operations on this execution_context.
     499              :     */
     500              :     void destroy() noexcept;
     501              : 
     502              : private:
     503              :     struct BOOST_CAPY_DECL
     504              :         factory
     505              :     {
     506              : #ifdef _MSC_VER
     507              : # pragma warning(push)
     508              : # pragma warning(disable: 4251)
     509              : #endif
     510              : // warning C4251: 'std::type_index' needs to have dll-interface
     511              :         detail::type_index t0;
     512              :         detail::type_index t1;
     513              : #ifdef _MSC_VER
     514              : # pragma warning(pop)
     515              : #endif
     516              : 
     517           29 :         factory(
     518              :             detail::type_info const& t0_,
     519              :             detail::type_info const& t1_)
     520           29 :             : t0(t0_), t1(t1_)
     521              :         {
     522           29 :         }
     523              : 
     524              :         virtual service* create(execution_context&) = 0;
     525              : 
     526              :     protected:
     527              :         ~factory() = default;
     528              :     };
     529              : 
     530              :     service* find_impl(detail::type_index ti) const noexcept;
     531              :     service& use_service_impl(factory& f);
     532              :     service& make_service_impl(factory& f);
     533              : 
     534              : #ifdef _MSC_VER
     535              : # pragma warning(push)
     536              : # pragma warning(disable: 4251)
     537              : #endif
     538              : // warning C4251: 'std::type_index' needs to have dll-interface
     539              :     mutable std::mutex mutex_;
     540              :     std::shared_ptr<void> owned_;
     541              : #ifdef _MSC_VER
     542              : # pragma warning(pop)
     543              : #endif
     544              :     std::pmr::memory_resource* frame_alloc_ = nullptr;
     545              :     service* head_ = nullptr;
     546              :     bool shutdown_ = false;
     547              : };
     548              : 
     549              : template< typename Derived >
     550            6 : execution_context::
     551              : execution_context( Derived* ) noexcept
     552            6 :     : execution_context()
     553              : {
     554            6 :     ti_ = &detail::type_id< Derived >();
     555            6 : }
     556              : 
     557              : } // namespace capy
     558              : } // namespace boost
     559              : 
     560              : #endif
        

Generated by: LCOV version 2.3