LCOV - code coverage report
Current view: top level - boost/capy/ex - execution_context.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 40 40
Test Date: 2026-01-30 23:43:15 Functions: 95.2 % 63 60

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

Generated by: LCOV version 2.3