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

Generated by: LCOV version 2.3