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