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

Generated by: LCOV version 2.3