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.6 % 336 301 35
Test Date: 2026-07-17 17:14:45 Functions: 97.0 % 33 32 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         112 :     void destroy(io_object::implementation* p) override
     167                 :     {
     168             112 :         auto& impl              = static_cast<posix_signal&>(*p);
     169             112 :         [[maybe_unused]] auto n = impl.clear();
     170             112 :         impl.cancel();
     171             112 :         destroy_impl(impl);
     172             112 :     }
     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::once_flag reader_once_;
     206                 : 
     207                 :     intrusive_list<posix_signal> impl_list_;
     208                 : 
     209                 :     // Per-signal registration table
     210                 :     signal_registration* registrations_[max_signal_number];
     211                 : 
     212                 :     // Registration counts for each signal
     213                 :     std::size_t registration_count_[max_signal_number];
     214                 : 
     215                 :     // Linked list of all posix_signal_service services for signal delivery
     216                 :     posix_signal_service* next_ = nullptr;
     217                 :     posix_signal_service* prev_ = nullptr;
     218                 : };
     219                 : 
     220                 : /** Get or create the signal service for the given context.
     221                 : 
     222                 :     This function is called by the concrete scheduler during initialization
     223                 :     to create the signal service with a reference to itself.
     224                 : 
     225                 :     @param ctx Reference to the owning execution_context.
     226                 :     @param sched Reference to the scheduler for posting completions.
     227                 :     @return Reference to the signal service.
     228                 : */
     229                 : posix_signal_service&
     230                 : get_signal_service(capy::execution_context& ctx, scheduler& sched);
     231                 : 
     232                 : } // namespace detail
     233                 : 
     234                 : } // namespace boost::corosio
     235                 : 
     236                 : // ---------------------------------------------------------------------------
     237                 : // Inline implementation
     238                 : // ---------------------------------------------------------------------------
     239                 : 
     240                 : namespace boost::corosio {
     241                 : 
     242                 : namespace detail {
     243                 : 
     244                 : namespace posix_signal_detail {
     245                 : 
     246                 : struct signal_state
     247                 : {
     248                 :     std::mutex mutex;
     249                 :     posix_signal_service* service_list                      = nullptr;
     250                 :     std::size_t registration_count[max_signal_number]       = {};
     251                 :     signal_set::flags_t registered_flags[max_signal_number] = {};
     252                 : 
     253                 :     // Self-pipe used to defer signal delivery out of handler context.
     254                 :     // The C handler writes the signal number to write_fd (async-signal-
     255                 :     // safe); a backend event loop drains read_fd and calls deliver_signal()
     256                 :     // in normal context. Created once (on the first signal registration) and
     257                 :     // kept for the process lifetime. Each posix_signal_service registers the
     258                 :     // read end with its own scheduler (see reader_once_) so every running
     259                 :     // io_context can drain it; multiple readers on one pipe are safe because
     260                 :     // each signal is a fixed sizeof(int) record read atomically.
     261                 :     int read_fd  = -1;
     262                 :     int write_fd = -1;
     263                 : };
     264                 : 
     265                 : BOOST_COROSIO_DECL signal_state* get_signal_state();
     266                 : 
     267                 : // Check if requested flags are supported on this platform.
     268                 : // Returns true if all flags are supported, false otherwise.
     269                 : inline bool
     270             114 : flags_supported([[maybe_unused]] signal_set::flags_t flags)
     271                 : {
     272                 : #ifndef SA_NOCLDWAIT
     273                 :     if (flags & signal_set::no_child_wait)
     274                 :         return false;
     275                 : #endif
     276             114 :     return true;
     277                 : }
     278                 : 
     279                 : // Map abstract flags to sigaction() flags.
     280                 : // Caller must ensure flags_supported() returns true first.
     281                 : inline int
     282              96 : to_sigaction_flags(signal_set::flags_t flags)
     283                 : {
     284              96 :     int sa_flags = 0;
     285              96 :     if (flags & signal_set::restart)
     286              18 :         sa_flags |= SA_RESTART;
     287              96 :     if (flags & signal_set::no_child_stop)
     288 MIS           0 :         sa_flags |= SA_NOCLDSTOP;
     289                 : #ifdef SA_NOCLDWAIT
     290 HIT          96 :     if (flags & signal_set::no_child_wait)
     291 MIS           0 :         sa_flags |= SA_NOCLDWAIT;
     292                 : #endif
     293 HIT          96 :     if (flags & signal_set::no_defer)
     294               2 :         sa_flags |= SA_NODEFER;
     295              96 :     if (flags & signal_set::reset_handler)
     296 MIS           0 :         sa_flags |= SA_RESETHAND;
     297 HIT          96 :     return sa_flags;
     298                 : }
     299                 : 
     300                 : // Check if two flag values are compatible
     301                 : inline bool
     302              18 : flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
     303                 : {
     304                 :     // dont_care is always compatible
     305              34 :     if ((existing & signal_set::dont_care) ||
     306              16 :         (requested & signal_set::dont_care))
     307               6 :         return true;
     308                 : 
     309                 :     // Mask out dont_care bit for comparison
     310              12 :     constexpr auto mask = ~signal_set::dont_care;
     311              12 :     return (existing & mask) == (requested & mask);
     312                 : }
     313                 : 
     314                 : // Lazily create the global signal self-pipe. Idempotent; call under
     315                 : // state->mutex before installing the first signal handler so write_fd is
     316                 : // valid by the time the handler can fire. Both ends are non-blocking and
     317                 : // close-on-exec (mirrors the reactor self-pipe setup in select_scheduler).
     318                 : // Returns false and leaves the fds at -1 if creation fails.
     319                 : inline bool
     320             114 : open_signal_pipe(signal_state* state)
     321                 : {
     322             114 :     if (state->read_fd >= 0)
     323             112 :         return true;
     324                 : 
     325                 :     int fds[2];
     326               2 :     if (::pipe(fds) < 0)
     327 MIS           0 :         return false;
     328                 : 
     329 HIT           6 :     for (int i = 0; i < 2; ++i)
     330                 :     {
     331               4 :         int fl = ::fcntl(fds[i], F_GETFL, 0);
     332               8 :         if (fl == -1 || ::fcntl(fds[i], F_SETFL, fl | O_NONBLOCK) == -1 ||
     333               4 :             ::fcntl(fds[i], F_SETFD, FD_CLOEXEC) == -1)
     334                 :         {
     335 MIS           0 :             ::close(fds[0]);
     336               0 :             ::close(fds[1]);
     337               0 :             return false;
     338                 :         }
     339                 :     }
     340                 : 
     341 HIT           2 :     state->read_fd  = fds[0];
     342               2 :     state->write_fd = fds[1];
     343               2 :     return true;
     344                 : }
     345                 : 
     346                 : // C signal handler. Async-signal-safe: it touches only the single global
     347                 : // write_fd (an int set before any handler is installed) and calls write(),
     348                 : // which POSIX lists as async-signal-safe. errno is saved and restored so an
     349                 : // interrupted foreground syscall is unaffected. A full pipe (write returns
     350                 : // EAGAIN) or a short write is intentionally dropped — the reactor still
     351                 : // coalesces because deliver_signal reports the signal to every waiting set.
     352                 : inline void
     353             300 : corosio_posix_signal_handler(int signal_number)
     354                 : {
     355             300 :     int saved_errno         = errno;
     356             300 :     signal_state* state     = get_signal_state();
     357                 :     [[maybe_unused]] ssize_t r =
     358             300 :         ::write(state->write_fd, &signal_number, sizeof(int));
     359             300 :     errno = saved_errno;
     360                 :     // With sigaction(), the handler persists automatically (unlike some
     361                 :     // signal() implementations that reset to SIG_DFL).
     362             300 : }
     363                 : 
     364                 : // Drain the signal self-pipe and deliver each pending signal. Runs in normal
     365                 : // thread context from the backend event loop, so deliver_signal()'s mutex
     366                 : // locking and scheduler post are safe here. Reads until EAGAIN (edge-
     367                 : // triggered backends require a full drain per readiness event).
     368                 : inline void
     369             300 : drain_signal_pipe()
     370                 : {
     371             300 :     signal_state* state = get_signal_state();
     372                 :     int signal_number;
     373             600 :     while (::read(state->read_fd, &signal_number, sizeof(int)) ==
     374                 :            static_cast<ssize_t>(sizeof(int)))
     375                 :     {
     376             300 :         posix_signal_service::deliver_signal(signal_number);
     377                 :     }
     378             300 : }
     379                 : 
     380                 : } // namespace posix_signal_detail
     381                 : 
     382                 : // signal_op implementation
     383                 : 
     384                 : inline void
     385             302 : signal_op::operator()()
     386                 : {
     387             302 :     if (ec_out)
     388             302 :         *ec_out = {};
     389             302 :     if (signal_out)
     390             302 :         *signal_out = signal_number;
     391                 : 
     392                 :     // Capture svc before resuming (coro may destroy us)
     393             302 :     auto* service = svc;
     394             302 :     svc           = nullptr;
     395                 : 
     396             302 :     cont.h = h;
     397             302 :     d.post(cont);
     398                 : 
     399                 :     // Balance the work_started() from start_wait
     400             302 :     if (service)
     401             302 :         service->work_finished();
     402             302 : }
     403                 : 
     404                 : inline void
     405 MIS           0 : signal_op::destroy()
     406                 : {
     407                 :     // No-op: signal_op is embedded in posix_signal
     408               0 : }
     409                 : 
     410                 : // posix_signal implementation
     411                 : 
     412 HIT         112 : inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
     413             112 :     : svc_(svc)
     414                 : {
     415             112 : }
     416                 : 
     417                 : inline std::coroutine_handle<>
     418             310 : posix_signal::wait(
     419                 :     std::coroutine_handle<> h,
     420                 :     capy::executor_ref d,
     421                 :     std::stop_token token,
     422                 :     std::error_code* ec,
     423                 :     int* signal_out)
     424                 : {
     425             310 :     pending_op_.h             = h;
     426             310 :     pending_op_.d             = d;
     427             310 :     pending_op_.ec_out        = ec;
     428             310 :     pending_op_.signal_out    = signal_out;
     429             310 :     pending_op_.signal_number = 0;
     430                 : 
     431             310 :     if (token.stop_requested())
     432                 :     {
     433               2 :         if (ec)
     434               2 :             *ec = make_error_code(capy::error::canceled);
     435               2 :         if (signal_out)
     436               2 :             *signal_out = 0;
     437               2 :         pending_op_.cont.h = h;
     438               2 :         d.post(pending_op_.cont);
     439                 :         // completion is always posted to scheduler queue, never inline.
     440               2 :         return std::noop_coroutine();
     441                 :     }
     442                 : 
     443             308 :     svc_.start_wait(*this, &pending_op_);
     444                 :     // completion is always posted to scheduler queue, never inline.
     445             308 :     return std::noop_coroutine();
     446                 : }
     447                 : 
     448                 : inline std::error_code
     449             118 : posix_signal::add(int signal_number, signal_set::flags_t flags)
     450                 : {
     451             118 :     return svc_.add_signal(*this, signal_number, flags);
     452                 : }
     453                 : 
     454                 : inline std::error_code
     455               6 : posix_signal::remove(int signal_number)
     456                 : {
     457               6 :     return svc_.remove_signal(*this, signal_number);
     458                 : }
     459                 : 
     460                 : inline std::error_code
     461             116 : posix_signal::clear()
     462                 : {
     463             116 :     return svc_.clear_signals(*this);
     464                 : }
     465                 : 
     466                 : inline void
     467             126 : posix_signal::cancel()
     468                 : {
     469             126 :     svc_.cancel_wait(*this);
     470             126 : }
     471                 : 
     472                 : // posix_signal_service implementation
     473                 : 
     474            1225 : inline posix_signal_service::posix_signal_service(
     475            1225 :     capy::execution_context&, scheduler& sched)
     476            1225 :     : sched_(&sched)
     477                 : {
     478           79625 :     for (int i = 0; i < max_signal_number; ++i)
     479                 :     {
     480           78400 :         registrations_[i]      = nullptr;
     481           78400 :         registration_count_[i] = 0;
     482                 :     }
     483            1225 :     add_service(this);
     484            1225 : }
     485                 : 
     486            2450 : inline posix_signal_service::~posix_signal_service()
     487                 : {
     488            1225 :     remove_service(this);
     489            2450 : }
     490                 : 
     491                 : inline void
     492            1225 : posix_signal_service::shutdown()
     493                 : {
     494            1225 :     std::lock_guard lock(mutex_);
     495                 : 
     496            1225 :     for (auto* impl = impl_list_.pop_front(); impl != nullptr;
     497 MIS           0 :          impl       = impl_list_.pop_front())
     498                 :     {
     499               0 :         while (auto* reg = impl->signals_)
     500                 :         {
     501               0 :             impl->signals_ = reg->next_in_set;
     502               0 :             delete reg;
     503               0 :         }
     504               0 :         delete impl;
     505                 :     }
     506 HIT        1225 : }
     507                 : 
     508                 : inline io_object::implementation*
     509             112 : posix_signal_service::construct()
     510                 : {
     511             112 :     auto* impl = new posix_signal(*this);
     512                 : 
     513                 :     {
     514             112 :         std::lock_guard lock(mutex_);
     515             112 :         impl_list_.push_back(impl);
     516             112 :     }
     517                 : 
     518             112 :     return impl;
     519                 : }
     520                 : 
     521                 : inline void
     522             112 : posix_signal_service::destroy_impl(posix_signal& impl)
     523                 : {
     524                 :     {
     525             112 :         std::lock_guard lock(mutex_);
     526             112 :         impl_list_.remove(&impl);
     527             112 :     }
     528                 : 
     529             112 :     delete &impl;
     530             112 : }
     531                 : 
     532                 : inline std::error_code
     533             118 : posix_signal_service::add_signal(
     534                 :     posix_signal& impl, int signal_number, signal_set::flags_t flags)
     535                 : {
     536             118 :     if (signal_number < 0 || signal_number >= max_signal_number)
     537               4 :         return make_error_code(std::errc::invalid_argument);
     538                 : 
     539                 :     // Validate that requested flags are supported on this platform
     540                 :     // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
     541             114 :     if (!posix_signal_detail::flags_supported(flags))
     542 MIS           0 :         return make_error_code(std::errc::operation_not_supported);
     543                 : 
     544                 :     posix_signal_detail::signal_state* state =
     545 HIT         114 :         posix_signal_detail::get_signal_state();
     546                 : 
     547                 :     // Ensure the global self-pipe exists and this service's scheduler is
     548                 :     // watching its read end, BEFORE taking the registration locks. The
     549                 :     // reactor drain path locks the descriptor mutex and then the signal-state
     550                 :     // and service mutexes; register_signal_reader locks the descriptor mutex
     551                 :     // (via register_descriptor), so it must run holding neither of those or
     552                 :     // the lock order would invert (a real deadlock, caught by TSan). call_once
     553                 :     // makes the once-per-service registration safe when two signal_sets on
     554                 :     // this context race add() from different threads.
     555                 :     {
     556             114 :         std::lock_guard state_lock(state->mutex);
     557             114 :         if (!posix_signal_detail::open_signal_pipe(state))
     558 MIS           0 :             return make_error_code(std::errc::io_error);
     559 HIT         114 :     }
     560             114 :     std::call_once(reader_once_, [this, state] {
     561              82 :         sched_->register_signal_reader(state->read_fd);
     562              82 :     });
     563                 : 
     564             114 :     std::lock_guard state_lock(state->mutex);
     565             114 :     std::lock_guard lock(mutex_);
     566                 : 
     567                 :     // Find insertion point (list is sorted by signal number)
     568             114 :     signal_registration** insertion_point = &impl.signals_;
     569             114 :     signal_registration* reg              = impl.signals_;
     570             128 :     while (reg && reg->signal_number < signal_number)
     571                 :     {
     572              14 :         insertion_point = &reg->next_in_set;
     573              14 :         reg             = reg->next_in_set;
     574                 :     }
     575                 : 
     576                 :     // Already registered in this set - check flag compatibility
     577                 :     // (same signal_set adding same signal twice with different flags)
     578             114 :     if (reg && reg->signal_number == signal_number)
     579                 :     {
     580              10 :         if (!posix_signal_detail::flags_compatible(reg->flags, flags))
     581               2 :             return make_error_code(std::errc::invalid_argument);
     582               8 :         return {};
     583                 :     }
     584                 : 
     585                 :     // Check flag compatibility with global registration
     586                 :     // (different signal_set already registered this signal with different flags)
     587             104 :     if (state->registration_count[signal_number] > 0)
     588                 :     {
     589               8 :         if (!posix_signal_detail::flags_compatible(
     590                 :                 state->registered_flags[signal_number], flags))
     591               2 :             return make_error_code(std::errc::invalid_argument);
     592                 :     }
     593                 : 
     594             102 :     auto* new_reg          = new signal_registration;
     595             102 :     new_reg->signal_number = signal_number;
     596             102 :     new_reg->flags         = flags;
     597             102 :     new_reg->owner         = &impl;
     598             102 :     new_reg->undelivered   = 0;
     599                 : 
     600                 :     // Install signal handler on first global registration
     601             102 :     if (state->registration_count[signal_number] == 0)
     602                 :     {
     603              96 :         struct sigaction sa = {};
     604              96 :         sa.sa_handler       = posix_signal_detail::corosio_posix_signal_handler;
     605              96 :         sigemptyset(&sa.sa_mask);
     606              96 :         sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
     607                 : 
     608              96 :         if (::sigaction(signal_number, &sa, nullptr) < 0)
     609                 :         {
     610 MIS           0 :             delete new_reg;
     611               0 :             return make_error_code(std::errc::invalid_argument);
     612                 :         }
     613                 : 
     614                 :         // Store the flags used for first registration
     615 HIT          96 :         state->registered_flags[signal_number] = flags;
     616                 :     }
     617                 : 
     618             102 :     new_reg->next_in_set = reg;
     619             102 :     *insertion_point     = new_reg;
     620                 : 
     621             102 :     new_reg->next_in_table = registrations_[signal_number];
     622             102 :     new_reg->prev_in_table = nullptr;
     623             102 :     if (registrations_[signal_number])
     624               6 :         registrations_[signal_number]->prev_in_table = new_reg;
     625             102 :     registrations_[signal_number] = new_reg;
     626                 : 
     627             102 :     ++state->registration_count[signal_number];
     628             102 :     ++registration_count_[signal_number];
     629                 : 
     630             102 :     return {};
     631             114 : }
     632                 : 
     633                 : inline std::error_code
     634               6 : posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
     635                 : {
     636               6 :     if (signal_number < 0 || signal_number >= max_signal_number)
     637               2 :         return make_error_code(std::errc::invalid_argument);
     638                 : 
     639                 :     posix_signal_detail::signal_state* state =
     640               4 :         posix_signal_detail::get_signal_state();
     641               4 :     std::lock_guard state_lock(state->mutex);
     642               4 :     std::lock_guard lock(mutex_);
     643                 : 
     644               4 :     signal_registration** deletion_point = &impl.signals_;
     645               4 :     signal_registration* reg             = impl.signals_;
     646               4 :     while (reg && reg->signal_number < signal_number)
     647                 :     {
     648 MIS           0 :         deletion_point = &reg->next_in_set;
     649               0 :         reg            = reg->next_in_set;
     650                 :     }
     651                 : 
     652 HIT           4 :     if (!reg || reg->signal_number != signal_number)
     653               2 :         return {};
     654                 : 
     655                 :     // Restore default handler on last global unregistration
     656               2 :     if (state->registration_count[signal_number] == 1)
     657                 :     {
     658               2 :         struct sigaction sa = {};
     659               2 :         sa.sa_handler       = SIG_DFL;
     660               2 :         sigemptyset(&sa.sa_mask);
     661               2 :         sa.sa_flags = 0;
     662                 : 
     663               2 :         if (::sigaction(signal_number, &sa, nullptr) < 0)
     664 MIS           0 :             return make_error_code(std::errc::invalid_argument);
     665                 : 
     666                 :         // Clear stored flags
     667 HIT           2 :         state->registered_flags[signal_number] = signal_set::none;
     668                 :     }
     669                 : 
     670               2 :     *deletion_point = reg->next_in_set;
     671                 : 
     672               2 :     if (registrations_[signal_number] == reg)
     673               2 :         registrations_[signal_number] = reg->next_in_table;
     674               2 :     if (reg->prev_in_table)
     675 MIS           0 :         reg->prev_in_table->next_in_table = reg->next_in_table;
     676 HIT           2 :     if (reg->next_in_table)
     677 MIS           0 :         reg->next_in_table->prev_in_table = reg->prev_in_table;
     678                 : 
     679 HIT           2 :     --state->registration_count[signal_number];
     680               2 :     --registration_count_[signal_number];
     681                 : 
     682               2 :     delete reg;
     683               2 :     return {};
     684               4 : }
     685                 : 
     686                 : inline std::error_code
     687             116 : posix_signal_service::clear_signals(posix_signal& impl)
     688                 : {
     689                 :     posix_signal_detail::signal_state* state =
     690             116 :         posix_signal_detail::get_signal_state();
     691             116 :     std::lock_guard state_lock(state->mutex);
     692             116 :     std::lock_guard lock(mutex_);
     693                 : 
     694             116 :     std::error_code first_error;
     695                 : 
     696             216 :     while (signal_registration* reg = impl.signals_)
     697                 :     {
     698             100 :         int signal_number = reg->signal_number;
     699                 : 
     700             100 :         if (state->registration_count[signal_number] == 1)
     701                 :         {
     702              94 :             struct sigaction sa = {};
     703              94 :             sa.sa_handler       = SIG_DFL;
     704              94 :             sigemptyset(&sa.sa_mask);
     705              94 :             sa.sa_flags = 0;
     706                 : 
     707              94 :             if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
     708 MIS           0 :                 first_error = make_error_code(std::errc::invalid_argument);
     709                 : 
     710                 :             // Clear stored flags
     711 HIT          94 :             state->registered_flags[signal_number] = signal_set::none;
     712                 :         }
     713                 : 
     714             100 :         impl.signals_ = reg->next_in_set;
     715                 : 
     716             100 :         if (registrations_[signal_number] == reg)
     717             100 :             registrations_[signal_number] = reg->next_in_table;
     718             100 :         if (reg->prev_in_table)
     719 MIS           0 :             reg->prev_in_table->next_in_table = reg->next_in_table;
     720 HIT         100 :         if (reg->next_in_table)
     721               6 :             reg->next_in_table->prev_in_table = reg->prev_in_table;
     722                 : 
     723             100 :         --state->registration_count[signal_number];
     724             100 :         --registration_count_[signal_number];
     725                 : 
     726             100 :         delete reg;
     727             100 :     }
     728                 : 
     729             116 :     if (first_error)
     730 MIS           0 :         return first_error;
     731 HIT         116 :     return {};
     732             116 : }
     733                 : 
     734                 : inline void
     735             126 : posix_signal_service::cancel_wait(posix_signal& impl)
     736                 : {
     737             126 :     bool was_waiting = false;
     738             126 :     signal_op* op    = nullptr;
     739                 : 
     740                 :     {
     741             126 :         std::lock_guard lock(mutex_);
     742             126 :         impl.cancelled_ = true;
     743             126 :         if (impl.waiting_)
     744                 :         {
     745               4 :             was_waiting   = true;
     746               4 :             impl.waiting_ = false;
     747               4 :             op            = &impl.pending_op_;
     748                 :         }
     749             126 :     }
     750                 : 
     751             126 :     if (was_waiting)
     752                 :     {
     753               4 :         if (op->ec_out)
     754               4 :             *op->ec_out = make_error_code(capy::error::canceled);
     755               4 :         if (op->signal_out)
     756               4 :             *op->signal_out = 0;
     757               4 :         op->cont.h = op->h;
     758               4 :         op->d.post(op->cont);
     759               4 :         sched_->work_finished();
     760                 :     }
     761             126 : }
     762                 : 
     763                 : inline void
     764             308 : posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
     765                 : {
     766                 :     {
     767             308 :         std::lock_guard lock(mutex_);
     768                 : 
     769                 :         // Check if cancel() was called before this wait started
     770             308 :         if (impl.cancelled_)
     771                 :         {
     772               2 :             impl.cancelled_ = false;
     773               2 :             if (op->ec_out)
     774               2 :                 *op->ec_out = make_error_code(capy::error::canceled);
     775               2 :             if (op->signal_out)
     776               2 :                 *op->signal_out = 0;
     777               2 :             op->cont.h = op->h;
     778               2 :             op->d.post(op->cont);
     779               2 :             return;
     780                 :         }
     781                 : 
     782                 :         // Check for queued signals first (signal arrived before wait started)
     783             306 :         signal_registration* reg = impl.signals_;
     784             614 :         while (reg)
     785                 :         {
     786             308 :             if (reg->undelivered > 0)
     787                 :             {
     788 MIS           0 :                 --reg->undelivered;
     789               0 :                 op->signal_number = reg->signal_number;
     790                 :                 // svc=nullptr: no work_finished needed since we never called work_started
     791               0 :                 op->svc = nullptr;
     792               0 :                 sched_->post(op);
     793               0 :                 return;
     794                 :             }
     795 HIT         308 :             reg = reg->next_in_set;
     796                 :         }
     797                 : 
     798                 :         // No queued signals - wait for delivery
     799             306 :         impl.waiting_ = true;
     800                 :         // svc=this: signal_op::operator() will call work_finished() to balance this
     801             306 :         op->svc = this;
     802             306 :         sched_->work_started();
     803             308 :     }
     804                 : }
     805                 : 
     806                 : inline void
     807             300 : posix_signal_service::deliver_signal(int signal_number)
     808                 : {
     809             300 :     if (signal_number < 0 || signal_number >= max_signal_number)
     810 MIS           0 :         return;
     811                 : 
     812                 :     posix_signal_detail::signal_state* state =
     813 HIT         300 :         posix_signal_detail::get_signal_state();
     814             300 :     std::lock_guard lock(state->mutex);
     815                 : 
     816             300 :     posix_signal_service* service = state->service_list;
     817             600 :     while (service)
     818                 :     {
     819             300 :         std::lock_guard svc_lock(service->mutex_);
     820                 : 
     821             300 :         signal_registration* reg = service->registrations_[signal_number];
     822             602 :         while (reg)
     823                 :         {
     824             302 :             posix_signal* impl = static_cast<posix_signal*>(reg->owner);
     825                 : 
     826             302 :             if (impl->waiting_)
     827                 :             {
     828             302 :                 impl->waiting_                  = false;
     829             302 :                 impl->pending_op_.signal_number = signal_number;
     830             302 :                 service->post(&impl->pending_op_);
     831                 :             }
     832                 :             else
     833                 :             {
     834 MIS           0 :                 ++reg->undelivered;
     835                 :             }
     836                 : 
     837 HIT         302 :             reg = reg->next_in_table;
     838                 :         }
     839                 : 
     840             300 :         service = service->next_;
     841             300 :     }
     842             300 : }
     843                 : 
     844                 : inline void
     845                 : posix_signal_service::work_started() noexcept
     846                 : {
     847                 :     sched_->work_started();
     848                 : }
     849                 : 
     850                 : inline void
     851             302 : posix_signal_service::work_finished() noexcept
     852                 : {
     853             302 :     sched_->work_finished();
     854             302 : }
     855                 : 
     856                 : inline void
     857             302 : posix_signal_service::post(signal_op* op)
     858                 : {
     859             302 :     sched_->post(op);
     860             302 : }
     861                 : 
     862                 : inline void
     863            1225 : posix_signal_service::add_service(posix_signal_service* service)
     864                 : {
     865                 :     posix_signal_detail::signal_state* state =
     866            1225 :         posix_signal_detail::get_signal_state();
     867            1225 :     std::lock_guard lock(state->mutex);
     868                 : 
     869            1225 :     service->next_ = state->service_list;
     870            1225 :     service->prev_ = nullptr;
     871            1225 :     if (state->service_list)
     872               5 :         state->service_list->prev_ = service;
     873            1225 :     state->service_list = service;
     874            1225 : }
     875                 : 
     876                 : inline void
     877            1225 : posix_signal_service::remove_service(posix_signal_service* service)
     878                 : {
     879                 :     posix_signal_detail::signal_state* state =
     880            1225 :         posix_signal_detail::get_signal_state();
     881            1225 :     std::lock_guard lock(state->mutex);
     882                 : 
     883            1225 :     if (service->next_ || service->prev_ || state->service_list == service)
     884                 :     {
     885            1225 :         if (state->service_list == service)
     886            1225 :             state->service_list = service->next_;
     887            1225 :         if (service->prev_)
     888 MIS           0 :             service->prev_->next_ = service->next_;
     889 HIT        1225 :         if (service->next_)
     890               5 :             service->next_->prev_ = service->prev_;
     891            1225 :         service->next_ = nullptr;
     892            1225 :         service->prev_ = nullptr;
     893                 :     }
     894            1225 : }
     895                 : 
     896                 : // get_signal_service - factory function
     897                 : 
     898                 : inline posix_signal_service&
     899            1225 : get_signal_service(capy::execution_context& ctx, scheduler& sched)
     900                 : {
     901            1225 :     return ctx.make_service<posix_signal_service>(sched);
     902                 : }
     903                 : 
     904                 : } // namespace detail
     905                 : } // namespace boost::corosio
     906                 : 
     907                 : #endif // BOOST_COROSIO_POSIX
     908                 : 
     909                 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
        

Generated by: LCOV version 2.3