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 % 31 31
Test Date: 2026-01-15 20:40:20 Functions: 94.7 % 57 54

            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/concept/executor.hpp>
      15              : #include <boost/capy/core/intrusive_queue.hpp>
      16              : #include <concepts>
      17              : #include <mutex>
      18              : #include <tuple>
      19              : #include <type_traits>
      20              : #include <typeindex>
      21              : #include <utility>
      22              : 
      23              : namespace boost {
      24              : namespace capy {
      25              : 
      26              : /** Base class for I/O object containers providing service management.
      27              : 
      28              :     An execution context represents a place where function objects are
      29              :     executed. It provides a service registry where polymorphic services
      30              :     can be stored and retrieved by type. Each service type may be stored
      31              :     at most once. Services may specify a nested `key_type` to enable
      32              :     lookup by a base class type.
      33              : 
      34              :     Derived classes such as `io_context` extend this to provide
      35              :     execution facilities like event loops and thread pools. Derived
      36              :     class destructors must call `shutdown()` and `destroy()` to ensure
      37              :     proper service cleanup before member destruction.
      38              : 
      39              :     @par Service Lifecycle
      40              :     Services are created on first use via `use_service()` or explicitly
      41              :     via `make_service()`. During destruction, `shutdown()` is called on
      42              :     each service in reverse order of creation, then `destroy()` deletes
      43              :     them. Both functions are idempotent.
      44              : 
      45              :     @par Thread Safety
      46              :     Service registration and lookup functions are thread-safe.
      47              :     The `shutdown()` and `destroy()` functions are not thread-safe
      48              :     and must only be called during destruction.
      49              : 
      50              :     @par Example
      51              :     @code
      52              :     struct file_service : execution_context::service
      53              :     {
      54              :     protected:
      55              :         void shutdown() override {}
      56              :     };
      57              : 
      58              :     struct posix_file_service : file_service
      59              :     {
      60              :         using key_type = file_service;
      61              : 
      62              :         explicit posix_file_service(execution_context&) {}
      63              :     };
      64              : 
      65              :     class io_context : public execution_context
      66              :     {
      67              :     public:
      68              :         ~io_context()
      69              :         {
      70              :             shutdown();
      71              :             destroy();
      72              :         }
      73              :     };
      74              : 
      75              :     io_context ctx;
      76              :     ctx.make_service<posix_file_service>();
      77              :     ctx.find_service<file_service>();       // returns posix_file_service*
      78              :     ctx.find_service<posix_file_service>(); // also works
      79              :     @endcode
      80              : 
      81              :     @see service, is_execution_context
      82              : */
      83              : class BOOST_CAPY_DECL
      84              :     execution_context
      85              : {
      86              :     template<class T, class = void>
      87              :     struct get_key : std::false_type
      88              :     {};
      89              : 
      90              :     template<class T>
      91              :     struct get_key<T, std::void_t<typename T::key_type>> : std::true_type
      92              :     {
      93              :         using type = typename T::key_type;
      94              :     };
      95              : 
      96              : public:
      97              :     //------------------------------------------------
      98              : 
      99              :     /** Abstract base class for services owned by an execution context.
     100              : 
     101              :         Services provide extensible functionality to an execution context.
     102              :         Each service type can be registered at most once. Services are
     103              :         created via `use_service()` or `make_service()` and are owned by
     104              :         the execution context for their lifetime.
     105              : 
     106              :         Derived classes must implement the pure virtual `shutdown()` member
     107              :         function, which is called when the owning execution context is
     108              :         being destroyed. The `shutdown()` function should release resources
     109              :         and cancel outstanding operations without blocking.
     110              : 
     111              :         @par Deriving from service
     112              :         @li Implement `shutdown()` to perform cleanup.
     113              :         @li Accept `execution_context&` as the first constructor parameter.
     114              :         @li Optionally define `key_type` to enable base-class lookup.
     115              : 
     116              :         @par Example
     117              :         @code
     118              :         struct my_service : execution_context::service
     119              :         {
     120              :             explicit my_service(execution_context&) {}
     121              : 
     122              :         protected:
     123              :             void shutdown() override
     124              :             {
     125              :                 // Cancel pending operations, release resources
     126              :             }
     127              :         };
     128              :         @endcode
     129              : 
     130              :         @see execution_context
     131              :     */
     132              :     class service
     133              :     {
     134              :     public:
     135           18 :         virtual ~service() = default;
     136              : 
     137              :     protected:
     138           18 :         service() = default;
     139              : 
     140              :         /** Called when the owning execution context shuts down.
     141              : 
     142              :             Implementations should release resources and cancel any
     143              :             outstanding asynchronous operations. This function must
     144              :             not block and must not throw exceptions. Services are
     145              :             shut down in reverse order of creation.
     146              : 
     147              :             @par Exception Safety
     148              :             No-throw guarantee.
     149              :         */
     150              :         virtual void shutdown() = 0;
     151              : 
     152              :     private:
     153              :         friend class execution_context;
     154              : 
     155              :         service* next_ = nullptr;
     156              :         std::type_index t0_ = typeid(void);
     157              :         std::type_index t1_ = typeid(void);
     158              :     };
     159              : 
     160              :     //------------------------------------------------
     161              : 
     162              :     /** Abstract base class for completion handlers.
     163              : 
     164              :         Handlers are continuations that execute after an asynchronous
     165              :         operation completes. They can be queued for deferred invocation,
     166              :         allowing callbacks and coroutine resumptions to be posted to an
     167              :         executor.
     168              : 
     169              :         Handlers should execute quickly - typically just initiating
     170              :         another I/O operation or suspending on a foreign task. Heavy
     171              :         computation should be avoided in handlers to prevent blocking
     172              :         the event loop.
     173              : 
     174              :         Handlers may be heap-allocated or may be data members of an
     175              :         enclosing object. The allocation strategy is determined by the
     176              :         creator of the handler.
     177              : 
     178              :         @par Ownership Contract
     179              : 
     180              :         Callers must invoke exactly ONE of `operator()` or `destroy()`,
     181              :         never both:
     182              : 
     183              :         @li `operator()` - Invokes the handler. The handler is
     184              :             responsible for its own cleanup (typically `delete this`
     185              :             for heap-allocated handlers). The caller must not call
     186              :             `destroy()` after invoking this.
     187              : 
     188              :         @li `destroy()` - Destroys an uninvoked handler. This is
     189              :             called when a queued handler must be discarded without
     190              :             execution, such as during shutdown or exception cleanup.
     191              :             For heap-allocated handlers, this typically calls
     192              :             `delete this`.
     193              : 
     194              :         @par Exception Safety
     195              : 
     196              :         Implementations of `operator()` must perform cleanup before
     197              :         any operation that might throw. This ensures that if the handler
     198              :         throws, the exception propagates cleanly to the caller of
     199              :         `run()` without leaking resources. Typical pattern:
     200              : 
     201              :         @code
     202              :         void operator()() override
     203              :         {
     204              :             auto h = h_;
     205              :             delete this;    // cleanup FIRST
     206              :             h.resume();     // then resume (may throw)
     207              :         }
     208              :         @endcode
     209              : 
     210              :         This "delete-before-invoke" pattern also enables memory
     211              :         recycling - the handler's memory can be reused immediately
     212              :         by subsequent allocations.
     213              : 
     214              :         @note Callers must never delete handlers directly with `delete`;
     215              :         use `operator()` for normal invocation or `destroy()` for cleanup.
     216              : 
     217              :         @note Heap-allocated handlers are typically allocated with
     218              :         custom allocators to minimize allocation overhead in
     219              :         high-frequency async operations.
     220              : 
     221              :         @note Some handlers (such as those owned by containers like
     222              :         `std::unique_ptr` or embedded as data members) are not meant to
     223              :         be destroyed and should implement both functions as no-ops
     224              :         (for `operator()`, invoke the continuation but don't delete).
     225              : 
     226              :         @see queue
     227              :     */
     228              :     class handler : public intrusive_queue<handler>::node
     229              :     {
     230              :     public:
     231              :         virtual void operator()() = 0;
     232              :         virtual void destroy() = 0;
     233              : 
     234              :         /** Returns the user-defined data pointer.
     235              : 
     236              :             Derived classes may set this to store auxiliary data
     237              :             such as a pointer to the most-derived object.
     238              : 
     239              :             @par Postconditions
     240              :             @li Initially returns `nullptr` for newly constructed handlers.
     241              :             @li Returns the current value of `data_` if modified by a derived class.
     242              : 
     243              :             @return The user-defined data pointer, or `nullptr` if not set.
     244              :         */
     245            4 :         void* data() const noexcept
     246              :         {
     247            4 :             return data_;
     248              :         }
     249              : 
     250              :     protected:
     251              :         ~handler() = default;
     252              : 
     253              :         void* data_ = nullptr;
     254              :     };
     255              : 
     256              :     //------------------------------------------------
     257              : 
     258              :     /** An intrusive FIFO queue of handlers.
     259              : 
     260              :         This queue stores handlers using an intrusive linked list,
     261              :         avoiding additional allocations for queue nodes. Handlers
     262              :         are popped in the order they were pushed (first-in, first-out).
     263              : 
     264              :         The destructor calls `destroy()` on any remaining handlers.
     265              : 
     266              :         @note This is not thread-safe. External synchronization is
     267              :         required for concurrent access.
     268              : 
     269              :         @see handler
     270              :     */
     271              :     class queue
     272              :     {
     273              :         intrusive_queue<handler> q_;
     274              : 
     275              :     public:
     276              :         /** Default constructor.
     277              : 
     278              :             Creates an empty queue.
     279              : 
     280              :             @post `empty() == true`
     281              :         */
     282              :         queue() = default;
     283              : 
     284              :         /** Move constructor.
     285              : 
     286              :             Takes ownership of all handlers from `other`,
     287              :             leaving `other` empty.
     288              : 
     289              :             @param other The queue to move from.
     290              : 
     291              :             @post `other.empty() == true`
     292              :         */
     293              :         queue(queue&& other) noexcept
     294              :             : q_(std::move(other.q_))
     295              :         {
     296              :         }
     297              : 
     298              :         queue(queue const&) = delete;
     299              :         queue& operator=(queue const&) = delete;
     300              :         queue& operator=(queue&&) = delete;
     301              : 
     302              :         /** Destructor.
     303              : 
     304              :             Calls `destroy()` on any remaining handlers in the queue.
     305              :         */
     306              :         ~queue()
     307              :         {
     308              :             while(auto* h = q_.pop())
     309              :                 h->destroy();
     310              :         }
     311              : 
     312              :         /** Return true if the queue is empty.
     313              : 
     314              :             @return `true` if the queue contains no handlers.
     315              :         */
     316              :         bool
     317              :         empty() const noexcept
     318              :         {
     319              :             return q_.empty();
     320              :         }
     321              : 
     322              :         /** Add a handler to the back of the queue.
     323              : 
     324              :             @param h Pointer to the handler to add.
     325              : 
     326              :             @pre `h` is not null and not already in a queue.
     327              :         */
     328              :         void
     329              :         push(handler* h) noexcept
     330              :         {
     331              :             q_.push(h);
     332              :         }
     333              : 
     334              :         /** Splice all handlers from another queue to the back.
     335              : 
     336              :             All handlers from `other` are moved to the back of this
     337              :             queue. After this call, `other` is empty.
     338              : 
     339              :             @param other The queue to splice from.
     340              : 
     341              :             @post `other.empty() == true`
     342              :         */
     343              :         void
     344              :         push(queue& other) noexcept
     345              :         {
     346              :             q_.splice(other.q_);
     347              :         }
     348              : 
     349              :         /** Remove and return the front handler.
     350              : 
     351              :             @return Pointer to the front handler, or `nullptr`
     352              :                 if the queue is empty.
     353              :         */
     354              :         handler*
     355              :         pop() noexcept
     356              :         {
     357              :             return q_.pop();
     358              :         }
     359              :     };
     360              : 
     361              :     //------------------------------------------------
     362              : 
     363              :     execution_context(execution_context const&) = delete;
     364              : 
     365              :     execution_context& operator=(execution_context const&) = delete;
     366              : 
     367              :     /** Destructor.
     368              : 
     369              :         Calls `shutdown()` then `destroy()` to clean up all services.
     370              : 
     371              :         @par Effects
     372              :         All services are shut down and deleted in reverse order
     373              :         of creation.
     374              : 
     375              :         @par Exception Safety
     376              :         No-throw guarantee.
     377              :     */
     378              :     ~execution_context();
     379              : 
     380              :     /** Default constructor.
     381              : 
     382              :         @par Exception Safety
     383              :         Strong guarantee.
     384              :     */
     385              :     execution_context();
     386              : 
     387              :     /** Return true if a service of type T exists.
     388              : 
     389              :         @par Thread Safety
     390              :         Thread-safe.
     391              : 
     392              :         @tparam T The type of service to check.
     393              : 
     394              :         @return `true` if the service exists.
     395              :     */
     396              :     template<class T>
     397           13 :     bool has_service() const noexcept
     398              :     {
     399           13 :         return find_service<T>() != nullptr;
     400              :     }
     401              : 
     402              :     /** Return a pointer to the service of type T, or nullptr.
     403              : 
     404              :         @par Thread Safety
     405              :         Thread-safe.
     406              : 
     407              :         @tparam T The type of service to find.
     408              : 
     409              :         @return A pointer to the service, or `nullptr` if not present.
     410              :     */
     411              :     template<class T>
     412           22 :     T* find_service() const noexcept
     413              :     {
     414           22 :         std::lock_guard<std::mutex> lock(mutex_);
     415           22 :         return static_cast<T*>(find_impl(typeid(T)));
     416           22 :     }
     417              : 
     418              :     /** Return a reference to the service of type T, creating it if needed.
     419              : 
     420              :         If no service of type T exists, one is created by calling
     421              :         `T(execution_context&)`. If T has a nested `key_type`, the
     422              :         service is also indexed under that type.
     423              : 
     424              :         @par Constraints
     425              :         @li `T` must derive from `service`.
     426              :         @li `T` must be constructible from `execution_context&`.
     427              : 
     428              :         @par Exception Safety
     429              :         Strong guarantee. If service creation throws, the container
     430              :         is unchanged.
     431              : 
     432              :         @par Thread Safety
     433              :         Thread-safe.
     434              : 
     435              :         @tparam T The type of service to retrieve or create.
     436              : 
     437              :         @return A reference to the service.
     438              :     */
     439              :     template<class T>
     440           17 :     T& use_service()
     441              :     {
     442              :         static_assert(std::is_base_of<service, T>::value,
     443              :             "T must derive from service");
     444              :         static_assert(std::is_constructible<T, execution_context&>::value,
     445              :             "T must be constructible from execution_context&");
     446              : 
     447              :         struct impl : factory
     448              :         {
     449           17 :             impl()
     450              :                 : factory(
     451              :                     typeid(T),
     452              :                     get_key<T>::value
     453              :                         ? typeid(typename get_key<T>::type)
     454           17 :                         : typeid(T))
     455              :             {
     456           17 :             }
     457              : 
     458           11 :             service* create(execution_context& ctx) override
     459              :             {
     460           11 :                 return new T(ctx);
     461              :             }
     462              :         };
     463              : 
     464           17 :         impl f;
     465           34 :         return static_cast<T&>(use_service_impl(f));
     466              :     }
     467              : 
     468              :     /** Construct and add a service.
     469              : 
     470              :         A new service of type T is constructed using the provided
     471              :         arguments and added to the container. If T has a nested
     472              :         `key_type`, the service is also indexed under that type.
     473              : 
     474              :         @par Constraints
     475              :         @li `T` must derive from `service`.
     476              :         @li `T` must be constructible from `execution_context&, Args...`.
     477              :         @li If `T::key_type` exists, `T&` must be convertible to `key_type&`.
     478              : 
     479              :         @par Exception Safety
     480              :         Strong guarantee. If service creation throws, the container
     481              :         is unchanged.
     482              : 
     483              :         @par Thread Safety
     484              :         Thread-safe.
     485              : 
     486              :         @throws std::invalid_argument if a service of the same type
     487              :             or `key_type` already exists.
     488              : 
     489              :         @tparam T The type of service to create.
     490              : 
     491              :         @param args Arguments forwarded to the constructor of T.
     492              : 
     493              :         @return A reference to the created service.
     494              :     */
     495              :     template<class T, class... Args>
     496           10 :     T& make_service(Args&&... args)
     497              :     {
     498              :         static_assert(std::is_base_of<service, T>::value,
     499              :             "T must derive from service");
     500              :         if constexpr(get_key<T>::value)
     501              :         {
     502              :             static_assert(
     503              :                 std::is_convertible<T&, typename get_key<T>::type&>::value,
     504              :                 "T& must be convertible to key_type&");
     505              :         }
     506              : 
     507              :         struct impl : factory
     508              :         {
     509              :             std::tuple<Args&&...> args_;
     510              : 
     511           10 :             explicit impl(Args&&... a)
     512              :                 : factory(
     513              :                     typeid(T),
     514              :                     get_key<T>::value
     515              :                         ? typeid(typename get_key<T>::type)
     516              :                         : typeid(T))
     517           10 :                 , args_(std::forward<Args>(a)...)
     518              :             {
     519           10 :             }
     520              : 
     521            7 :             service* create(execution_context& ctx) override
     522              :             {
     523           20 :                 return std::apply([&ctx](auto&&... a) {
     524            9 :                     return new T(ctx, std::forward<decltype(a)>(a)...);
     525           21 :                 }, std::move(args_));
     526              :             }
     527              :         };
     528              : 
     529           10 :         impl f(std::forward<Args>(args)...);
     530           17 :         return static_cast<T&>(make_service_impl(f));
     531              :     }
     532              : 
     533              : protected:
     534              :     /** Shut down all services.
     535              : 
     536              :         Calls `shutdown()` on each service in reverse order of creation.
     537              :         After this call, services remain allocated but are in a stopped
     538              :         state. Derived classes should call this in their destructor
     539              :         before any members are destroyed. This function is idempotent;
     540              :         subsequent calls have no effect.
     541              : 
     542              :         @par Effects
     543              :         Each service's `shutdown()` member function is invoked once.
     544              : 
     545              :         @par Postconditions
     546              :         @li All services are in a stopped state.
     547              : 
     548              :         @par Exception Safety
     549              :         No-throw guarantee.
     550              : 
     551              :         @par Thread Safety
     552              :         Not thread-safe. Must not be called concurrently with other
     553              :         operations on this execution_context.
     554              :     */
     555              :     void shutdown() noexcept;
     556              : 
     557              :     /** Destroy all services.
     558              : 
     559              :         Deletes all services in reverse order of creation. Derived
     560              :         classes should call this as the final step of destruction.
     561              :         This function is idempotent; subsequent calls have no effect.
     562              : 
     563              :         @par Preconditions
     564              :         @li `shutdown()` has been called.
     565              : 
     566              :         @par Effects
     567              :         All services are deleted and removed from the container.
     568              : 
     569              :         @par Postconditions
     570              :         @li The service container is empty.
     571              : 
     572              :         @par Exception Safety
     573              :         No-throw guarantee.
     574              : 
     575              :         @par Thread Safety
     576              :         Not thread-safe. Must not be called concurrently with other
     577              :         operations on this execution_context.
     578              :     */
     579              :     void destroy() noexcept;
     580              : 
     581              : private:
     582              :     struct factory
     583              :     {
     584              :         std::type_index t0;
     585              :         std::type_index t1;
     586              : 
     587           27 :         factory(std::type_index t0_, std::type_index t1_)
     588           27 :             : t0(t0_), t1(t1_)
     589              :         {
     590           27 :         }
     591              : 
     592              :         virtual service* create(execution_context&) = 0;
     593              : 
     594              :     protected:
     595              :         ~factory() = default;
     596              :     };
     597              : 
     598              :     service* find_impl(std::type_index ti) const noexcept;
     599              :     service& use_service_impl(factory& f);
     600              :     service& make_service_impl(factory& f);
     601              : 
     602              : #ifdef _MSC_VER
     603              : # pragma warning(push)
     604              : # pragma warning(disable: 4251)
     605              : #endif
     606              :     mutable std::mutex mutex_;
     607              : #ifdef _MSC_VER
     608              : # pragma warning(pop)
     609              : #endif
     610              :     service* head_ = nullptr;
     611              :     bool shutdown_ = false;
     612              : };
     613              : 
     614              : } // namespace capy
     615              : } // namespace boost
     616              : 
     617              : #endif
        

Generated by: LCOV version 2.3