LCOV - code coverage report
Current view: top level - boost/capy/ex - stop_token.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 83.6 % 61 51
Test Date: 2026-01-21 22:59:19 Functions: 37.5 % 40 15

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2026 Cinar Gursoy
       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_EX_STOP_TOKEN_HPP
      11              : #define BOOST_CAPY_EX_STOP_TOKEN_HPP
      12              : 
      13              : #include <boost/capy/detail/config.hpp>
      14              : 
      15              : #include <atomic>
      16              : #include <functional>
      17              : #include <memory>
      18              : #include <mutex>
      19              : #include <vector>
      20              : 
      21              : namespace boost {
      22              : namespace capy {
      23              : 
      24              : namespace detail {
      25              : 
      26              : struct stop_state
      27              :     {
      28              :         std::atomic<bool> stop_requested_{false};
      29              :         std::mutex mutex_;
      30              :         std::vector<std::function<void()>> callbacks_;
      31              : 
      32           15 :         bool request_stop() noexcept
      33              :         {
      34           15 :             bool expected = false;
      35           15 :             if (!stop_requested_.compare_exchange_strong(expected, true))
      36            4 :                 return false;  // Already requested
      37              : 
      38              :             // Invoke all callbacks
      39           11 :             std::vector<std::function<void()>> cbs;
      40              :             {
      41           11 :                 std::lock_guard<std::mutex> lock(mutex_);
      42           11 :                 cbs.swap(callbacks_);
      43           11 :             }
      44           12 :             for (auto& cb : cbs)
      45              :             {
      46              :                 try
      47              :                 {
      48            1 :                     cb();
      49              :                 }
      50            0 :                 catch (...)
      51              :                 {
      52              :                     // Callbacks should not throw, but ignore if they do
      53            0 :                 }
      54              :             }
      55           11 :             return true;
      56           11 :         }
      57              : 
      58            4 :         void register_callback(std::function<void()> cb)
      59              :         {
      60              :             // If already stopped, invoke immediately
      61            4 :             if (stop_requested_.load(std::memory_order_acquire))
      62              :             {
      63              :                 try
      64              :                 {
      65            1 :                     cb();
      66              :                 }
      67            0 :                 catch (...)
      68              :                 {
      69              :                     // Ignore exceptions from callbacks
      70            0 :                 }
      71            1 :                 return;
      72              :             }
      73              : 
      74              :             // Register for later invocation
      75            3 :             std::lock_guard<std::mutex> lock(mutex_);
      76            3 :             if (stop_requested_.load(std::memory_order_acquire))
      77              :             {
      78              :                 // Stop was requested while we were acquiring lock
      79              :                 try
      80              :                 {
      81            0 :                     cb();
      82              :                 }
      83            0 :                 catch (...)
      84              :                 {
      85              :                     // Ignore exceptions
      86            0 :                 }
      87              :             }
      88              :             else
      89              :             {
      90            3 :                 callbacks_.push_back(std::move(cb));
      91              :             }
      92            3 :         }
      93              : 
      94              :     };
      95              : 
      96              : } // namespace detail
      97              : 
      98              : /** A token that can be checked for cancellation requests.
      99              : 
     100              :     A `stop_token` can be checked to see if a stop request has been made.
     101              :     Multiple tokens can share the same stop state, allowing coordinated
     102              :     cancellation across multiple operations.
     103              : 
     104              :     @see stop_source, stop_callback
     105              : */
     106              : class stop_token
     107              : {
     108              :     std::shared_ptr<detail::stop_state> state_;
     109              : 
     110           28 :     explicit stop_token(std::shared_ptr<detail::stop_state> state) noexcept
     111           28 :         : state_(std::move(state))
     112              :     {
     113           28 :     }
     114              : 
     115              :     friend class stop_source;
     116              :     template<class Callback>
     117              :     friend class stop_callback;
     118              : 
     119              : public:
     120              :     /** Default constructor.
     121              : 
     122              :         Creates a stop token that cannot be stopped.
     123              :         `stop_possible()` returns `false` for default-constructed tokens.
     124              :     */
     125          326 :     stop_token() noexcept = default;
     126              : 
     127              :     /** Copy constructor.
     128              : 
     129              :         Creates a copy that shares the same stop state.
     130              :     */
     131          372 :     stop_token(stop_token const&) noexcept = default;
     132              : 
     133              :     /** Copy assignment.
     134              : 
     135              :         Assigns the stop state from another token.
     136              :     */
     137          222 :     stop_token& operator=(stop_token const&) noexcept = default;
     138              : 
     139              :     /** Move constructor.
     140              : 
     141              :         Moves the stop state from another token.
     142              :     */
     143           63 :     stop_token(stop_token&&) noexcept = default;
     144              : 
     145              :     /** Move assignment.
     146              : 
     147              :         Moves the stop state from another token.
     148              :     */
     149            0 :     stop_token& operator=(stop_token&&) noexcept = default;
     150              : 
     151              :     /** Check if a stop request has been made.
     152              : 
     153              :         @return `true` if a stop request has been made, `false` otherwise.
     154              :     */
     155          110 :     bool stop_requested() const noexcept
     156              :     {
     157          110 :         if (!state_)
     158          102 :             return false;
     159            8 :         return state_->stop_requested_.load(std::memory_order_acquire);
     160              :     }
     161              : 
     162              :     /** Check if this token can receive stop requests.
     163              : 
     164              :         @return `true` if this token is associated with a stop source,
     165              :                 `false` for default-constructed tokens.
     166              :     */
     167           34 :     bool stop_possible() const noexcept
     168              :     {
     169           34 :         return state_ != nullptr;
     170              :     }
     171              : 
     172              :     /** Swap with another stop token.
     173              : 
     174              :         @param other The token to swap with.
     175              :     */
     176              :     void swap(stop_token& other) noexcept
     177              :     {
     178              :         state_.swap(other.state_);
     179              :     }
     180              : };
     181              : 
     182              : /** Swap two stop tokens.
     183              : 
     184              :     @param lhs First token.
     185              :     @param rhs Second token.
     186              : */
     187              : inline void swap(stop_token& lhs, stop_token& rhs) noexcept
     188              : {
     189              :     lhs.swap(rhs);
     190              : }
     191              : 
     192              : //----------------------------------------------------------
     193              : //
     194              : // stop_source
     195              : //
     196              : //----------------------------------------------------------
     197              : 
     198              : /** A source for stop tokens that can request cancellation.
     199              : 
     200              :     A `stop_source` creates `stop_token` objects and can request
     201              :     cancellation on all tokens it has created. Multiple tokens from
     202              :     the same source share the same stop state.
     203              : 
     204              :     @par Thread Safety
     205              :     Distinct objects: Safe.
     206              :     Shared objects: Safe.
     207              : 
     208              :     @par Example
     209              :     @code
     210              :     capy::stop_source source;
     211              :     auto token = source.get_token();
     212              : 
     213              :     // Start operation with token
     214              :     start_operation(token);
     215              : 
     216              :     // Later, cancel the operation
     217              :     source.request_stop();
     218              :     @endcode
     219              : 
     220              :     @see stop_token, stop_callback
     221              : */
     222              : class stop_source
     223              : {
     224              :     std::shared_ptr<detail::stop_state> state_;
     225              : 
     226              : public:
     227              :     /** Default constructor.
     228              : 
     229              :         Creates a stop source that can create stop tokens.
     230              :     */
     231           29 :     stop_source()
     232           29 :         : state_(std::make_shared<detail::stop_state>())
     233              :     {
     234           29 :     }
     235              : 
     236              :     /** Copy constructor.
     237              : 
     238              :         Creates a new stop source that shares the same stop state
     239              :         as the source being copied. Both sources can request stop
     240              :         and affect the same tokens.
     241              :     */
     242              :     stop_source(stop_source const&) = default;
     243              : 
     244              :     /** Copy assignment.
     245              : 
     246              :         Assigns the stop state from another source.
     247              :     */
     248              :     stop_source& operator=(stop_source const&) = default;
     249              : 
     250              :     /** Move constructor.
     251              : 
     252              :         Moves the stop state from another source.
     253              :     */
     254              :     stop_source(stop_source&&) noexcept = default;
     255              : 
     256              :     /** Move assignment.
     257              : 
     258              :         Moves the stop state from another source.
     259              :     */
     260              :     stop_source& operator=(stop_source&&) noexcept = default;
     261              : 
     262              :     /** Get a stop token associated with this source.
     263              : 
     264              :         All tokens returned from the same source share the same
     265              :         stop state. Requesting stop on the source affects all
     266              :         tokens created from it.
     267              : 
     268              :         @return A stop token that can be checked for cancellation.
     269              :     */
     270           28 :     stop_token get_token() const noexcept
     271              :     {
     272           28 :         return stop_token(state_);
     273              :     }
     274              : 
     275              :     /** Request stop on all tokens created from this source.
     276              : 
     277              :         This function is idempotent—calling it multiple times
     278              :         has the same effect as calling it once. After the first
     279              :         call, all subsequent calls return `false`.
     280              : 
     281              :         @return `true` if this was the first stop request,
     282              :                 `false` if stop was already requested.
     283              :     */
     284           15 :     bool request_stop() noexcept
     285              :     {
     286           15 :         if (!state_)
     287            0 :             return false;
     288           15 :         return state_->request_stop();
     289              :     }
     290              : 
     291              :     /** Check if stop has been requested.
     292              : 
     293              :         @return `true` if `request_stop()` has been called,
     294              :                 `false` otherwise.
     295              :     */
     296              :     bool stop_requested() const noexcept
     297              :     {
     298              :         if (!state_)
     299              :             return false;
     300              :         return state_->stop_requested_.load(std::memory_order_acquire);
     301              :     }
     302              : };
     303              : 
     304              : //----------------------------------------------------------
     305              : //
     306              : // stop_callback
     307              : //
     308              : //----------------------------------------------------------
     309              : 
     310              : /** Register a callback to be invoked when stop is requested.
     311              : 
     312              :     The callback is invoked when the associated stop token receives
     313              :     a stop request. If stop has already been requested when the
     314              :     callback is constructed, it is invoked immediately.
     315              : 
     316              :     The callback is automatically unregistered when the `stop_callback`
     317              :     object is destroyed.
     318              : 
     319              :     @tparam Callback The type of the callback function object.
     320              :                      Must be callable with no arguments.
     321              : 
     322              :     @par Thread Safety
     323              :     Distinct objects: Safe.
     324              :     Shared objects: Unsafe.
     325              : 
     326              :     @par Example
     327              :     @code
     328              :     capy::stop_source source;
     329              :     auto token = source.get_token();
     330              : 
     331              :     // Register callback
     332              :     capy::stop_callback cb(token, [] {
     333              :         std::cout << "Stop requested!\n";
     334              :     });
     335              : 
     336              :     // Requesting stop will invoke the callback
     337              :     source.request_stop();
     338              :     @endcode
     339              : 
     340              :     @see stop_token, stop_source
     341              : */
     342              : template<class Callback>
     343              : class stop_callback
     344              : {
     345              :     std::shared_ptr<detail::stop_state> state_;
     346              :     std::shared_ptr<Callback> callback_;
     347              : 
     348              : public:
     349              :     /** Construct a stop callback.
     350              : 
     351              :         Registers the callback to be invoked when the stop token
     352              :         receives a stop request. If stop has already been requested,
     353              :         the callback is invoked immediately during construction.
     354              : 
     355              :         @param st The stop token to monitor.
     356              :         @param cb The callback to invoke when stop is requested.
     357              :     */
     358            4 :     explicit stop_callback(stop_token const& st, Callback&& cb)
     359            4 :         : state_(st.state_)
     360            4 :         , callback_(std::make_shared<Callback>(std::forward<Callback>(cb)))
     361              :     {
     362            4 :         if (!state_)
     363            0 :             return;  // No-op token
     364              : 
     365              :         // Register callback with shared_ptr to keep it alive
     366            4 :         auto weak_cb = std::weak_ptr<Callback>(callback_);
     367            8 :         state_->register_callback([weak_cb] {
     368            3 :             if (auto cb = weak_cb.lock())
     369              :             {
     370            1 :                 (*cb)();
     371              :             }
     372              :         });
     373            4 :     }
     374              : 
     375              :     /** Destructor.
     376              : 
     377              :         The callback may still be invoked after destruction if
     378              :         stop is requested, as long as the callback object itself
     379              :         remains valid (via shared_ptr).
     380              :     */
     381            4 :     ~stop_callback() = default;
     382              : 
     383              :     // Non-copyable, non-movable
     384              :     stop_callback(stop_callback const&) = delete;
     385              :     stop_callback& operator=(stop_callback const&) = delete;
     386              :     stop_callback(stop_callback&&) = delete;
     387              :     stop_callback& operator=(stop_callback&&) = delete;
     388              : };
     389              : 
     390              : } // namespace capy
     391              : } // namespace boost
     392              : 
     393              : #endif
        

Generated by: LCOV version 2.3