LCOV - code coverage report
Current view: top level - corosio/native/detail/posix - posix_signal_service.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 99.0 % 399 395 4
Test Date: 2026-09-25 22:49:29 Functions: 97.1 % 35 34 1

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Steve Gerbino
       3                 : // Copyright (c) 2026 Michael Vandeberg
       4                 : //
       5                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       6                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       7                 : //
       8                 : // Official repository: https://github.com/cppalliance/corosio
       9                 : //
      10                 : 
      11                 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
      12                 : #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
      13                 : 
      14                 : #include <boost/corosio/detail/platform.hpp>
      15                 : 
      16                 : #if BOOST_COROSIO_POSIX
      17                 : 
      18                 : #include <boost/corosio/native/detail/posix/posix_signal.hpp>
      19                 : 
      20                 : #include <boost/corosio/detail/config.hpp>
      21                 : #include <boost/capy/ex/execution_context.hpp>
      22                 : #include <boost/corosio/detail/scheduler.hpp>
      23                 : #include <boost/corosio/native/detail/make_err.hpp>
      24                 : #include <boost/capy/error.hpp>
      25                 : 
      26                 : #include <mutex>
      27                 : #include <tuple>
      28                 : 
      29                 : #include <errno.h>
      30                 : #include <fcntl.h>
      31                 : #include <signal.h>
      32                 : #include <unistd.h>
      33                 : 
      34                 : /*
      35                 :     POSIX Signal Service
      36                 :     ====================
      37                 : 
      38                 :     Concrete signal service implementation for POSIX backends. Manages signal
      39                 :     registrations via sigaction() and dispatches completions through the
      40                 :     scheduler. One instance per execution_context, created by
      41                 :     get_signal_service().
      42                 : 
      43                 :     See the block comment further down for the full architecture overview.
      44                 : */
      45                 : 
      46                 : /*
      47                 :     POSIX Signal Implementation
      48                 :     ===========================
      49                 : 
      50                 :     This file implements signal handling for POSIX systems using sigaction().
      51                 :     The implementation supports signal flags (SA_RESTART, etc.) and integrates
      52                 :     with any POSIX-compatible scheduler via the abstract scheduler interface.
      53                 : 
      54                 :     Architecture Overview
      55                 :     ---------------------
      56                 : 
      57                 :     Three layers manage signal registrations:
      58                 : 
      59                 :     1. signal_state (global singleton)
      60                 :        - Tracks the global service list and per-signal registration counts
      61                 :        - Stores the flags used for first registration of each signal (for
      62                 :          conflict detection when multiple signal_sets register same signal)
      63                 :        - Owns the mutex that protects signal handler installation/removal
      64                 : 
      65                 :     2. posix_signal_service (one per execution_context)
      66                 :        - Maintains registrations_[] table indexed by signal number
      67                 :        - Each slot is a doubly-linked list of signal_registrations for that signal
      68                 :        - Also maintains impl_list_ of all posix_signal objects it owns
      69                 : 
      70                 :     3. posix_signal (one per signal_set)
      71                 :        - Owns a singly-linked list (sorted by signal number) of signal_registrations
      72                 :        - Contains the pending_op_ used for wait operations
      73                 : 
      74                 :     Signal Delivery Flow
      75                 :     --------------------
      76                 : 
      77                 :     Delivery uses the self-pipe trick so the signal handler itself performs
      78                 :     only async-signal-safe work (mirrors Boost.Asio):
      79                 : 
      80                 :     1. Signal arrives -> corosio_posix_signal_handler(). The handler only
      81                 :        write()s the signal number to the global self-pipe (write_fd) and
      82                 :        restores errno. No locks, no allocation, no scheduler dispatch.
      83                 : 
      84                 :     2. The read end of the pipe is watched by one backend's event loop
      85                 :        (registered via scheduler::register_signal_reader on the first
      86                 :        registration). When it becomes readable the backend drains it
      87                 :        (drain_signal_pipe) and calls deliver_signal() in normal context.
      88                 : 
      89                 :     3. deliver_signal() iterates all posix_signal_service services:
      90                 :        - If a signal_set is waiting (impl->waiting_ == true), post the signal_op
      91                 :          to the scheduler for immediate completion
      92                 :        - Otherwise, increment reg->undelivered to queue the signal
      93                 : 
      94                 :     4. When wait() is called via start_wait():
      95                 :        - First check for queued signals (undelivered > 0); if found, post
      96                 :          immediate completion without blocking
      97                 :        - Otherwise, set waiting_ = true and call work_started() to keep
      98                 :          the io_context alive
      99                 : 
     100                 :     Locking Protocol
     101                 :     ----------------
     102                 : 
     103                 :     Two mutex levels exist (MUST acquire in this order to avoid deadlock):
     104                 :       1. signal_state::mutex - protects handler registration and service list
     105                 :       2. posix_signal_service::mutex_ - protects per-service registration tables
     106                 : 
     107                 :     Async-Signal-Safety
     108                 :     -------------------
     109                 : 
     110                 :     The C signal handler (corosio_posix_signal_handler) performs only
     111                 :     async-signal-safe operations: it reads the single global write_fd and
     112                 :     calls write(), saving/restoring errno. It never locks a mutex, allocates
     113                 :     memory, or dispatches through the scheduler. All of that happens in
     114                 :     deliver_signal(), which runs in normal thread context from the backend
     115                 :     event loop after draining the self-pipe. There is therefore no
     116                 :     self-deadlock risk if a signal arrives while a thread holds state->mutex
     117                 :     or service->mutex_.
     118                 : 
     119                 :     Flag Handling
     120                 :     -------------
     121                 : 
     122                 :     - Flags are abstract values in the public API (signal_set::flags_t)
     123                 :     - flags_supported() validates that requested flags are available on
     124                 :       this platform; returns false if SA_NOCLDWAIT is unavailable and
     125                 :       no_child_wait is requested
     126                 :     - to_sigaction_flags() maps validated flags to actual SA_* constants
     127                 :     - First registration of a signal establishes the flags; subsequent
     128                 :       registrations must be compatible (same flags or dont_care)
     129                 :     - Requesting unavailable flags returns operation_not_supported
     130                 : 
     131                 :     Work Tracking
     132                 :     -------------
     133                 : 
     134                 :     When waiting for a signal:
     135                 :       - start_wait() calls sched_->work_started() to prevent io_context::run()
     136                 :         from returning while we wait
     137                 :       - signal_op::svc is set to point to the service
     138                 :       - signal_op::operator()() calls work_finished() after resuming the coroutine
     139                 : 
     140                 :     If a signal was already queued (undelivered > 0), no work tracking is needed
     141                 :     because completion is posted immediately.
     142                 : */
     143                 : 
     144                 : namespace boost::corosio {
     145                 : 
     146                 : namespace detail {
     147                 : 
     148                 : /** Signal service for POSIX backends.
     149                 : 
     150                 :     Manages signal registrations via sigaction() and dispatches signal
     151                 :     completions through the scheduler. One instance per execution_context.
     152                 : */
     153                 : class BOOST_COROSIO_DECL posix_signal_service final
     154                 :     : public capy::execution_context::service
     155                 :     , public io_object::io_service
     156                 : {
     157                 : public:
     158                 :     using key_type = posix_signal_service;
     159                 : 
     160                 :     posix_signal_service(capy::execution_context& ctx, scheduler& sched);
     161                 :     ~posix_signal_service() override;
     162                 : 
     163                 :     posix_signal_service(posix_signal_service const&)            = delete;
     164                 :     posix_signal_service& operator=(posix_signal_service const&) = delete;
     165                 : 
     166                 :     io_object::implementation* construct() override;
     167                 : 
     168 HIT         184 :     void destroy(io_object::implementation* p) override
     169                 :     {
     170             184 :         auto& impl              = static_cast<posix_signal&>(*p);
     171             184 :         [[maybe_unused]] auto n = impl.clear();
     172             184 :         impl.disarm_stop();
     173             184 :         impl.cancel();
     174             184 :         destroy_impl(impl);
     175             184 :     }
     176                 : 
     177                 :     /** Shut down the service.
     178                 : 
     179                 :         Destroys every implementation the service still owns and gives
     180                 :         each of their registrations back to the process-global table.
     181                 :     */
     182                 :     void shutdown() override;
     183                 : 
     184                 :     void destroy_impl(posix_signal& impl);
     185                 : 
     186                 :     std::error_code add_signal(
     187                 :         posix_signal& impl, int signal_number, signal_set::flags_t flags);
     188                 : 
     189                 :     std::error_code remove_signal(posix_signal& impl, int signal_number);
     190                 : 
     191                 :     std::error_code clear_signals(posix_signal& impl);
     192                 : 
     193                 :     void cancel_wait(posix_signal& impl);
     194                 :     void start_wait(posix_signal& impl, signal_op* op);
     195                 : 
     196                 :     /** Cancel an in-flight wait on behalf of a stop token.
     197                 : 
     198                 :         Identical to @ref cancel_wait except that it does not set the
     199                 :         sticky `cancelled_` latch: a stop token scopes to one operation,
     200                 :         so a request arriving after the wait completed must do nothing.
     201                 :     */
     202                 :     void cancel_wait_token(posix_signal& impl) noexcept;
     203                 : 
     204                 :     /** Clear the per-operation stop flag before a new wait arms.
     205                 : 
     206                 :         Lives here rather than on the implementation because `mutex_` is
     207                 :         the service's; the service is a friend of `posix_signal`, not the
     208                 :         reverse.
     209                 :     */
     210             985 :     void reset_token_cancel(posix_signal& impl) noexcept
     211                 :     {
     212             985 :         std::lock_guard lock(mutex_);
     213             985 :         impl.token_cancelled_ = false;
     214             985 :     }
     215                 : 
     216                 :     static void deliver_signal(int signal_number);
     217                 : 
     218                 :     void work_started() noexcept;
     219                 :     void work_finished() noexcept;
     220                 :     void post(signal_op* op);
     221                 : 
     222                 : private:
     223                 :     static void add_service(posix_signal_service* service);
     224                 :     static void remove_service(posix_signal_service* service);
     225                 : 
     226                 :     scheduler* sched_;
     227                 :     std::mutex mutex_;
     228                 : 
     229                 :     // Registers the signal self-pipe's read end with sched_ exactly once per
     230                 :     // service, so every io_context that waits on a signal can drain the pipe.
     231                 :     // A once_flag (not a bool under mutex_) because registration must run
     232                 :     // without holding mutex_ or the signal-state mutex — see add_signal.
     233                 :     std::mutex reader_mutex_;
     234                 :     bool reader_registered_ = false;
     235                 : 
     236                 :     intrusive_list<posix_signal> impl_list_;
     237                 : 
     238                 :     // Per-signal registration table
     239                 :     signal_registration* registrations_[max_signal_number];
     240                 : 
     241                 :     // Registration counts for each signal
     242                 :     std::size_t registration_count_[max_signal_number];
     243                 : 
     244                 :     // Linked list of all posix_signal_service services for signal delivery
     245                 :     posix_signal_service* next_ = nullptr;
     246                 :     posix_signal_service* prev_ = nullptr;
     247                 : };
     248                 : 
     249                 : /** Get or create the signal service for the given context.
     250                 : 
     251                 :     This function is called by the concrete scheduler during initialization
     252                 :     to create the signal service with a reference to itself.
     253                 : 
     254                 :     @param ctx Reference to the owning execution_context.
     255                 :     @param sched Reference to the scheduler for posting completions.
     256                 :     @return Reference to the signal service.
     257                 : */
     258                 : posix_signal_service&
     259                 : get_signal_service(capy::execution_context& ctx, scheduler& sched);
     260                 : 
     261                 : } // namespace detail
     262                 : 
     263                 : } // namespace boost::corosio
     264                 : 
     265                 : // ---------------------------------------------------------------------------
     266                 : // Inline implementation
     267                 : // ---------------------------------------------------------------------------
     268                 : 
     269                 : namespace boost::corosio {
     270                 : 
     271                 : namespace detail {
     272                 : 
     273                 : namespace posix_signal_detail {
     274                 : 
     275                 : struct signal_state
     276                 : {
     277                 :     std::mutex mutex;
     278                 :     posix_signal_service* service_list                      = nullptr;
     279                 :     std::size_t registration_count[max_signal_number]       = {};
     280                 :     signal_set::flags_t registered_flags[max_signal_number] = {};
     281                 : 
     282                 :     // Self-pipe used to defer signal delivery out of handler context.
     283                 :     // The C handler writes the signal number to write_fd (async-signal-
     284                 :     // safe); a backend event loop drains read_fd and calls deliver_signal()
     285                 :     // in normal context. Created once (on the first signal registration) and
     286                 :     // kept for the process lifetime. Each posix_signal_service registers the
     287                 :     // read end with its own scheduler (see reader_once_) so every running
     288                 :     // io_context can drain it; multiple readers on one pipe are safe because
     289                 :     // each signal is a fixed sizeof(int) record read atomically.
     290                 :     int read_fd  = -1;
     291                 :     int write_fd = -1;
     292                 : };
     293                 : 
     294                 : BOOST_COROSIO_DECL signal_state* get_signal_state();
     295                 : 
     296                 : // Check if requested flags are supported on this platform.
     297                 : // Returns true if all flags are supported, false otherwise.
     298                 : inline bool
     299             207 : flags_supported([[maybe_unused]] signal_set::flags_t flags)
     300                 : {
     301                 : #ifndef SA_NOCLDWAIT
     302                 :     if (flags & signal_set::no_child_wait)
     303                 :         return false;
     304                 : #endif
     305             207 :     return true;
     306                 : }
     307                 : 
     308                 : // Map abstract flags to sigaction() flags.
     309                 : // Caller must ensure flags_supported() returns true first.
     310                 : inline int
     311             159 : to_sigaction_flags(signal_set::flags_t flags)
     312                 : {
     313             159 :     int sa_flags = 0;
     314             159 :     if (flags & signal_set::restart)
     315              23 :         sa_flags |= SA_RESTART;
     316             159 :     if (flags & signal_set::no_child_stop)
     317               3 :         sa_flags |= SA_NOCLDSTOP;
     318                 : #ifdef SA_NOCLDWAIT
     319             159 :     if (flags & signal_set::no_child_wait)
     320               2 :         sa_flags |= SA_NOCLDWAIT;
     321                 : #endif
     322             159 :     if (flags & signal_set::no_defer)
     323               4 :         sa_flags |= SA_NODEFER;
     324             159 :     if (flags & signal_set::reset_handler)
     325               2 :         sa_flags |= SA_RESETHAND;
     326             159 :     return sa_flags;
     327                 : }
     328                 : 
     329                 : // Check if two flag values are compatible
     330                 : inline bool
     331              39 : flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
     332                 : {
     333                 :     // dont_care is always compatible
     334              76 :     if ((existing & signal_set::dont_care) ||
     335              37 :         (requested & signal_set::dont_care))
     336               7 :         return true;
     337                 : 
     338                 :     // Mask out dont_care bit for comparison
     339              32 :     constexpr auto mask = ~signal_set::dont_care;
     340              32 :     return (existing & mask) == (requested & mask);
     341                 : }
     342                 : 
     343                 : // Lazily create the global signal self-pipe. Idempotent; call under
     344                 : // state->mutex before installing the first signal handler so write_fd is
     345                 : // valid by the time the handler can fire. Both ends are non-blocking and
     346                 : // close-on-exec (mirrors the reactor self-pipe setup in select_scheduler).
     347                 : // Returns the failing call's errno and leaves the fds at -1 if creation
     348                 : // fails: an exhausted descriptor table and a rejected fcntl are different
     349                 : // problems to the caller of add().
     350                 : [[nodiscard]] inline std::error_code
     351             207 : open_signal_pipe(signal_state* state)
     352                 : {
     353             207 :     if (state->read_fd >= 0)
     354             193 :         return {};
     355                 : 
     356                 :     int fds[2];
     357              14 :     if (::pipe(fds) < 0)
     358               1 :         return make_err(errno);
     359                 : 
     360              30 :     for (int i = 0; i < 2; ++i)
     361                 :     {
     362              23 :         int fl = ::fcntl(fds[i], F_GETFL, 0);
     363              42 :         if (fl == -1 || ::fcntl(fds[i], F_SETFL, fl | O_NONBLOCK) == -1 ||
     364              19 :             ::fcntl(fds[i], F_SETFD, FD_CLOEXEC) == -1)
     365                 :         {
     366               6 :             auto ec = make_err(errno);
     367               6 :             ::close(fds[0]);
     368               6 :             ::close(fds[1]);
     369               6 :             return ec;
     370                 :         }
     371                 :     }
     372                 : 
     373               7 :     state->read_fd  = fds[0];
     374               7 :     state->write_fd = fds[1];
     375               7 :     return {};
     376                 : }
     377                 : 
     378                 : // C signal handler. Async-signal-safe: it touches only the single global
     379                 : // write_fd (an int set before any handler is installed) and calls write(),
     380                 : // which POSIX lists as async-signal-safe. errno is saved and restored so an
     381                 : // interrupted foreground syscall is unaffected. A full pipe (write returns
     382                 : // EAGAIN) or a short write is intentionally dropped — the reactor still
     383                 : // coalesces because deliver_signal reports the signal to every waiting set.
     384                 : inline void
     385             317 : corosio_posix_signal_handler(int signal_number)
     386                 : {
     387             317 :     int saved_errno     = errno;
     388             317 :     signal_state* state = get_signal_state();
     389                 :     [[maybe_unused]] ssize_t r =
     390             317 :         ::write(state->write_fd, &signal_number, sizeof(int));
     391             317 :     errno = saved_errno;
     392                 :     // With sigaction(), the handler persists automatically (unlike some
     393                 :     // signal() implementations that reset to SIG_DFL).
     394             317 : }
     395                 : 
     396                 : // Drain the signal self-pipe and deliver each pending signal. Runs in normal
     397                 : // thread context from the backend event loop, so deliver_signal()'s mutex
     398                 : // locking and scheduler post are safe here. Reads until EAGAIN (edge-
     399                 : // triggered backends require a full drain per readiness event).
     400                 : inline void
     401             317 : drain_signal_pipe()
     402                 : {
     403             317 :     signal_state* state = get_signal_state();
     404                 :     int signal_number;
     405             634 :     while (::read(state->read_fd, &signal_number, sizeof(int)) ==
     406                 :            static_cast<ssize_t>(sizeof(int)))
     407                 :     {
     408             317 :         posix_signal_service::deliver_signal(signal_number);
     409                 :     }
     410             317 : }
     411                 : 
     412                 : } // namespace posix_signal_detail
     413                 : 
     414                 : // signal_op implementation
     415                 : 
     416                 : inline void
     417             321 : signal_op::operator()()
     418                 : {
     419             321 :     if (ec_out)
     420             321 :         *ec_out = {};
     421             321 :     if (signal_out)
     422             321 :         *signal_out = signal_number;
     423                 : 
     424                 :     // Capture svc before resuming (coro may destroy us)
     425             321 :     auto* service = svc;
     426             321 :     svc           = nullptr;
     427                 : 
     428             321 :     cont.h = h;
     429             321 :     d.post(cont);
     430                 : 
     431                 :     // Balance the work_started() from start_wait
     432             321 :     if (service)
     433             319 :         service->work_finished();
     434             321 : }
     435                 : 
     436                 : inline void
     437 MIS           0 : signal_op::destroy()
     438                 : {
     439                 :     // No-op: signal_op is embedded in posix_signal
     440               0 : }
     441                 : 
     442                 : // posix_signal implementation
     443                 : 
     444 HIT         190 : inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
     445             190 :     : svc_(svc)
     446                 : {
     447             190 : }
     448                 : 
     449                 : inline std::coroutine_handle<>
     450            1106 : posix_signal::wait(
     451                 :     std::coroutine_handle<> h,
     452                 :     capy::executor_ref d,
     453                 :     std::stop_token token,
     454                 :     std::error_code* ec,
     455                 :     int* signal_out)
     456                 : {
     457            1106 :     pending_op_.h             = h;
     458            1106 :     pending_op_.d             = d;
     459            1106 :     pending_op_.ec_out        = ec;
     460            1106 :     pending_op_.signal_out    = signal_out;
     461            1106 :     pending_op_.signal_number = 0;
     462                 : 
     463                 :     // Disarm any callback left over from a previous wait before doing
     464                 :     // anything else, including the early return below: otherwise that
     465                 :     // path leaves this object owning a callback it no longer uses.
     466                 :     // Outside start_wait's lock on purpose: ~stop_callback blocks until a
     467                 :     // concurrently running callback returns, and that callback takes
     468                 :     // posix_signal_service::mutex_.
     469            1106 :     stop_cb_.reset();
     470                 : 
     471            1106 :     if (token.stop_requested())
     472                 :     {
     473             121 :         if (ec)
     474             121 :             *ec = make_error_code(capy::error::canceled);
     475             121 :         if (signal_out)
     476             121 :             *signal_out = 0;
     477             121 :         pending_op_.cont.h = h;
     478             121 :         d.post(pending_op_.cont);
     479                 :         // completion is always posted to scheduler queue, never inline.
     480             121 :         return std::noop_coroutine();
     481                 :     }
     482                 : 
     483                 :     // Clearing the flag before arming is load-bearing: reset_token_cancel
     484                 :     // must run immediately before emplace, not before the early return
     485                 :     // above.
     486             985 :     svc_.reset_token_cancel(*this);
     487             985 :     if (token.stop_possible())
     488             655 :         stop_cb_.emplace(token, token_canceller{this});
     489                 : 
     490             985 :     svc_.start_wait(*this, &pending_op_);
     491                 :     // completion is always posted to scheduler queue, never inline.
     492             985 :     return std::noop_coroutine();
     493                 : }
     494                 : 
     495                 : inline std::error_code
     496             211 : posix_signal::add(int signal_number, signal_set::flags_t flags)
     497                 : {
     498             211 :     return svc_.add_signal(*this, signal_number, flags);
     499                 : }
     500                 : 
     501                 : inline std::error_code
     502              26 : posix_signal::remove(int signal_number)
     503                 : {
     504              26 :     return svc_.remove_signal(*this, signal_number);
     505                 : }
     506                 : 
     507                 : inline std::error_code
     508             198 : posix_signal::clear()
     509                 : {
     510             198 :     return svc_.clear_signals(*this);
     511                 : }
     512                 : 
     513                 : inline void
     514             201 : posix_signal::cancel() noexcept
     515                 : {
     516             201 :     svc_.cancel_wait(*this);
     517             201 : }
     518                 : 
     519                 : // posix_signal_service implementation
     520                 : 
     521            2241 : inline posix_signal_service::posix_signal_service(
     522            2241 :     capy::execution_context&, scheduler& sched)
     523            2241 :     : sched_(&sched)
     524                 : {
     525          145665 :     for (int i = 0; i < max_signal_number; ++i)
     526                 :     {
     527          143424 :         registrations_[i]      = nullptr;
     528          143424 :         registration_count_[i] = 0;
     529                 :     }
     530            2241 :     add_service(this);
     531            2241 : }
     532                 : 
     533            4482 : inline posix_signal_service::~posix_signal_service()
     534                 : {
     535            2241 :     remove_service(this);
     536            4482 : }
     537                 : 
     538                 : inline void
     539            2241 : posix_signal_service::shutdown()
     540                 : {
     541                 :     // Collected under the locks below and deleted after they are released:
     542                 :     // ~posix_signal destroys an armed stop_cb_, and ~stop_callback blocks
     543                 :     // until a concurrently running token_canceller returns -- which takes
     544                 :     // mutex_. Deleting while still holding mutex_ would self-deadlock the
     545                 :     // same way disarm_stop() would if called inside the locked loop.
     546            2241 :     intrusive_list<posix_signal> doomed;
     547                 : 
     548                 :     {
     549                 :         posix_signal_detail::signal_state* state =
     550            2241 :             posix_signal_detail::get_signal_state();
     551            2241 :         std::lock_guard state_lock(state->mutex);
     552            2241 :         std::lock_guard lock(mutex_);
     553                 : 
     554            2247 :         for (auto* impl = impl_list_.pop_front(); impl != nullptr;
     555               6 :              impl       = impl_list_.pop_front())
     556                 :         {
     557              12 :             while (auto* reg = impl->signals_)
     558                 :             {
     559               6 :                 int const signal_number = reg->signal_number;
     560                 : 
     561                 :                 // The registration table outlives every io_context, so a set
     562                 :                 // still registered here has to give its count and disposition
     563                 :                 // back the way clear() would: otherwise the signal stays
     564                 :                 // installed with these flags and the next add() of it is
     565                 :                 // refused. The per-node table unlink clear() also does is
     566                 :                 // skipped in favour of the wholesale null-out below.
     567               6 :                 if (state->registration_count[signal_number] == 1)
     568                 :                 {
     569               4 :                     struct sigaction sa = {};
     570               4 :                     sa.sa_handler       = SIG_DFL;
     571               4 :                     sigemptyset(&sa.sa_mask);
     572               4 :                     sa.sa_flags = 0;
     573               4 :                     std::ignore = ::sigaction(signal_number, &sa, nullptr);
     574               4 :                     state->registered_flags[signal_number] = signal_set::none;
     575                 :                 }
     576                 : 
     577               6 :                 --state->registration_count[signal_number];
     578               6 :                 --registration_count_[signal_number];
     579                 : 
     580               6 :                 impl->signals_ = reg->next_in_set;
     581               6 :                 delete reg;
     582               6 :             }
     583               6 :             doomed.push_back(impl);
     584                 :         }
     585                 : 
     586                 :         // Every live registration hung off an implementation in impl_list_,
     587                 :         // so the whole table goes stale at once and can be dropped wholesale
     588                 :         // rather than node by node. It has to be dropped: deliver_signal()
     589                 :         // walks this service until the destructor unlinks it from the global
     590                 :         // list.
     591          145665 :         for (int i = 0; i < max_signal_number; ++i)
     592          143424 :             registrations_[i] = nullptr;
     593            2241 :     }
     594                 : 
     595            2247 :     for (auto* impl = doomed.pop_front(); impl != nullptr;
     596               6 :          impl       = doomed.pop_front())
     597                 :     {
     598               6 :         delete impl;
     599                 :     }
     600            2241 : }
     601                 : 
     602                 : inline io_object::implementation*
     603             190 : posix_signal_service::construct()
     604                 : {
     605             190 :     auto* impl = new posix_signal(*this);
     606                 : 
     607                 :     {
     608             190 :         std::lock_guard lock(mutex_);
     609             190 :         impl_list_.push_back(impl);
     610             190 :     }
     611                 : 
     612             190 :     return impl;
     613                 : }
     614                 : 
     615                 : inline void
     616             184 : posix_signal_service::destroy_impl(posix_signal& impl)
     617                 : {
     618                 :     {
     619             184 :         std::lock_guard lock(mutex_);
     620             184 :         impl_list_.remove(&impl);
     621             184 :     }
     622                 : 
     623             184 :     delete &impl;
     624             184 : }
     625                 : 
     626                 : inline std::error_code
     627             211 : posix_signal_service::add_signal(
     628                 :     posix_signal& impl, int signal_number, signal_set::flags_t flags)
     629                 : {
     630             211 :     if (signal_number < 0 || signal_number >= max_signal_number)
     631               4 :         return make_error_code(std::errc::invalid_argument);
     632                 : 
     633                 :     // Validate that requested flags are supported on this platform
     634                 :     // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
     635             207 :     if (!posix_signal_detail::flags_supported(flags))
     636 MIS           0 :         return make_error_code(std::errc::operation_not_supported);
     637                 : 
     638                 :     posix_signal_detail::signal_state* state =
     639 HIT         207 :         posix_signal_detail::get_signal_state();
     640                 : 
     641                 :     // Ensure the global self-pipe exists and this service's scheduler is
     642                 :     // watching its read end, BEFORE taking the registration locks. The
     643                 :     // reactor drain path locks the descriptor mutex and then the signal-state
     644                 :     // and service mutexes; register_signal_reader locks the descriptor mutex
     645                 :     // (via register_descriptor), so it must run holding neither of those or
     646                 :     // the lock order would invert (a real deadlock, caught by TSan). call_once
     647                 :     // makes the once-per-service registration safe when two signal_sets on
     648                 :     // this context race add() from different threads.
     649                 :     {
     650             207 :         std::lock_guard state_lock(state->mutex);
     651             207 :         if (auto ec = posix_signal_detail::open_signal_pipe(state))
     652               7 :             return ec;
     653             207 :     }
     654                 :     {
     655                 :         // Success-latched so a failed environmental registration
     656                 :         // (epoll_ctl ENOMEM/ENOSPC) is retried by the next add()
     657                 :         // instead of being lost; the code travels the return channel.
     658             200 :         std::lock_guard reg_lock(reader_mutex_);
     659             200 :         if (!reader_registered_)
     660                 :         {
     661             137 :             if (auto ec = sched_->register_signal_reader(state->read_fd))
     662               2 :                 return ec;
     663             135 :             reader_registered_ = true;
     664                 :         }
     665             200 :     }
     666                 : 
     667             198 :     std::lock_guard state_lock(state->mutex);
     668             198 :     std::lock_guard lock(mutex_);
     669                 : 
     670                 :     // Find insertion point (list is sorted by signal number)
     671             198 :     signal_registration** insertion_point = &impl.signals_;
     672             198 :     signal_registration* reg              = impl.signals_;
     673             221 :     while (reg && reg->signal_number < signal_number)
     674                 :     {
     675              23 :         insertion_point = &reg->next_in_set;
     676              23 :         reg             = reg->next_in_set;
     677                 :     }
     678                 : 
     679                 :     // Already registered in this set - check flag compatibility
     680                 :     // (same signal_set adding same signal twice with different flags)
     681             198 :     if (reg && reg->signal_number == signal_number)
     682                 :     {
     683              13 :         if (!posix_signal_detail::flags_compatible(reg->flags, flags))
     684               4 :             return make_error_code(std::errc::invalid_argument);
     685               9 :         return {};
     686                 :     }
     687                 : 
     688                 :     // Check flag compatibility with global registration
     689                 :     // (different signal_set already registered this signal with different flags)
     690             185 :     if (state->registration_count[signal_number] > 0)
     691                 :     {
     692              26 :         if (!posix_signal_detail::flags_compatible(
     693                 :                 state->registered_flags[signal_number], flags))
     694               2 :             return make_error_code(std::errc::invalid_argument);
     695                 :     }
     696                 : 
     697             183 :     auto* new_reg          = new signal_registration;
     698             183 :     new_reg->signal_number = signal_number;
     699             183 :     new_reg->flags         = flags;
     700             183 :     new_reg->owner         = &impl;
     701             183 :     new_reg->undelivered   = 0;
     702                 : 
     703                 :     // Install signal handler on first global registration
     704             183 :     if (state->registration_count[signal_number] == 0)
     705                 :     {
     706             159 :         struct sigaction sa = {};
     707             159 :         sa.sa_handler       = posix_signal_detail::corosio_posix_signal_handler;
     708             159 :         sigemptyset(&sa.sa_mask);
     709             159 :         sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
     710                 : 
     711             159 :         if (::sigaction(signal_number, &sa, nullptr) < 0)
     712                 :         {
     713               1 :             delete new_reg;
     714               1 :             return make_error_code(std::errc::invalid_argument);
     715                 :         }
     716                 : 
     717                 :         // Store the flags used for first registration
     718             158 :         state->registered_flags[signal_number] = flags;
     719                 :     }
     720                 : 
     721             182 :     new_reg->next_in_set = reg;
     722             182 :     *insertion_point     = new_reg;
     723                 : 
     724             182 :     new_reg->next_in_table = registrations_[signal_number];
     725             182 :     new_reg->prev_in_table = nullptr;
     726             182 :     if (registrations_[signal_number])
     727              18 :         registrations_[signal_number]->prev_in_table = new_reg;
     728             182 :     registrations_[signal_number] = new_reg;
     729                 : 
     730             182 :     ++state->registration_count[signal_number];
     731             182 :     ++registration_count_[signal_number];
     732                 : 
     733             182 :     return {};
     734             198 : }
     735                 : 
     736                 : inline std::error_code
     737              26 : posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
     738                 : {
     739              26 :     if (signal_number < 0 || signal_number >= max_signal_number)
     740               2 :         return make_error_code(std::errc::invalid_argument);
     741                 : 
     742                 :     posix_signal_detail::signal_state* state =
     743              24 :         posix_signal_detail::get_signal_state();
     744              24 :     std::lock_guard state_lock(state->mutex);
     745              24 :     std::lock_guard lock(mutex_);
     746                 : 
     747              24 :     signal_registration** deletion_point = &impl.signals_;
     748              24 :     signal_registration* reg             = impl.signals_;
     749              26 :     while (reg && reg->signal_number < signal_number)
     750                 :     {
     751               2 :         deletion_point = &reg->next_in_set;
     752               2 :         reg            = reg->next_in_set;
     753                 :     }
     754                 : 
     755              24 :     if (!reg || reg->signal_number != signal_number)
     756               3 :         return {};
     757                 : 
     758                 :     // Restore default handler on last global unregistration
     759              21 :     if (state->registration_count[signal_number] == 1)
     760                 :     {
     761              17 :         struct sigaction sa = {};
     762              17 :         sa.sa_handler       = SIG_DFL;
     763              17 :         sigemptyset(&sa.sa_mask);
     764              17 :         sa.sa_flags = 0;
     765                 : 
     766              17 :         if (::sigaction(signal_number, &sa, nullptr) < 0)
     767               1 :             return make_error_code(std::errc::invalid_argument);
     768                 : 
     769                 :         // Clear stored flags
     770              16 :         state->registered_flags[signal_number] = signal_set::none;
     771                 :     }
     772                 : 
     773              20 :     *deletion_point = reg->next_in_set;
     774                 : 
     775              20 :     if (registrations_[signal_number] == reg)
     776              18 :         registrations_[signal_number] = reg->next_in_table;
     777              20 :     if (reg->prev_in_table)
     778               2 :         reg->prev_in_table->next_in_table = reg->next_in_table;
     779              20 :     if (reg->next_in_table)
     780               2 :         reg->next_in_table->prev_in_table = reg->prev_in_table;
     781                 : 
     782              20 :     --state->registration_count[signal_number];
     783              20 :     --registration_count_[signal_number];
     784                 : 
     785              20 :     delete reg;
     786              20 :     return {};
     787              24 : }
     788                 : 
     789                 : inline std::error_code
     790             198 : posix_signal_service::clear_signals(posix_signal& impl)
     791                 : {
     792                 :     posix_signal_detail::signal_state* state =
     793             198 :         posix_signal_detail::get_signal_state();
     794             198 :     std::lock_guard state_lock(state->mutex);
     795             198 :     std::lock_guard lock(mutex_);
     796                 : 
     797             198 :     std::error_code first_error;
     798                 : 
     799             354 :     while (signal_registration* reg = impl.signals_)
     800                 :     {
     801             156 :         int signal_number = reg->signal_number;
     802                 : 
     803             156 :         if (state->registration_count[signal_number] == 1)
     804                 :         {
     805             138 :             struct sigaction sa = {};
     806             138 :             sa.sa_handler       = SIG_DFL;
     807             138 :             sigemptyset(&sa.sa_mask);
     808             138 :             sa.sa_flags = 0;
     809                 : 
     810             138 :             if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
     811               1 :                 first_error = make_error_code(std::errc::invalid_argument);
     812                 : 
     813                 :             // Clear stored flags
     814             138 :             state->registered_flags[signal_number] = signal_set::none;
     815                 :         }
     816                 : 
     817             156 :         impl.signals_ = reg->next_in_set;
     818                 : 
     819             156 :         if (registrations_[signal_number] == reg)
     820             154 :             registrations_[signal_number] = reg->next_in_table;
     821             156 :         if (reg->prev_in_table)
     822               2 :             reg->prev_in_table->next_in_table = reg->next_in_table;
     823             156 :         if (reg->next_in_table)
     824              12 :             reg->next_in_table->prev_in_table = reg->prev_in_table;
     825                 : 
     826             156 :         --state->registration_count[signal_number];
     827             156 :         --registration_count_[signal_number];
     828                 : 
     829             156 :         delete reg;
     830             156 :     }
     831                 : 
     832             198 :     if (first_error)
     833               1 :         return first_error;
     834             197 :     return {};
     835             198 : }
     836                 : 
     837                 : inline void
     838             201 : posix_signal_service::cancel_wait(posix_signal& impl)
     839                 : {
     840             201 :     bool was_waiting = false;
     841             201 :     signal_op* op    = nullptr;
     842                 : 
     843                 :     {
     844             201 :         std::lock_guard lock(mutex_);
     845             201 :         impl.cancelled_ = true;
     846             201 :         if (impl.waiting_)
     847                 :         {
     848               7 :             was_waiting   = true;
     849               7 :             impl.waiting_ = false;
     850               7 :             op            = &impl.pending_op_;
     851                 :         }
     852             201 :     }
     853                 : 
     854             201 :     if (was_waiting)
     855                 :     {
     856               7 :         if (op->ec_out)
     857               7 :             *op->ec_out = make_error_code(capy::error::canceled);
     858               7 :         if (op->signal_out)
     859               7 :             *op->signal_out = 0;
     860               7 :         op->cont.h = op->h;
     861               7 :         op->d.post(op->cont);
     862               7 :         sched_->work_finished();
     863                 :     }
     864             201 : }
     865                 : 
     866                 : inline void
     867             651 : posix_signal_service::cancel_wait_token(posix_signal& impl) noexcept
     868                 : {
     869             651 :     bool was_waiting = false;
     870             651 :     signal_op* op    = nullptr;
     871                 : 
     872                 :     {
     873             651 :         std::lock_guard lock(mutex_);
     874                 :         // Persist the request even when no wait is parked yet: wait()
     875                 :         // arms the callback before start_wait takes this lock, and
     876                 :         // start_wait consumes this flag.
     877             651 :         impl.token_cancelled_ = true;
     878             651 :         if (impl.waiting_)
     879                 :         {
     880             514 :             was_waiting   = true;
     881             514 :             impl.waiting_ = false;
     882             514 :             op            = &impl.pending_op_;
     883                 :         }
     884             651 :     }
     885                 : 
     886             651 :     if (was_waiting)
     887                 :     {
     888             514 :         if (op->ec_out)
     889             514 :             *op->ec_out = make_error_code(capy::error::canceled);
     890             514 :         if (op->signal_out)
     891             514 :             *op->signal_out = 0;
     892             514 :         op->cont.h = op->h;
     893             514 :         op->d.post(op->cont);
     894             514 :         sched_->work_finished();
     895                 :     }
     896             651 : }
     897                 : 
     898                 : inline void
     899             651 : posix_signal::token_canceller::operator()() const noexcept
     900                 : {
     901             651 :     self->svc_.cancel_wait_token(*self);
     902             651 : }
     903                 : 
     904                 : inline void
     905             985 : posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
     906                 : {
     907                 :     {
     908             985 :         std::lock_guard lock(mutex_);
     909                 : 
     910                 :         // Check if cancel() was called before this wait started
     911             985 :         if (impl.cancelled_)
     912                 :         {
     913               2 :             impl.cancelled_ = false;
     914               2 :             if (op->ec_out)
     915               2 :                 *op->ec_out = make_error_code(capy::error::canceled);
     916               2 :             if (op->signal_out)
     917               2 :                 *op->signal_out = 0;
     918               2 :             op->cont.h = op->h;
     919               2 :             op->d.post(op->cont);
     920               2 :             return;
     921                 :         }
     922                 : 
     923                 :         // A stop request that arrived between wait() arming the callback
     924                 :         // and this lock: complete now rather than parking forever.
     925             983 :         if (impl.token_cancelled_)
     926                 :         {
     927             135 :             impl.token_cancelled_ = false;
     928             135 :             if (op->ec_out)
     929             135 :                 *op->ec_out = make_error_code(capy::error::canceled);
     930             135 :             if (op->signal_out)
     931             135 :                 *op->signal_out = 0;
     932             135 :             op->cont.h = op->h;
     933             135 :             op->d.post(op->cont);
     934             135 :             return;
     935                 :         }
     936                 : 
     937                 :         // Check for queued signals first (signal arrived before wait started)
     938             848 :         signal_registration* reg = impl.signals_;
     939            1698 :         while (reg)
     940                 :         {
     941             852 :             if (reg->undelivered > 0)
     942                 :             {
     943               2 :                 --reg->undelivered;
     944               2 :                 op->signal_number = reg->signal_number;
     945                 :                 // svc=nullptr: no work_finished needed since we never called work_started
     946               2 :                 op->svc = nullptr;
     947               2 :                 sched_->post(op);
     948               2 :                 return;
     949                 :             }
     950             850 :             reg = reg->next_in_set;
     951                 :         }
     952                 : 
     953                 :         // No queued signals - wait for delivery
     954             846 :         impl.waiting_ = true;
     955                 :         // svc=this: signal_op::operator() will call work_finished() to balance this
     956             846 :         op->svc = this;
     957             846 :         sched_->work_started();
     958             985 :     }
     959                 : }
     960                 : 
     961                 : inline void
     962             317 : posix_signal_service::deliver_signal(int signal_number)
     963                 : {
     964             317 :     if (signal_number < 0 || signal_number >= max_signal_number)
     965 MIS           0 :         return;
     966                 : 
     967                 :     posix_signal_detail::signal_state* state =
     968 HIT         317 :         posix_signal_detail::get_signal_state();
     969             317 :     std::lock_guard lock(state->mutex);
     970                 : 
     971             317 :     posix_signal_service* service = state->service_list;
     972             634 :     while (service)
     973                 :     {
     974             317 :         std::lock_guard svc_lock(service->mutex_);
     975                 : 
     976             317 :         signal_registration* reg = service->registrations_[signal_number];
     977             638 :         while (reg)
     978                 :         {
     979             321 :             posix_signal* impl = static_cast<posix_signal*>(reg->owner);
     980                 : 
     981             321 :             if (impl->waiting_)
     982                 :             {
     983             319 :                 impl->waiting_                  = false;
     984             319 :                 impl->pending_op_.signal_number = signal_number;
     985             319 :                 service->post(&impl->pending_op_);
     986                 :             }
     987                 :             else
     988                 :             {
     989               2 :                 ++reg->undelivered;
     990                 :             }
     991                 : 
     992             321 :             reg = reg->next_in_table;
     993                 :         }
     994                 : 
     995             317 :         service = service->next_;
     996             317 :     }
     997             317 : }
     998                 : 
     999                 : inline void
    1000                 : posix_signal_service::work_started() noexcept
    1001                 : {
    1002                 :     sched_->work_started();
    1003                 : }
    1004                 : 
    1005                 : inline void
    1006             319 : posix_signal_service::work_finished() noexcept
    1007                 : {
    1008             319 :     sched_->work_finished();
    1009             319 : }
    1010                 : 
    1011                 : inline void
    1012             319 : posix_signal_service::post(signal_op* op)
    1013                 : {
    1014             319 :     sched_->post(op);
    1015             319 : }
    1016                 : 
    1017                 : inline void
    1018            2241 : posix_signal_service::add_service(posix_signal_service* service)
    1019                 : {
    1020                 :     posix_signal_detail::signal_state* state =
    1021            2241 :         posix_signal_detail::get_signal_state();
    1022            2241 :     std::lock_guard lock(state->mutex);
    1023                 : 
    1024            2241 :     service->next_ = state->service_list;
    1025            2241 :     service->prev_ = nullptr;
    1026            2241 :     if (state->service_list)
    1027              11 :         state->service_list->prev_ = service;
    1028            2241 :     state->service_list = service;
    1029            2241 : }
    1030                 : 
    1031                 : inline void
    1032            2241 : posix_signal_service::remove_service(posix_signal_service* service)
    1033                 : {
    1034                 :     posix_signal_detail::signal_state* state =
    1035            2241 :         posix_signal_detail::get_signal_state();
    1036            2241 :     std::lock_guard lock(state->mutex);
    1037                 : 
    1038            2241 :     if (service->next_ || service->prev_ || state->service_list == service)
    1039                 :     {
    1040            2241 :         if (state->service_list == service)
    1041            2239 :             state->service_list = service->next_;
    1042            2241 :         if (service->prev_)
    1043               2 :             service->prev_->next_ = service->next_;
    1044            2241 :         if (service->next_)
    1045               9 :             service->next_->prev_ = service->prev_;
    1046            2241 :         service->next_ = nullptr;
    1047            2241 :         service->prev_ = nullptr;
    1048                 :     }
    1049            2241 : }
    1050                 : 
    1051                 : // get_signal_service - factory function
    1052                 : 
    1053                 : inline posix_signal_service&
    1054            2241 : get_signal_service(capy::execution_context& ctx, scheduler& sched)
    1055                 : {
    1056            2241 :     return ctx.make_service<posix_signal_service>(sched);
    1057                 : }
    1058                 : 
    1059                 : } // namespace detail
    1060                 : } // namespace boost::corosio
    1061                 : 
    1062                 : #endif // BOOST_COROSIO_POSIX
    1063                 : 
    1064                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
        

Generated by: LCOV version 2.3