LCOV - code coverage report
Current view: top level - /jenkins/workspace/boost-root/boost/corosio - signal_set.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 93.5 % 31 29
Test Date: 2026-02-04 19:19:32 Functions: 100.0 % 16 16

            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/corosio
       8              : //
       9              : 
      10              : #ifndef BOOST_COROSIO_SIGNAL_SET_HPP
      11              : #define BOOST_COROSIO_SIGNAL_SET_HPP
      12              : 
      13              : #include <boost/corosio/detail/config.hpp>
      14              : #include <boost/corosio/detail/except.hpp>
      15              : #include <boost/corosio/io_object.hpp>
      16              : #include <boost/capy/io_result.hpp>
      17              : #include <boost/capy/error.hpp>
      18              : #include <boost/capy/ex/executor_ref.hpp>
      19              : #include <boost/capy/ex/execution_context.hpp>
      20              : #include <boost/capy/concept/executor.hpp>
      21              : #include <system_error>
      22              : 
      23              : #include <concepts>
      24              : #include <coroutine>
      25              : #include <stop_token>
      26              : #include <system_error>
      27              : 
      28              : /*
      29              :     Signal Set Public API
      30              :     =====================
      31              : 
      32              :     This header provides the public interface for asynchronous signal handling.
      33              :     The implementation is split across platform-specific files:
      34              :       - posix/signals.cpp: Uses sigaction() for robust signal handling
      35              :       - iocp/signals.cpp: Uses C runtime signal() (Windows lacks sigaction)
      36              : 
      37              :     Key design decisions:
      38              : 
      39              :     1. Abstract flag values: The flags_t enum uses arbitrary bit positions
      40              :        (not SA_RESTART, etc.) to avoid including <signal.h> in public headers.
      41              :        The POSIX implementation maps these to actual SA_* constants internally.
      42              : 
      43              :     2. Flag conflict detection: When multiple signal_sets register for the
      44              :        same signal, they must use compatible flags. The first registration
      45              :        establishes the flags; subsequent registrations must match or use
      46              :        dont_care.
      47              : 
      48              :     3. Polymorphic implementation: signal_set_impl is an abstract base that
      49              :        platform-specific implementations (posix_signal_impl, win_signal_impl)
      50              :        derive from. This allows the public API to be platform-agnostic.
      51              : 
      52              :     4. The inline add(int) overload avoids a virtual call for the common case
      53              :        of adding signals without flags (delegates to add(int, none)).
      54              : */
      55              : 
      56              : namespace boost::corosio {
      57              : 
      58              : /** An asynchronous signal set for coroutine I/O.
      59              : 
      60              :     This class provides the ability to perform an asynchronous wait
      61              :     for one or more signals to occur. The signal set registers for
      62              :     signals using sigaction() on POSIX systems or the C runtime
      63              :     signal() function on Windows.
      64              : 
      65              :     @par Thread Safety
      66              :     Distinct objects: Safe.@n
      67              :     Shared objects: Unsafe. A signal_set must not have concurrent
      68              :     wait operations.
      69              : 
      70              :     @par Semantics
      71              :     Wraps platform signal handling (sigaction on POSIX, C runtime
      72              :     signal() on Windows). Operations dispatch to OS signal APIs
      73              :     via the io_context reactor.
      74              : 
      75              :     @par Supported Signals
      76              :     On Windows, the following signals are supported:
      77              :     SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGILL, SIGSEGV.
      78              : 
      79              :     @par Example
      80              :     @code
      81              :     signal_set signals(ctx, SIGINT, SIGTERM);
      82              :     auto [ec, signum] = co_await signals.wait();
      83              :     if (ec == capy::cond::canceled)
      84              :     {
      85              :         // Operation was cancelled via stop_token or cancel()
      86              :     }
      87              :     else if (!ec)
      88              :     {
      89              :         std::cout << "Received signal " << signum << std::endl;
      90              :     }
      91              :     @endcode
      92              : */
      93              : class BOOST_COROSIO_DECL signal_set : public io_object
      94              : {
      95              : public:
      96              :     /** Flags for signal registration.
      97              : 
      98              :         These flags control the behavior of signal handling. Multiple
      99              :         flags can be combined using the bitwise OR operator.
     100              : 
     101              :         @note Flags only have effect on POSIX systems. On Windows,
     102              :         only `none` and `dont_care` are supported; other flags return
     103              :         `operation_not_supported`.
     104              :     */
     105              :     enum flags_t : unsigned
     106              :     {
     107              :         /// Use existing flags if signal is already registered.
     108              :         /// When adding a signal that's already registered by another
     109              :         /// signal_set, this flag indicates acceptance of whatever
     110              :         /// flags were used for the existing registration.
     111              :         dont_care = 1u << 16,
     112              : 
     113              :         /// No special flags.
     114              :         none = 0,
     115              : 
     116              :         /// Restart interrupted system calls.
     117              :         /// Equivalent to SA_RESTART on POSIX systems.
     118              :         restart = 1u << 0,
     119              : 
     120              :         /// Don't generate SIGCHLD when children stop.
     121              :         /// Equivalent to SA_NOCLDSTOP on POSIX systems.
     122              :         no_child_stop = 1u << 1,
     123              : 
     124              :         /// Don't create zombie processes on child termination.
     125              :         /// Equivalent to SA_NOCLDWAIT on POSIX systems.
     126              :         no_child_wait = 1u << 2,
     127              : 
     128              :         /// Don't block the signal while its handler runs.
     129              :         /// Equivalent to SA_NODEFER on POSIX systems.
     130              :         no_defer = 1u << 3,
     131              : 
     132              :         /// Reset handler to SIG_DFL after one invocation.
     133              :         /// Equivalent to SA_RESETHAND on POSIX systems.
     134              :         reset_handler = 1u << 4
     135              :     };
     136              : 
     137              :     /// Combine two flag values.
     138            4 :     friend constexpr flags_t operator|(flags_t a, flags_t b) noexcept
     139              :     {
     140              :         return static_cast<flags_t>(
     141            4 :             static_cast<unsigned>(a) | static_cast<unsigned>(b));
     142              :     }
     143              : 
     144              :     /// Mask two flag values.
     145          448 :     friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
     146              :     {
     147              :         return static_cast<flags_t>(
     148          448 :             static_cast<unsigned>(a) & static_cast<unsigned>(b));
     149              :     }
     150              : 
     151              :     /// Compound assignment OR.
     152            2 :     friend constexpr flags_t& operator|=(flags_t& a, flags_t b) noexcept
     153              :     {
     154            2 :         return a = a | b;
     155              :     }
     156              : 
     157              :     /// Compound assignment AND.
     158              :     friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
     159              :     {
     160              :         return a = a & b;
     161              :     }
     162              : 
     163              :     /// Bitwise NOT (complement).
     164              :     friend constexpr flags_t operator~(flags_t a) noexcept
     165              :     {
     166              :         return static_cast<flags_t>(~static_cast<unsigned>(a));
     167              :     }
     168              : 
     169              : private:
     170              :     struct wait_awaitable
     171              :     {
     172              :         signal_set& s_;
     173              :         std::stop_token token_;
     174              :         mutable std::error_code ec_;
     175              :         mutable int signal_number_ = 0;
     176              : 
     177           26 :         explicit wait_awaitable(signal_set& s) noexcept : s_(s) {}
     178              : 
     179           26 :         bool await_ready() const noexcept
     180              :         {
     181           26 :             return token_.stop_requested();
     182              :         }
     183              : 
     184           26 :         capy::io_result<int> await_resume() const noexcept
     185              :         {
     186           26 :             if (token_.stop_requested())
     187            0 :                 return {capy::error::canceled};
     188           26 :             return {ec_, signal_number_};
     189              :         }
     190              : 
     191              :         template<typename Ex>
     192           26 :         auto await_suspend(
     193              :             std::coroutine_handle<> h,
     194              :             Ex const& ex,
     195              :             std::stop_token token) -> std::coroutine_handle<>
     196              :         {
     197           26 :             token_ = std::move(token);
     198           26 :             s_.get().wait(h, ex, token_, &ec_, &signal_number_);
     199           26 :             return std::noop_coroutine();
     200              :         }
     201              :     };
     202              : 
     203              : public:
     204              :     struct signal_set_impl : io_object_impl
     205              :     {
     206              :         virtual void wait(
     207              :             std::coroutine_handle<>,
     208              :             capy::executor_ref,
     209              :             std::stop_token,
     210              :             std::error_code*,
     211              :             int*) = 0;
     212              : 
     213              :         virtual std::error_code add(int signal_number, flags_t flags) = 0;
     214              :         virtual std::error_code remove(int signal_number) = 0;
     215              :         virtual std::error_code clear() = 0;
     216              :         virtual void cancel() = 0;
     217              :     };
     218              : 
     219              :     /** Destructor.
     220              : 
     221              :         Cancels any pending operations and releases signal resources.
     222              :     */
     223              :     ~signal_set();
     224              : 
     225              :     /** Construct an empty signal set.
     226              : 
     227              :         @param ctx The execution context that will own this signal set.
     228              :     */
     229              :     explicit signal_set(capy::execution_context& ctx);
     230              : 
     231              :     /** Construct a signal set with initial signals.
     232              : 
     233              :         @param ctx The execution context that will own this signal set.
     234              :         @param signal First signal number to add.
     235              :         @param signals Additional signal numbers to add.
     236              : 
     237              :         @throws std::system_error Thrown on failure.
     238              :     */
     239              :     template<std::convertible_to<int>... Signals>
     240           36 :     signal_set(
     241              :         capy::execution_context& ctx,
     242              :         int signal,
     243              :         Signals... signals)
     244           36 :         : signal_set(ctx)
     245              :     {
     246           44 :         auto check = [](std::error_code ec) {
     247           44 :             if( ec )
     248            0 :                 throw std::system_error(ec);
     249              :         };
     250           36 :         check(add(signal));
     251            6 :         (check(add(signals)), ...);
     252           36 :     }
     253              : 
     254              :     /** Move constructor.
     255              : 
     256              :         Transfers ownership of the signal set resources.
     257              : 
     258              :         @param other The signal set to move from.
     259              :     */
     260              :     signal_set(signal_set&& other) noexcept;
     261              : 
     262              :     /** Move assignment operator.
     263              : 
     264              :         Closes any existing signal set and transfers ownership.
     265              :         The source and destination must share the same execution context.
     266              : 
     267              :         @param other The signal set to move from.
     268              : 
     269              :         @return Reference to this signal set.
     270              : 
     271              :         @throws std::logic_error if the signal sets have different
     272              :             execution contexts.
     273              :     */
     274              :     signal_set& operator=(signal_set&& other);
     275              : 
     276              :     signal_set(signal_set const&) = delete;
     277              :     signal_set& operator=(signal_set const&) = delete;
     278              : 
     279              :     /** Add a signal to the signal set.
     280              : 
     281              :         This function adds the specified signal to the set with the
     282              :         specified flags. It has no effect if the signal is already
     283              :         in the set with the same flags.
     284              : 
     285              :         If the signal is already registered globally (by another
     286              :         signal_set) and the flags differ, an error is returned
     287              :         unless one of them has the `dont_care` flag.
     288              : 
     289              :         @param signal_number The signal to be added to the set.
     290              :         @param flags The flags to apply when registering the signal.
     291              :             On POSIX systems, these map to sigaction() flags.
     292              :             On Windows, flags are accepted but ignored.
     293              : 
     294              :         @return Success, or an error if the signal could not be added.
     295              :             Returns `errc::invalid_argument` if the signal is already
     296              :             registered with different flags.
     297              :     */
     298              :     std::error_code add(int signal_number, flags_t flags);
     299              : 
     300              :     /** Add a signal to the signal set with default flags.
     301              : 
     302              :         This is equivalent to calling `add(signal_number, none)`.
     303              : 
     304              :         @param signal_number The signal to be added to the set.
     305              : 
     306              :         @return Success, or an error if the signal could not be added.
     307              :     */
     308           58 :     std::error_code add(int signal_number)
     309              :     {
     310           58 :         return add(signal_number, none);
     311              :     }
     312              : 
     313              :     /** Remove a signal from the signal set.
     314              : 
     315              :         This function removes the specified signal from the set. It has
     316              :         no effect if the signal is not in the set.
     317              : 
     318              :         @param signal_number The signal to be removed from the set.
     319              : 
     320              :         @return Success, or an error if the signal could not be removed.
     321              :     */
     322              :     std::error_code remove(int signal_number);
     323              : 
     324              :     /** Remove all signals from the signal set.
     325              : 
     326              :         This function removes all signals from the set. It has no effect
     327              :         if the set is already empty.
     328              : 
     329              :         @return Success, or an error if resetting any signal handler fails.
     330              :     */
     331              :     std::error_code clear();
     332              : 
     333              :     /** Cancel all operations associated with the signal set.
     334              : 
     335              :         This function forces the completion of any pending asynchronous
     336              :         wait operations against the signal set. The handler for each
     337              :         cancelled operation will be invoked with an error code that
     338              :         compares equal to `capy::cond::canceled`.
     339              : 
     340              :         Cancellation does not alter the set of registered signals.
     341              :     */
     342              :     void cancel();
     343              : 
     344              :     /** Wait for a signal to be delivered.
     345              : 
     346              :         The operation supports cancellation via `std::stop_token` through
     347              :         the affine awaitable protocol. If the associated stop token is
     348              :         triggered, the operation completes immediately with an error
     349              :         that compares equal to `capy::cond::canceled`.
     350              : 
     351              :         @par Example
     352              :         @code
     353              :         signal_set signals(ctx, SIGINT);
     354              :         auto [ec, signum] = co_await signals.wait();
     355              :         if (ec == capy::cond::canceled)
     356              :         {
     357              :             // Cancelled via stop_token or cancel()
     358              :             co_return;
     359              :         }
     360              :         if (ec)
     361              :         {
     362              :             // Handle other errors
     363              :             co_return;
     364              :         }
     365              :         // Process signal
     366              :         std::cout << "Received signal " << signum << std::endl;
     367              :         @endcode
     368              : 
     369              :         @return An awaitable that completes with `io_result<int>`.
     370              :             Returns the signal number when a signal is delivered,
     371              :             or an error code on failure. Compare against error conditions
     372              :             (e.g., `ec == capy::cond::canceled`) rather than error codes.
     373              :     */
     374           26 :     auto wait()
     375              :     {
     376           26 :         return wait_awaitable(*this);
     377              :     }
     378              : 
     379              : private:
     380          142 :     signal_set_impl& get() const noexcept
     381              :     {
     382          142 :         return *static_cast<signal_set_impl*>(impl_);
     383              :     }
     384              : };
     385              : 
     386              : } // namespace boost::corosio
     387              : 
     388              : #endif
        

Generated by: LCOV version 2.3