1 +
//
 
2 +
// Copyright (c) 2026 Steve Gerbino
 
3 +
//
 
4 +
// Distributed under the Boost Software License, Version 1.0. (See accompanying
 
5 +
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 
6 +
//
 
7 +
// Official repository: https://github.com/cppalliance/corosio
 
8 +
//
 
9 +

 
10 +
#ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
 
11 +
#define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
 
12 +

 
13 +
#include <boost/corosio/detail/platform.hpp>
 
14 +

 
15 +
#if BOOST_COROSIO_POSIX
 
16 +

 
17 +
#include <boost/corosio/native/detail/posix/posix_signal.hpp>
 
18 +

 
19 +
#include <boost/corosio/detail/config.hpp>
 
20 +
#include <boost/capy/ex/execution_context.hpp>
 
21 +
#include <boost/corosio/detail/scheduler.hpp>
 
22 +
#include <boost/capy/error.hpp>
 
23 +

 
24 +
#include <mutex>
 
25 +

 
26 +
#include <signal.h>
 
27 +

 
28 +
/*
 
29 +
    POSIX Signal Service
 
30 +
    ====================
 
31 +

 
32 +
    Concrete signal service implementation for POSIX backends. Manages signal
 
33 +
    registrations via sigaction() and dispatches completions through the
 
34 +
    scheduler. One instance per execution_context, created by
 
35 +
    get_signal_service().
 
36 +

 
37 +
    See the block comment further down for the full architecture overview.
 
38 +
*/
 
39 +

 
40 +
/*
 
41 +
    POSIX Signal Implementation
 
42 +
    ===========================
 
43 +

 
44 +
    This file implements signal handling for POSIX systems using sigaction().
 
45 +
    The implementation supports signal flags (SA_RESTART, etc.) and integrates
 
46 +
    with any POSIX-compatible scheduler via the abstract scheduler interface.
 
47 +

 
48 +
    Architecture Overview
 
49 +
    ---------------------
 
50 +

 
51 +
    Three layers manage signal registrations:
 
52 +

 
53 +
    1. signal_state (global singleton)
 
54 +
       - Tracks the global service list and per-signal registration counts
 
55 +
       - Stores the flags used for first registration of each signal (for
 
56 +
         conflict detection when multiple signal_sets register same signal)
 
57 +
       - Owns the mutex that protects signal handler installation/removal
 
58 +

 
59 +
    2. posix_signal_service (one per execution_context)
 
60 +
       - Maintains registrations_[] table indexed by signal number
 
61 +
       - Each slot is a doubly-linked list of signal_registrations for that signal
 
62 +
       - Also maintains impl_list_ of all posix_signal objects it owns
 
63 +

 
64 +
    3. posix_signal (one per signal_set)
 
65 +
       - Owns a singly-linked list (sorted by signal number) of signal_registrations
 
66 +
       - Contains the pending_op_ used for wait operations
 
67 +

 
68 +
    Signal Delivery Flow
 
69 +
    --------------------
 
70 +

 
71 +
    1. Signal arrives -> corosio_posix_signal_handler() (must be async-signal-safe)
 
72 +
       -> deliver_signal()
 
73 +

 
74 +
    2. deliver_signal() iterates all posix_signal_service services:
 
75 +
       - If a signal_set is waiting (impl->waiting_ == true), post the signal_op
 
76 +
         to the scheduler for immediate completion
 
77 +
       - Otherwise, increment reg->undelivered to queue the signal
 
78 +

 
79 +
    3. When wait() is called via start_wait():
 
80 +
       - First check for queued signals (undelivered > 0); if found, post
 
81 +
         immediate completion without blocking
 
82 +
       - Otherwise, set waiting_ = true and call work_started() to keep
 
83 +
         the io_context alive
 
84 +

 
85 +
    Locking Protocol
 
86 +
    ----------------
 
87 +

 
88 +
    Two mutex levels exist (MUST acquire in this order to avoid deadlock):
 
89 +
      1. signal_state::mutex - protects handler registration and service list
 
90 +
      2. posix_signal_service::mutex_ - protects per-service registration tables
 
91 +

 
92 +
    Async-Signal-Safety Limitation
 
93 +
    ------------------------------
 
94 +

 
95 +
    IMPORTANT: deliver_signal() is called from signal handler context and
 
96 +
    acquires mutexes. This is NOT strictly async-signal-safe per POSIX.
 
97 +
    The limitation:
 
98 +
      - If a signal arrives while another thread holds state->mutex or
 
99 +
        service->mutex_, and that same thread receives the signal, a
 
100 +
        deadlock can occur (self-deadlock on non-recursive mutex).
 
101 +

 
102 +
    This design trades strict async-signal-safety for implementation simplicity.
 
103 +
    In practice, deadlocks are rare because:
 
104 +
      - Mutexes are held only briefly during registration changes
 
105 +
      - Most programs don't modify signal sets while signals are expected
 
106 +
      - The window for signal arrival during mutex hold is small
 
107 +

 
108 +
    A fully async-signal-safe implementation would require lock-free data
 
109 +
    structures and atomic operations throughout, significantly increasing
 
110 +
    complexity.
 
111 +

 
112 +
    Flag Handling
 
113 +
    -------------
 
114 +

 
115 +
    - Flags are abstract values in the public API (signal_set::flags_t)
 
116 +
    - flags_supported() validates that requested flags are available on
 
117 +
      this platform; returns false if SA_NOCLDWAIT is unavailable and
 
118 +
      no_child_wait is requested
 
119 +
    - to_sigaction_flags() maps validated flags to actual SA_* constants
 
120 +
    - First registration of a signal establishes the flags; subsequent
 
121 +
      registrations must be compatible (same flags or dont_care)
 
122 +
    - Requesting unavailable flags returns operation_not_supported
 
123 +

 
124 +
    Work Tracking
 
125 +
    -------------
 
126 +

 
127 +
    When waiting for a signal:
 
128 +
      - start_wait() calls sched_->work_started() to prevent io_context::run()
 
129 +
        from returning while we wait
 
130 +
      - signal_op::svc is set to point to the service
 
131 +
      - signal_op::operator()() calls work_finished() after resuming the coroutine
 
132 +

 
133 +
    If a signal was already queued (undelivered > 0), no work tracking is needed
 
134 +
    because completion is posted immediately.
 
135 +
*/
 
