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

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