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