include/boost/corosio/native/detail/posix/posix_signal_service.hpp

89.6% Lines (301/336) 96.9% List of functions (31/32)
posix_signal_service.hpp
f(x) Functions (32)
Function Calls Lines Blocks
boost::corosio::detail::posix_signal_service::destroy(boost::corosio::io_object::implementation*) :166 168x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::flags_supported(boost::corosio::signal_set::flags_t) :270 171x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::to_sigaction_flags(boost::corosio::signal_set::flags_t) :282 144x 76.9% 82.0% boost::corosio::detail::posix_signal_detail::flags_compatible(boost::corosio::signal_set::flags_t, boost::corosio::signal_set::flags_t) :302 27x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::open_signal_pipe(boost::corosio::detail::posix_signal_detail::signal_state*) :320 171x 73.3% 79.0% boost::corosio::detail::posix_signal_detail::corosio_posix_signal_handler(int) :353 450x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::drain_signal_pipe() :369 450x 100.0% 100.0% boost::corosio::detail::signal_op::operator()() :385 453x 100.0% 100.0% boost::corosio::detail::signal_op::destroy() :405 0 0.0% 0.0% boost::corosio::detail::posix_signal::posix_signal(boost::corosio::detail::posix_signal_service&) :412 168x 100.0% 100.0% boost::corosio::detail::posix_signal::wait(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, std::stop_token, std::error_code*, int*) :418 465x 100.0% 100.0% boost::corosio::detail::posix_signal::add(int, boost::corosio::signal_set::flags_t) :449 177x 100.0% 100.0% boost::corosio::detail::posix_signal::remove(int) :455 9x 100.0% 100.0% boost::corosio::detail::posix_signal::clear() :461 174x 100.0% 100.0% boost::corosio::detail::posix_signal::cancel() :467 189x 100.0% 100.0% boost::corosio::detail::posix_signal_service::posix_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :474 1876x 100.0% 79.0% boost::corosio::detail::posix_signal_service::~posix_signal_service() :486 3752x 100.0% 100.0% boost::corosio::detail::posix_signal_service::shutdown() :492 1876x 40.0% 43.0% boost::corosio::detail::posix_signal_service::construct() :509 168x 100.0% 100.0% boost::corosio::detail::posix_signal_service::destroy_impl(boost::corosio::detail::posix_signal&) :522 168x 100.0% 100.0% boost::corosio::detail::posix_signal_service::add_signal(boost::corosio::detail::posix_signal&, int, boost::corosio::signal_set::flags_t) :533 177x 80.0% 76.0% boost::corosio::detail::posix_signal_service::add_signal(boost::corosio::detail::posix_signal&, int, boost::corosio::signal_set::flags_t)::{lambda()#1}::operator()() const :560 123x 95.2% 100.0% boost::corosio::detail::posix_signal_service::remove_signal(boost::corosio::detail::posix_signal&, int) :634 9x 84.8% 82.0% boost::corosio::detail::posix_signal_service::clear_signals(boost::corosio::detail::posix_signal&) :687 174x 90.0% 79.0% boost::corosio::detail::posix_signal_service::cancel_wait(boost::corosio::detail::posix_signal&) :735 189x 100.0% 100.0% boost::corosio::detail::posix_signal_service::start_wait(boost::corosio::detail::posix_signal&, boost::corosio::detail::signal_op*) :764 462x 79.2% 84.0% boost::corosio::detail::posix_signal_service::deliver_signal(int) :807 450x 90.0% 72.0% boost::corosio::detail::posix_signal_service::work_finished() :851 453x 100.0% 100.0% boost::corosio::detail::posix_signal_service::post(boost::corosio::detail::signal_op*) :857 453x 100.0% 100.0% boost::corosio::detail::posix_signal_service::add_service(boost::corosio::detail::posix_signal_service*) :863 1876x 100.0% 100.0% boost::corosio::detail::posix_signal_service::remove_service(boost::corosio::detail::posix_signal_service*) :877 1876x 92.3% 93.0% boost::corosio::detail::get_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :899 1876x 100.0% 100.0%
Line TLA Hits 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 168x void destroy(io_object::implementation* p) override
167 {
168 168x auto& impl = static_cast<posix_signal&>(*p);
169 168x [[maybe_unused]] auto n = impl.clear();
170 168x impl.cancel();
171 168x destroy_impl(impl);
172 168x }
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 171x 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 171x return true;
277 }
278
279 // Map abstract flags to sigaction() flags.
280 // Caller must ensure flags_supported() returns true first.
281 inline int
282 144x to_sigaction_flags(signal_set::flags_t flags)
283 {
284 144x int sa_flags = 0;
285 144x if (flags & signal_set::restart)
286 27x sa_flags |= SA_RESTART;
287 144x if (flags & signal_set::no_child_stop)
288 sa_flags |= SA_NOCLDSTOP;
289 #ifdef SA_NOCLDWAIT
290 144x if (flags & signal_set::no_child_wait)
291 sa_flags |= SA_NOCLDWAIT;
292 #endif
293 144x if (flags & signal_set::no_defer)
294 3x sa_flags |= SA_NODEFER;
295 144x if (flags & signal_set::reset_handler)
296 sa_flags |= SA_RESETHAND;
297 144x return sa_flags;
298 }
299
300 // Check if two flag values are compatible
301 inline bool
302 27x flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
303 {
304 // dont_care is always compatible
305 51x if ((existing & signal_set::dont_care) ||
306 24x (requested & signal_set::dont_care))
307 9x return true;
308
309 // Mask out dont_care bit for comparison
310 18x constexpr auto mask = ~signal_set::dont_care;
311 18x 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 171x open_signal_pipe(signal_state* state)
321 {
322 171x if (state->read_fd >= 0)
323 169x return true;
324
325 int fds[2];
326 2x if (::pipe(fds) < 0)
327 return false;
328
329 6x for (int i = 0; i < 2; ++i)
330 {
331 4x int fl = ::fcntl(fds[i], F_GETFL, 0);
332 8x if (fl == -1 || ::fcntl(fds[i], F_SETFL, fl | O_NONBLOCK) == -1 ||
333 4x ::fcntl(fds[i], F_SETFD, FD_CLOEXEC) == -1)
334 {
335 ::close(fds[0]);
336 ::close(fds[1]);
337 return false;
338 }
339 }
340
341 2x state->read_fd = fds[0];
342 2x state->write_fd = fds[1];
343 2x 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 450x corosio_posix_signal_handler(int signal_number)
354 {
355 450x int saved_errno = errno;
356 450x signal_state* state = get_signal_state();
357 [[maybe_unused]] ssize_t r =
358 450x ::write(state->write_fd, &signal_number, sizeof(int));
359 450x errno = saved_errno;
360 // With sigaction(), the handler persists automatically (unlike some
361 // signal() implementations that reset to SIG_DFL).
362 450x }
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 450x drain_signal_pipe()
370 {
371 450x signal_state* state = get_signal_state();
372 int signal_number;
373 900x while (::read(state->read_fd, &signal_number, sizeof(int)) ==
374 static_cast<ssize_t>(sizeof(int)))
375 {
376 450x posix_signal_service::deliver_signal(signal_number);
377 }
378 450x }
379
380 } // namespace posix_signal_detail
381
382 // signal_op implementation
383
384 inline void
385 453x signal_op::operator()()
386 {
387 453x if (ec_out)
388 453x *ec_out = {};
389 453x if (signal_out)
390 453x *signal_out = signal_number;
391
392 // Capture svc before resuming (coro may destroy us)
393 453x auto* service = svc;
394 453x svc = nullptr;
395
396 453x cont.h = h;
397 453x d.post(cont);
398
399 // Balance the work_started() from start_wait
400 453x if (service)
401 453x service->work_finished();
402 453x }
403
404 inline void
405 signal_op::destroy()
406 {
407 // No-op: signal_op is embedded in posix_signal
408 }
409
410 // posix_signal implementation
411
412 168x inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
413 168x : svc_(svc)
414 {
415 168x }
416
417 inline std::coroutine_handle<>
418 465x 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 465x pending_op_.h = h;
426 465x pending_op_.d = d;
427 465x pending_op_.ec_out = ec;
428 465x pending_op_.signal_out = signal_out;
429 465x pending_op_.signal_number = 0;
430
431 465x if (token.stop_requested())
432 {
433 3x if (ec)
434 3x *ec = make_error_code(capy::error::canceled);
435 3x if (signal_out)
436 3x *signal_out = 0;
437 3x pending_op_.cont.h = h;
438 3x d.post(pending_op_.cont);
439 // completion is always posted to scheduler queue, never inline.
440 3x return std::noop_coroutine();
441 }
442
443 462x svc_.start_wait(*this, &pending_op_);
444 // completion is always posted to scheduler queue, never inline.
445 462x return std::noop_coroutine();
446 }
447
448 inline std::error_code
449 177x posix_signal::add(int signal_number, signal_set::flags_t flags)
450 {
451 177x return svc_.add_signal(*this, signal_number, flags);
452 }
453
454 inline std::error_code
455 9x posix_signal::remove(int signal_number)
456 {
457 9x return svc_.remove_signal(*this, signal_number);
458 }
459
460 inline std::error_code
461 174x posix_signal::clear()
462 {
463 174x return svc_.clear_signals(*this);
464 }
465
466 inline void
467 189x posix_signal::cancel()
468 {
469 189x svc_.cancel_wait(*this);
470 189x }
471
472 // posix_signal_service implementation
473
474 1876x inline posix_signal_service::posix_signal_service(
475 1876x capy::execution_context&, scheduler& sched)
476 1876x : sched_(&sched)
477 {
478 121940x for (int i = 0; i < max_signal_number; ++i)
479 {
480 120064x registrations_[i] = nullptr;
481 120064x registration_count_[i] = 0;
482 }
483 1876x add_service(this);
484 1876x }
485
486 3752x inline posix_signal_service::~posix_signal_service()
487 {
488 1876x remove_service(this);
489 3752x }
490
491 inline void
492 1876x posix_signal_service::shutdown()
493 {
494 1876x std::lock_guard lock(mutex_);
495
496 1876x for (auto* impl = impl_list_.pop_front(); impl != nullptr;
497 impl = impl_list_.pop_front())
498 {
499 while (auto* reg = impl->signals_)
500 {
501 impl->signals_ = reg->next_in_set;
502 delete reg;
503 }
504 delete impl;
505 }
506 1876x }
507
508 inline io_object::implementation*
509 168x posix_signal_service::construct()
510 {
511 168x auto* impl = new posix_signal(*this);
512
513 {
514 168x std::lock_guard lock(mutex_);
515 168x impl_list_.push_back(impl);
516 168x }
517
518 168x return impl;
519 }
520
521 inline void
522 168x posix_signal_service::destroy_impl(posix_signal& impl)
523 {
524 {
525 168x std::lock_guard lock(mutex_);
526 168x impl_list_.remove(&impl);
527 168x }
528
529 168x delete &impl;
530 168x }
531
532 inline std::error_code
533 177x posix_signal_service::add_signal(
534 posix_signal& impl, int signal_number, signal_set::flags_t flags)
535 {
536 177x if (signal_number < 0 || signal_number >= max_signal_number)
537 6x 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 171x if (!posix_signal_detail::flags_supported(flags))
542 return make_error_code(std::errc::operation_not_supported);
543
544 posix_signal_detail::signal_state* state =
545 171x 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 171x std::lock_guard state_lock(state->mutex);
557 171x if (!posix_signal_detail::open_signal_pipe(state))
558 return make_error_code(std::errc::io_error);
559 171x }
560 171x std::call_once(reader_once_, [this, state] {
561 123x sched_->register_signal_reader(state->read_fd);
562 123x });
563
564 171x std::lock_guard state_lock(state->mutex);
565 171x std::lock_guard lock(mutex_);
566
567 // Find insertion point (list is sorted by signal number)
568 171x signal_registration** insertion_point = &impl.signals_;
569 171x signal_registration* reg = impl.signals_;
570 192x while (reg && reg->signal_number < signal_number)
571 {
572 21x insertion_point = &reg->next_in_set;
573 21x 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 171x if (reg && reg->signal_number == signal_number)
579 {
580 15x if (!posix_signal_detail::flags_compatible(reg->flags, flags))
581 3x return make_error_code(std::errc::invalid_argument);
582 12x return {};
583 }
584
585 // Check flag compatibility with global registration
586 // (different signal_set already registered this signal with different flags)
587 156x if (state->registration_count[signal_number] > 0)
588 {
589 12x if (!posix_signal_detail::flags_compatible(
590 state->registered_flags[signal_number], flags))
591 3x return make_error_code(std::errc::invalid_argument);
592 }
593
594 153x auto* new_reg = new signal_registration;
595 153x new_reg->signal_number = signal_number;
596 153x new_reg->flags = flags;
597 153x new_reg->owner = &impl;
598 153x new_reg->undelivered = 0;
599
600 // Install signal handler on first global registration
601 153x if (state->registration_count[signal_number] == 0)
602 {
603 144x struct sigaction sa = {};
604 144x sa.sa_handler = posix_signal_detail::corosio_posix_signal_handler;
605 144x sigemptyset(&sa.sa_mask);
606 144x sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
607
608 144x if (::sigaction(signal_number, &sa, nullptr) < 0)
609 {
610 delete new_reg;
611 return make_error_code(std::errc::invalid_argument);
612 }
613
614 // Store the flags used for first registration
615 144x state->registered_flags[signal_number] = flags;
616 }
617
618 153x new_reg->next_in_set = reg;
619 153x *insertion_point = new_reg;
620
621 153x new_reg->next_in_table = registrations_[signal_number];
622 153x new_reg->prev_in_table = nullptr;
623 153x if (registrations_[signal_number])
624 9x registrations_[signal_number]->prev_in_table = new_reg;
625 153x registrations_[signal_number] = new_reg;
626
627 153x ++state->registration_count[signal_number];
628 153x ++registration_count_[signal_number];
629
630 153x return {};
631 171x }
632
633 inline std::error_code
634 9x posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
635 {
636 9x if (signal_number < 0 || signal_number >= max_signal_number)
637 3x return make_error_code(std::errc::invalid_argument);
638
639 posix_signal_detail::signal_state* state =
640 6x posix_signal_detail::get_signal_state();
641 6x std::lock_guard state_lock(state->mutex);
642 6x std::lock_guard lock(mutex_);
643
644 6x signal_registration** deletion_point = &impl.signals_;
645 6x signal_registration* reg = impl.signals_;
646 6x while (reg && reg->signal_number < signal_number)
647 {
648 deletion_point = &reg->next_in_set;
649 reg = reg->next_in_set;
650 }
651
652 6x if (!reg || reg->signal_number != signal_number)
653 3x return {};
654
655 // Restore default handler on last global unregistration
656 3x if (state->registration_count[signal_number] == 1)
657 {
658 3x struct sigaction sa = {};
659 3x sa.sa_handler = SIG_DFL;
660 3x sigemptyset(&sa.sa_mask);
661 3x sa.sa_flags = 0;
662
663 3x if (::sigaction(signal_number, &sa, nullptr) < 0)
664 return make_error_code(std::errc::invalid_argument);
665
666 // Clear stored flags
667 3x state->registered_flags[signal_number] = signal_set::none;
668 }
669
670 3x *deletion_point = reg->next_in_set;
671
672 3x if (registrations_[signal_number] == reg)
673 3x registrations_[signal_number] = reg->next_in_table;
674 3x if (reg->prev_in_table)
675 reg->prev_in_table->next_in_table = reg->next_in_table;
676 3x if (reg->next_in_table)
677 reg->next_in_table->prev_in_table = reg->prev_in_table;
678
679 3x --state->registration_count[signal_number];
680 3x --registration_count_[signal_number];
681
682 3x delete reg;
683 3x return {};
684 6x }
685
686 inline std::error_code
687 174x posix_signal_service::clear_signals(posix_signal& impl)
688 {
689 posix_signal_detail::signal_state* state =
690 174x posix_signal_detail::get_signal_state();
691 174x std::lock_guard state_lock(state->mutex);
692 174x std::lock_guard lock(mutex_);
693
694 174x std::error_code first_error;
695
696 324x while (signal_registration* reg = impl.signals_)
697 {
698 150x int signal_number = reg->signal_number;
699
700 150x if (state->registration_count[signal_number] == 1)
701 {
702 141x struct sigaction sa = {};
703 141x sa.sa_handler = SIG_DFL;
704 141x sigemptyset(&sa.sa_mask);
705 141x sa.sa_flags = 0;
706
707 141x if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
708 first_error = make_error_code(std::errc::invalid_argument);
709
710 // Clear stored flags
711 141x state->registered_flags[signal_number] = signal_set::none;
712 }
713
714 150x impl.signals_ = reg->next_in_set;
715
716 150x if (registrations_[signal_number] == reg)
717 150x registrations_[signal_number] = reg->next_in_table;
718 150x if (reg->prev_in_table)
719 reg->prev_in_table->next_in_table = reg->next_in_table;
720 150x if (reg->next_in_table)
721 9x reg->next_in_table->prev_in_table = reg->prev_in_table;
722
723 150x --state->registration_count[signal_number];
724 150x --registration_count_[signal_number];
725
726 150x delete reg;
727 150x }
728
729 174x if (first_error)
730 return first_error;
731 174x return {};
732 174x }
733
734 inline void
735 189x posix_signal_service::cancel_wait(posix_signal& impl)
736 {
737 189x bool was_waiting = false;
738 189x signal_op* op = nullptr;
739
740 {
741 189x std::lock_guard lock(mutex_);
742 189x impl.cancelled_ = true;
743 189x if (impl.waiting_)
744 {
745 6x was_waiting = true;
746 6x impl.waiting_ = false;
747 6x op = &impl.pending_op_;
748 }
749 189x }
750
751 189x if (was_waiting)
752 {
753 6x if (op->ec_out)
754 6x *op->ec_out = make_error_code(capy::error::canceled);
755 6x if (op->signal_out)
756 6x *op->signal_out = 0;
757 6x op->cont.h = op->h;
758 6x op->d.post(op->cont);
759 6x sched_->work_finished();
760 }
761 189x }
762
763 inline void
764 462x posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
765 {
766 {
767 462x std::lock_guard lock(mutex_);
768
769 // Check if cancel() was called before this wait started
770 462x if (impl.cancelled_)
771 {
772 3x impl.cancelled_ = false;
773 3x if (op->ec_out)
774 3x *op->ec_out = make_error_code(capy::error::canceled);
775 3x if (op->signal_out)
776 3x *op->signal_out = 0;
777 3x op->cont.h = op->h;
778 3x op->d.post(op->cont);
779 3x return;
780 }
781
782 // Check for queued signals first (signal arrived before wait started)
783 459x signal_registration* reg = impl.signals_;
784 921x while (reg)
785 {
786 462x if (reg->undelivered > 0)
787 {
788 --reg->undelivered;
789 op->signal_number = reg->signal_number;
790 // svc=nullptr: no work_finished needed since we never called work_started
791 op->svc = nullptr;
792 sched_->post(op);
793 return;
794 }
795 462x reg = reg->next_in_set;
796 }
797
798 // No queued signals - wait for delivery
799 459x impl.waiting_ = true;
800 // svc=this: signal_op::operator() will call work_finished() to balance this
801 459x op->svc = this;
802 459x sched_->work_started();
803 462x }
804 }
805
806 inline void
807 450x posix_signal_service::deliver_signal(int signal_number)
808 {
809 450x if (signal_number < 0 || signal_number >= max_signal_number)
810 return;
811
812 posix_signal_detail::signal_state* state =
813 450x posix_signal_detail::get_signal_state();
814 450x std::lock_guard lock(state->mutex);
815
816 450x posix_signal_service* service = state->service_list;
817 900x while (service)
818 {
819 450x std::lock_guard svc_lock(service->mutex_);
820
821 450x signal_registration* reg = service->registrations_[signal_number];
822 903x while (reg)
823 {
824 453x posix_signal* impl = static_cast<posix_signal*>(reg->owner);
825
826 453x if (impl->waiting_)
827 {
828 453x impl->waiting_ = false;
829 453x impl->pending_op_.signal_number = signal_number;
830 453x service->post(&impl->pending_op_);
831 }
832 else
833 {
834 ++reg->undelivered;
835 }
836
837 453x reg = reg->next_in_table;
838 }
839
840 450x service = service->next_;
841 450x }
842 450x }
843
844 inline void
845 posix_signal_service::work_started() noexcept
846 {
847 sched_->work_started();
848 }
849
850 inline void
851 453x posix_signal_service::work_finished() noexcept
852 {
853 453x sched_->work_finished();
854 453x }
855
856 inline void
857 453x posix_signal_service::post(signal_op* op)
858 {
859 453x sched_->post(op);
860 453x }
861
862 inline void
863 1876x posix_signal_service::add_service(posix_signal_service* service)
864 {
865 posix_signal_detail::signal_state* state =
866 1876x posix_signal_detail::get_signal_state();
867 1876x std::lock_guard lock(state->mutex);
868
869 1876x service->next_ = state->service_list;
870 1876x service->prev_ = nullptr;
871 1876x if (state->service_list)
872 7x state->service_list->prev_ = service;
873 1876x state->service_list = service;
874 1876x }
875
876 inline void
877 1876x posix_signal_service::remove_service(posix_signal_service* service)
878 {
879 posix_signal_detail::signal_state* state =
880 1876x posix_signal_detail::get_signal_state();
881 1876x std::lock_guard lock(state->mutex);
882
883 1876x if (service->next_ || service->prev_ || state->service_list == service)
884 {
885 1876x if (state->service_list == service)
886 1876x state->service_list = service->next_;
887 1876x if (service->prev_)
888 service->prev_->next_ = service->next_;
889 1876x if (service->next_)
890 7x service->next_->prev_ = service->prev_;
891 1876x service->next_ = nullptr;
892 1876x service->prev_ = nullptr;
893 }
894 1876x }
895
896 // get_signal_service - factory function
897
898 inline posix_signal_service&
899 1876x get_signal_service(capy::execution_context& ctx, scheduler& sched)
900 {
901 1876x 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
910