136 +

 
137 +
namespace boost::corosio {
 
138 +

 
139 +
namespace detail {
 
140 +

 
141 +
/** Signal service for POSIX backends.
 
142 +

 
143 +
    Manages signal registrations via sigaction() and dispatches signal
 
144 +
    completions through the scheduler. One instance per execution_context.
 
145 +
*/
 
146 +
class BOOST_COROSIO_DECL posix_signal_service final
 
147 +
    : public capy::execution_context::service
 
148 +
    , public io_object::io_service
 
149 +
{
 
150 +
public:
 
151 +
    using key_type = posix_signal_service;
 
152 +

 
153 +
    posix_signal_service(capy::execution_context& ctx, scheduler& sched);
 
154 +
    ~posix_signal_service() override;
 
155 +

 
156 +
    posix_signal_service(posix_signal_service const&)            = delete;
 
157 +
    posix_signal_service& operator=(posix_signal_service const&) = delete;
 
158 +

 
159 +
    io_object::implementation* construct() override;
 
160 +

 
161 +
    void destroy(io_object::implementation* p) override
 
162 +
    {
 
163 +
        auto& impl              = static_cast<posix_signal&>(*p);
 
164 +
        [[maybe_unused]] auto n = impl.clear();
 
165 +
        impl.cancel();
 
166 +
        destroy_impl(impl);
 
167 +
    }
 
168 +

 
169 +
    void shutdown() override;
 
170 +

 
171 +
    void destroy_impl(posix_signal& impl);
 
172 +

 
173 +
    std::error_code add_signal(
 
174 +
        posix_signal& impl, int signal_number, signal_set::flags_t flags);
 
175 +

 
176 +
    std::error_code remove_signal(posix_signal& impl, int signal_number);
 
177 +

 
178 +
    std::error_code clear_signals(posix_signal& impl);
 
179 +

 
180 +
    void cancel_wait(posix_signal& impl);
 
181 +
    void start_wait(posix_signal& impl, signal_op* op);
 
182 +

 
183 +
    static void deliver_signal(int signal_number);
 
184 +

 
185 +
    void work_started() noexcept;
 
186 +
    void work_finished() noexcept;
 
187 +
    void post(signal_op* op);
 
188 +

 
189 +
private:
 
190 +
    static void add_service(posix_signal_service* service);
 
191 +
    static void remove_service(posix_signal_service* service);
 
192 +

 
193 +
    scheduler* sched_;
 
194 +
    std::mutex mutex_;
 
195 +
    intrusive_list<posix_signal> impl_list_;
 
196 +

 
197 +
    // Per-signal registration table
 
198 +
    signal_registration* registrations_[max_signal_number];
 
199 +

 
200 +
    // Registration counts for each signal
 
201 +
    std::size_t registration_count_[max_signal_number];
 
202 +

 
203 +
    // Linked list of all posix_signal_service services for signal delivery
 
204 +
    posix_signal_service* next_ = nullptr;
 
205 +
    posix_signal_service* prev_ = nullptr;
 
206 +
};
 
207 +

 
208 +
/** Get or create the signal service for the given context.
 
209 +

 
210 +
    This function is called by the concrete scheduler during initialization
 
211 +
    to create the signal service with a reference to itself.
 
212 +

 
213 +
    @param ctx Reference to the owning execution_context.
 
214 +
    @param sched Reference to the scheduler for posting completions.
 
215 +
    @return Reference to the signal service.
 
216 +
*/
 
217 +
posix_signal_service&
 
218 +
get_signal_service(capy::execution_context& ctx, scheduler& sched);
 
219 +

 
220 +
} // namespace detail
 
221 +

 
222 +
} // namespace boost::corosio
 
