LCOV - code coverage report
Current view: top level - include/boost/corosio - signal_set.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 93.3 % 30 28
Test Date: 2026-02-12 21:00:53 Functions: 100.0 % 16 16

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

Generated by: LCOV version 2.3