223 +

 
224 +
// ---------------------------------------------------------------------------
 
225 +
// Inline implementation
 
226 +
// ---------------------------------------------------------------------------
 
227 +

 
228 +
namespace boost::corosio {
 
229 +

 
230 +
namespace detail {
 
231 +

 
232 +
namespace posix_signal_detail {
 
233 +

 
234 +
struct signal_state
 
235 +
{
 
236 +
    std::mutex mutex;
 
237 +
    posix_signal_service* service_list                      = nullptr;
 
238 +
    std::size_t registration_count[max_signal_number]       = {};
 
239 +
    signal_set::flags_t registered_flags[max_signal_number] = {};
 
240 +
};
 
241 +

 
242 +
BOOST_COROSIO_DECL signal_state* get_signal_state();
 
243 +

 
244 +
// Check if requested flags are supported on this platform.
 
245 +
// Returns true if all flags are supported, false otherwise.
 
246 +
inline bool
 
247 +
flags_supported([[maybe_unused]] signal_set::flags_t flags)
 
248 +
{
 
249 +
#ifndef SA_NOCLDWAIT
 
250 +
    if (flags & signal_set::no_child_wait)
 
251 +
        return false;
 
252 +
#endif
 
253 +
    return true;
 
254 +
}
 
255 +

 
256 +
// Map abstract flags to sigaction() flags.
 
257 +
// Caller must ensure flags_supported() returns true first.
 
258 +
inline int
 
259 +
to_sigaction_flags(signal_set::flags_t flags)
 
260 +
{
 
261 +
    int sa_flags = 0;
 
262 +
    if (flags & signal_set::restart)
 
263 +
        sa_flags |= SA_RESTART;
 
264 +
    if (flags & signal_set::no_child_stop)
 
265 +
        sa_flags |= SA_NOCLDSTOP;
 
266 +
#ifdef SA_NOCLDWAIT
 
267 +
    if (flags & signal_set::no_child_wait)
 
268 +
        sa_flags |= SA_NOCLDWAIT;
 
269 +
#endif
 
270 +
    if (flags & signal_set::no_defer)
 
271 +
        sa_flags |= SA_NODEFER;
 
272 +
    if (flags & signal_set::reset_handler)
 
273 +
        sa_flags |= SA_RESETHAND;
 
274 +
    return sa_flags;
 
275 +
}
 
276 +

 
277 +
// Check if two flag values are compatible
 
278 +
inline bool
 
279 +
flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
 
280 +
{
 
281 +
    // dont_care is always compatible
 
282 +
    if ((existing & signal_set::dont_care) ||
 
283 +
        (requested & signal_set::dont_care))
 
284 +
        return true;
 
285 +

 
286 +
    // Mask out dont_care bit for comparison
 
287 +
    constexpr auto mask = ~signal_set::dont_care;
 
288 +
    return (existing & mask) == (requested & mask);
 
289 +
}
 
290 +

 
291 +
// C signal handler - must be async-signal-safe
 
292 +
inline void
 
293 +
corosio_posix_signal_handler(int signal_number)
 
294 +
{
 
295 +
    posix_signal_service::deliver_signal(signal_number);
 
296 +
    // Note: With sigaction(), the handler persists automatically
 
297 +
    // (unlike some signal() implementations that reset to SIG_DFL)
 
298 +
}
 
299 +

 
300 +
} // namespace posix_signal_detail
 
301 +

 
302 +
// signal_op implementation
 
303 +

 
304 +
inline void
 
305 +
signal_op::operator()()
 
306 +
{
 
307 +
    if (ec_out)
 
308 +
        *ec_out = {};
 
309 +
    if (signal_out)
 
310 +
        *signal_out = signal_number;
 
311 +

 
312 +
    // Capture svc before resuming (coro may destroy us)
 
313 +
    auto* service = svc;
 
314 +
    svc           = nullptr;
 
315 +

 
316 +
    d.post(h);
 
317 +

 
318 +
    // Balance the work_started() from start_wait
 
319 +
    if (service)
 
320 +
        service->work_finished();
 
321 +
}
 
322 +

 
323 +
inline void
 
324 +
signal_op::destroy()
 
325 +
{
 
326 +
    // No-op: signal_op is embedded in posix_signal
 
327 +
}
 
328 +

 
329 +
// posix_signal implementation
 
330 +

 
331 +
inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
 
332 +
    : svc_(svc)
 
333 +
{
 
334 +
}
 
335 +

 
336 +
inline std::coroutine_handle<>
 
337 +
posix_signal::wait(
 
338 +
    std::coroutine_handle<> h,
 
339 +
    capy::executor_ref d,
 
340 +
    std::stop_token token,
 
341 +
    std::error_code* ec,
 
342 +
    int* signal_out)
 
343 +
{
 
344 +
    pending_op_.h             = h;
 
345 +
    pending_op_.d             = d;
 
346 +
    pending_op_.ec_out        = ec;
 
347 +
    pending_op_.signal_out    = signal_out;
 
348 +
    pending_op_.signal_number = 0;
 
349 +

 
350 +
    if (token.stop_requested())
 
351 +
    {
 
352 +
        if (ec)
 
353 +
            *ec = make_error_code(capy::error::canceled);
 
354 +
        if (signal_out)
 
355 +
            *signal_out = 0;
 
356 +
        d.post(h);
 
357 +
        // completion is always posted to scheduler queue, never inline.
 
358 +
        return std::noop_coroutine();
 
359 +
    }
 
360 +

 
361 +
    svc_.start_wait(*this, &pending_op_);
 
362 +
    // completion is always posted to scheduler queue, never inline.
 
363 +
    return std::noop_coroutine();
 
364 +
}
 
365 +

 
366 +
inline std::error_code
 
367 +
posix_signal::add(int signal_number, signal_set::flags_t flags)
 
368 +
{
 
369 +
    return svc_.add_signal(*this, signal_number, flags);
 
370 +
}
 
371 +

 
372 +
inline std::error_code
 
373 +
posix_signal::remove(int signal_number)
 
374 +
{
 
375 +
    return svc_.remove_signal(*this, signal_number);
 
376 +
}
 
377 +

 
378 +
inline std::error_code
 
379 +
posix_signal::clear()
 
380 +
{
 
381 +
    return svc_.clear_signals(*this);
 
382 +
}
 
383 +

 
384 +
inline void
 
385 +
posix_signal::cancel()
 
386 +
{
 
387 +
    svc_.cancel_wait(*this);
 
388 +
}
 
389 +

 
390 +
// posix_signal_service implementation
 
391 +

 
392 +
inline posix_signal_service::posix_signal_service(
 
393 +
    capy::execution_context&, scheduler& sched)
 
394 +
    : sched_(&sched)
 
395 +
{
 
396 +
    for (int i = 0; i < max_signal_number; ++i)
 
397 +
    {
 
398 +
        registrations_[i]      = nullptr;
 
399 +
        registration_count_[i] = 0;
 
400 +
    }
 
401 +
    add_service(this);
 
402 +
}
 
403 +

 
404 +
inline posix_signal_service::~posix_signal_service()
 
405 +
{
 
406 +
    remove_service(this);
 
407 +
}
 
408 +

 
409 +
inline void
 
410 +
posix_signal_service::shutdown()
 
411 +
{
 
412 +
    std::lock_guard lock(mutex_);
 
413 +

 
414 +
    for (auto* impl = impl_list_.pop_front(); impl != nullptr;
 
415 +
         impl       = impl_list_.pop_front())
 
416 +
    {
 
417 +
        while (auto* reg = impl->signals_)
 
418 +
        {
 
419 +
            impl->signals_ = reg->next_in_set;
 
420 +
            delete reg;
 
421 +
        }
 
422 +
        delete impl;
 
423 +
    }
 
424 +
}
 
425 +

 
426 +
inline io_object::implementation*
 
427 +
posix_signal_service::construct()
 
428 +
{
 
429 +
    auto* impl = new posix_signal(*this);
 
430 +

 
431 +
    {
 
432 +
        std::lock_guard lock(mutex_);
 
433 +
        impl_list_.push_back(impl);
 
434 +
    }
 
435 +

 
436 +
    return impl;
 
437 +
}
 
438 +

 
439 +
inline void
 
440 +
posix_signal_service::destroy_impl(posix_signal& impl)
 
441 +
{
 
442 +
    {
 
443 +
        std::lock_guard lock(mutex_);
 
444 +
        impl_list_.remove(&impl);
 
445 +
    }
 
446 +

 
447 +
    delete &impl;
 
448 +
}
 
449 +

 
450 +
inline std::error_code
 
451 +
posix_signal_service::add_signal(
 
452 +
    posix_signal& impl, int signal_number, signal_set::flags_t flags)
 
453 +
{
 
454 +
    if (signal_number < 0 || signal_number >= max_signal_number)
 
455 +
        return make_error_code(std::errc::invalid_argument);
 
456 +

 
457 +
    // Validate that requested flags are supported on this platform
 
458 +
    // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
 
459 +
    if (!posix_signal_detail::flags_supported(flags))
 
460 +
        return make_error_code(std::errc::operation_not_supported);
 
461 +

 
462 +
    posix_signal_detail::signal_state* state =
 
463 +
        posix_signal_detail::get_signal_state();
 
464 +
    std::lock_guard state_lock(state->mutex);
 
465 +
    std::lock_guard lock(mutex_);
 
466 +

 
467 +
    // Find insertion point (list is sorted by signal number)
 
468 +
    signal_registration** insertion_point = &impl.signals_;
 
469 +
    signal_registration* reg              = impl.signals_;
 
470 +
    while (reg && reg->signal_number < signal_number)
 
471 +
    {
 
472 +
        insertion_point = &reg->next_in_set;
 
473 +
        reg             = reg->next_in_set;
 
474 +
    }
 
475 +

 
476 +
    // Already registered in this set - check flag compatibility
 
477 +
    // (same signal_set adding same signal twice with different flags)
 
478 +
    if (reg && reg->signal_number == signal_number)
 
479 +
    {
 
480 +
        if (!posix_signal_detail::flags_compatible(reg->flags, flags))
 
481 +
            return make_error_code(std::errc::invalid_argument);
 
482 +
        return {};
 
483 +
    }
 
484 +

 
485 +
    // Check flag compatibility with global registration
 
486 +
    // (different signal_set already registered this signal with different flags)
 
487 +
    if (state->registration_count[signal_number] > 0)
 
488 +
    {
 
489 +
        if (!posix_signal_detail::flags_compatible(
 
490 +
                state->registered_flags[signal_number], flags))
 
491 +
            return make_error_code(std::errc::invalid_argument);
 
492 +
    }
 
493 +

 
494 +
    auto* new_reg          = new signal_registration;
 
495 +
    new_reg->signal_number = signal_number;
 
496 +
    new_reg->flags         = flags;
 
497 +
    new_reg->owner         = &impl;
 
498 +
    new_reg->undelivered   = 0;
 
499 +

 
500 +
    // Install signal handler on first global registration
 
501 +
    if (state->registration_count[signal_number] == 0)
 
502 +
    {
 
503 +
        struct sigaction sa = {};
 
504 +
        sa.sa_handler       = posix_signal_detail::corosio_posix_signal_handler;
 
505 +
        sigemptyset(&sa.sa_mask);
 
506 +
        sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
 
507 +

 
508 +
        if (::sigaction(signal_number, &sa, nullptr) < 0)
 
509 +
        {
 
510 +
            delete new_reg;
 
511 +
            return make_error_code(std::errc::invalid_argument);
 
512 +
        }
 
513 +

 
514 +
        // Store the flags used for first registration
 
515 +
        state->registered_flags[signal_number] = flags;
 
516 +
    }
 
517 +

 
518 +
    new_reg->next_in_set = reg;
 
519 +
    *insertion_point     = new_reg;
 
520 +

 
521 +
    new_reg->next_in_table = registrations_[signal_number];
 
522 +
    new_reg->prev_in_table = nullptr;
 
523 +
    if (registrations_[signal_number])
 
524 +
        registrations_[signal_number]->prev_in_table = new_reg;
 
525 +
    registrations_[signal_number] = new_reg;
 
526 +

 
527 +
    ++state->registration_count[signal_number];
 
528 +
    ++registration_count_[signal_number];
 
529 +

 
530 +
    return {};
 
531 +
}
 
532 +

 
533 +
inline std::error_code
 
534 +
posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
 
535 +
{
 
536 +
    if (signal_number < 0 || signal_number >= max_signal_number)
 
537 +
        return make_error_code(std::errc::invalid_argument);
 
538 +

 
539 +
    posix_signal_detail::signal_state* state =
 
540 +
        posix_signal_detail::get_signal_state();
 
541 +
    std::lock_guard state_lock(state->mutex);
 
542 +
    std::lock_guard lock(mutex_);
 
543 +

 
544 +
    signal_registration** deletion_point = &impl.signals_;
 
545 +
    signal_registration* reg             = impl.signals_;
 
546 +
    while (reg && reg->signal_number < signal_number)
 
547 +
    {
 
548 +
        deletion_point = &reg->next_in_set;
 
549 +
        reg            = reg->next_in_set;
 
550 +
    }
 
551 +

 
552 +
    if (!reg || reg->signal_number != signal_number)
 
553 +
        return {};
 
554 +

 
555 +
    // Restore default handler on last global unregistration
 
556 +
    if (state->registration_count[signal_number] == 1)
 
557 +
    {
 
558 +
        struct sigaction sa = {};
 
559 +
        sa.sa_handler       = SIG_DFL;
 
560 +
        sigemptyset(&sa.sa_mask);
 
561 +
        sa.sa_flags = 0;
 
562 +

 
563 +
        if (::sigaction(signal_number, &sa, nullptr) < 0)
 
564 +
            return make_error_code(std::errc::invalid_argument);
 
565 +

 
566 +
        // Clear stored flags
 
567 +
        state->registered_flags[signal_number] = signal_set::none;
 
568 +
    }
 
569 +

 
570 +
    *deletion_point = reg->next_in_set;
 
571 +

 
572 +
    if (registrations_[signal_number] == reg)
 
573 +
        registrations_[signal_number] = reg->next_in_table;
 
574 +
    if (reg->prev_in_table)
 
575 +
        reg->prev_in_table->next_in_table = reg->next_in_table;
 
576 +
    if (reg->next_in_table)
 
577 +
        reg->next_in_table->prev_in_table = reg->prev_in_table;
 
578 +

 
579 +
    --state->registration_count[signal_number];
 
580 +
    --registration_count_[signal_number];
 
581 +

 
582 +
    delete reg;
 
583 +
    return {};
 
584 +
}
 
585 +

 
586 +
inline std::error_code
 
587 +
posix_signal_service::clear_signals(posix_signal& impl)
 
588 +
{
 
589 +
    posix_signal_detail::signal_state* state =
 
590 +
        posix_signal_detail::get_signal_state();
 
591 +
    std::lock_guard state_lock(state->mutex);
 
592 +
    std::lock_guard lock(mutex_);
 
593 +

 
594 +
    std::error_code first_error;
 
595 +

 
596 +
    while (signal_registration* reg = impl.signals_)
 
597 +
    {
 
598 +
        int signal_number = reg->signal_number;
 
599 +

 
600 +
        if (state->registration_count[signal_number] == 1)
 
601 +
        {
 
602 +
            struct sigaction sa = {};
 
603 +
            sa.sa_handler       = SIG_DFL;
 
604 +
            sigemptyset(&sa.sa_mask);
 
605 +
            sa.sa_flags = 0;
 
606 +

 
607 +
            if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
 
608 +
                first_error = make_error_code(std::errc::invalid_argument);
 
609 +

 
610 +
            // Clear stored flags
 
611 +
            state->registered_flags[signal_number] = signal_set::none;
 
612 +
        }
 
613 +

 
614 +
        impl.signals_ = reg->next_in_set;
 
615 +

 
616 +
        if (registrations_[signal_number] == reg)
 
617 +
            registrations_[signal_number] = reg->next_in_table;
 
618 +
        if (reg->prev_in_table)
 
619 +
            reg->prev_in_table->next_in_table = reg->next_in_table;
 
620 +
        if (reg->next_in_table)
 
621 +
            reg->next_in_table->prev_in_table = reg->prev_in_table;
 
622 +

 
623 +
        --state->registration_count[signal_number];
 
624 +
        --registration_count_[signal_number];
 
625 +

 
626 +
        delete reg;
 
627 +
    }
 
628 +

 
629 +
    if (first_error)
 
630 +
        return first_error;
 
631 +
    return {};
 
632 +
}
 
633 +

 
634 +
inline void
 
635 +
posix_signal_service::cancel_wait(posix_signal& impl)
 
636 +
{
 
637 +
    bool was_waiting = false;
 
638 +
    signal_op* op    = nullptr;
 
639 +

 
640 +
    {
 
641 +
        std::lock_guard lock(mutex_);
 
642 +
        if (impl.waiting_)
 
643 +
        {
 
644 +
            was_waiting   = true;
 
645 +
            impl.waiting_ = false;
 
646 +
            op            = &impl.pending_op_;
 
647 +
        }
 
648 +
    }
 
649 +

 
650 +
    if (was_waiting)
 
651 +
    {
 
652 +
        if (op->ec_out)
 
653 +
            *op->ec_out = make_error_code(capy::error::canceled);
 
654 +
        if (op->signal_out)
 
655 +
            *op->signal_out = 0;
 
656 +
        op->d.post(op->h);
 
657 +
        sched_->work_finished();
 
658 +
    }
 
659 +
}
 
660 +

 
661 +
inline void
 
662 +
posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
 
663 +
{
 
664 +
    {
 
665 +
        std::lock_guard lock(mutex_);
 
666 +

 
667 +
        // Check for queued signals first (signal arrived before wait started)
 
668 +
        signal_registration* reg = impl.signals_;
 
669 +
        while (reg)
 
670 +
        {
 
671 +
            if (reg->undelivered > 0)
 
672 +
            {
 
673 +
                --reg->undelivered;
 
674 +
                op->signal_number = reg->signal_number;
 
675 +
                // svc=nullptr: no work_finished needed since we never called work_started
 
676 +
                op->svc = nullptr;
 
677 +
                sched_->post(op);
 
678 +
                return;
 
679 +
            }
 
680 +
            reg = reg->next_in_set;
 
681 +
        }
 
682 +

 
683 +
        // No queued signals - wait for delivery
 
684 +
        impl.waiting_ = true;
 
685 +
        // svc=this: signal_op::operator() will call work_finished() to balance this
 
686 +
        op->svc = this;
 
687 +
        sched_->work_started();
 
688 +
    }
 
689 +
}
 
690 +

 
691 +
inline void
 
692 +
posix_signal_service::deliver_signal(int signal_number)
 
693 +
{
 
694 +
    if (signal_number < 0 || signal_number >= max_signal_number)
 
695 +
        return;
 
696 +

 
697 +
    posix_signal_detail::signal_state* state =
 
698 +
        posix_signal_detail::get_signal_state();
 
699 +
    std::lock_guard lock(state->mutex);
 
700 +

 
701 +
    posix_signal_service* service = state->service_list;
 
702 +
    while (service)
 
703 +
    {
 
704 +
        std::lock_guard svc_lock(service->mutex_);
 
705 +

 
706 +
        signal_registration* reg = service->registrations_[signal_number];
 
707 +
        while (reg)
 
708 +
        {
 
709 +
            posix_signal* impl = static_cast<posix_signal*>(reg->owner);
 
710 +

 
711 +
            if (impl->waiting_)
 
712 +
            {
 
713 +
                impl->waiting_                  = false;
 
714 +
                impl->pending_op_.signal_number = signal_number;
 
715 +
                service->post(&impl->pending_op_);
 
716 +
            }
 
717 +
            else
 
718 +
            {
 
719 +
                ++reg->undelivered;
 
720 +
            }
 
721 +

 
722 +
            reg = reg->next_in_table;
 
723 +
        }
 
724 +

 
725 +
        service = service->next_;
 
726 +
    }
 
727 +
}
 
728 +

 
729 +
inline void
 
730 +
posix_signal_service::work_started() noexcept
 
731 +
{
 
732 +
    sched_->work_started();
 
733 +
}
 
734 +

 
735 +
inline void
 
736 +
posix_signal_service::work_finished() noexcept
 
737 +
{
 
738 +
    sched_->work_finished();
 
739 +
}
 
740 +

 
741 +
inline void
 
742 +
posix_signal_service::post(signal_op* op)
 
743 +
{
 
744 +
    sched_->post(op);
 
745 +
}
 
746 +

 
747 +
inline void
 
748 +
posix_signal_service::add_service(posix_signal_service* service)
 
749 +
{
 
750 +
    posix_signal_detail::signal_state* state =
 
751 +
        posix_signal_detail::get_signal_state();
 
752 +
    std::lock_guard lock(state->mutex);
 
753 +

 
754 +
    service->next_ = state->service_list;
 
755 +
    service->prev_ = nullptr;
 
756 +
    if (state->service_list)
 
757 +
        state->service_list->prev_ = service;
 
758 +
    state->service_list = service;
 
759 +
}
 
760 +

 
761 +
inline void
 
762 +
posix_signal_service::remove_service(posix_signal_service* service)
 
763 +
{
 
764 +
    posix_signal_detail::signal_state* state =
 
765 +
        posix_signal_detail::get_signal_state();
 
766 +
    std::lock_guard lock(state->mutex);
 
767 +

 
768 +
    if (service->next_ || service->prev_ || state->service_list == service)
 
769 +
    {
 
770 +
        if (state->service_list == service)
 
771 +
            state->service_list = service->next_;
 
772 +
        if (service->prev_)
 
773 +
            service->prev_->next_ = service->next_;
 
774 +
        if (service->next_)
 
775 +
            service->next_->prev_ = service->prev_;
 
776 +
        service->next_ = nullptr;
 
777 +
        service->prev_ = nullptr;
 
778 +
    }
 
779 +
}
 
780 +

 
781 +
// get_signal_service - factory function
 
782 +

 
783 +
inline posix_signal_service&
 
784 +
get_signal_service(capy::execution_context& ctx, scheduler& sched)
 
785 +
{
 
786 +
    return ctx.make_service<posix_signal_service>(sched);
 
787 +
}
 
788 +

 
789 +
} // namespace detail
 
790 +
} // namespace boost::corosio
 
791 +

 
792 +
#endif // BOOST_COROSIO_POSIX
 
793 +

 
794 +
#endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP