TLA Line data 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/corosio/native/detail/make_err.hpp>
24 : #include <boost/capy/error.hpp>
25 :
26 : #include <mutex>
27 : #include <tuple>
28 :
29 : #include <errno.h>
30 : #include <fcntl.h>
31 : #include <signal.h>
32 : #include <unistd.h>
33 :
34 : /*
35 : POSIX Signal Service
36 : ====================
37 :
38 : Concrete signal service implementation for POSIX backends. Manages signal
39 : registrations via sigaction() and dispatches completions through the
40 : scheduler. One instance per execution_context, created by
41 : get_signal_service().
42 :
43 : See the block comment further down for the full architecture overview.
44 : */
45 :
46 : /*
47 : POSIX Signal Implementation
48 : ===========================
49 :
50 : This file implements signal handling for POSIX systems using sigaction().
51 : The implementation supports signal flags (SA_RESTART, etc.) and integrates
52 : with any POSIX-compatible scheduler via the abstract scheduler interface.
53 :
54 : Architecture Overview
55 : ---------------------
56 :
57 : Three layers manage signal registrations:
58 :
59 : 1. signal_state (global singleton)
60 : - Tracks the global service list and per-signal registration counts
61 : - Stores the flags used for first registration of each signal (for
62 : conflict detection when multiple signal_sets register same signal)
63 : - Owns the mutex that protects signal handler installation/removal
64 :
65 : 2. posix_signal_service (one per execution_context)
66 : - Maintains registrations_[] table indexed by signal number
67 : - Each slot is a doubly-linked list of signal_registrations for that signal
68 : - Also maintains impl_list_ of all posix_signal objects it owns
69 :
70 : 3. posix_signal (one per signal_set)
71 : - Owns a singly-linked list (sorted by signal number) of signal_registrations
72 : - Contains the pending_op_ used for wait operations
73 :
74 : Signal Delivery Flow
75 : --------------------
76 :
77 : Delivery uses the self-pipe trick so the signal handler itself performs
78 : only async-signal-safe work (mirrors Boost.Asio):
79 :
80 : 1. Signal arrives -> corosio_posix_signal_handler(). The handler only
81 : write()s the signal number to the global self-pipe (write_fd) and
82 : restores errno. No locks, no allocation, no scheduler dispatch.
83 :
84 : 2. The read end of the pipe is watched by one backend's event loop
85 : (registered via scheduler::register_signal_reader on the first
86 : registration). When it becomes readable the backend drains it
87 : (drain_signal_pipe) and calls deliver_signal() in normal context.
88 :
89 : 3. deliver_signal() iterates all posix_signal_service services:
90 : - If a signal_set is waiting (impl->waiting_ == true), post the signal_op
91 : to the scheduler for immediate completion
92 : - Otherwise, increment reg->undelivered to queue the signal
93 :
94 : 4. When wait() is called via start_wait():
95 : - First check for queued signals (undelivered > 0); if found, post
96 : immediate completion without blocking
97 : - Otherwise, set waiting_ = true and call work_started() to keep
98 : the io_context alive
99 :
100 : Locking Protocol
101 : ----------------
102 :
103 : Two mutex levels exist (MUST acquire in this order to avoid deadlock):
104 : 1. signal_state::mutex - protects handler registration and service list
105 : 2. posix_signal_service::mutex_ - protects per-service registration tables
106 :
107 : Async-Signal-Safety
108 : -------------------
109 :
110 : The C signal handler (corosio_posix_signal_handler) performs only
111 : async-signal-safe operations: it reads the single global write_fd and
112 : calls write(), saving/restoring errno. It never locks a mutex, allocates
113 : memory, or dispatches through the scheduler. All of that happens in
114 : deliver_signal(), which runs in normal thread context from the backend
115 : event loop after draining the self-pipe. There is therefore no
116 : self-deadlock risk if a signal arrives while a thread holds state->mutex
117 : or service->mutex_.
118 :
119 : Flag Handling
120 : -------------
121 :
122 : - Flags are abstract values in the public API (signal_set::flags_t)
123 : - flags_supported() validates that requested flags are available on
124 : this platform; returns false if SA_NOCLDWAIT is unavailable and
125 : no_child_wait is requested
126 : - to_sigaction_flags() maps validated flags to actual SA_* constants
127 : - First registration of a signal establishes the flags; subsequent
128 : registrations must be compatible (same flags or dont_care)
129 : - Requesting unavailable flags returns operation_not_supported
130 :
131 : Work Tracking
132 : -------------
133 :
134 : When waiting for a signal:
135 : - start_wait() calls sched_->work_started() to prevent io_context::run()
136 : from returning while we wait
137 : - signal_op::svc is set to point to the service
138 : - signal_op::operator()() calls work_finished() after resuming the coroutine
139 :
140 : If a signal was already queued (undelivered > 0), no work tracking is needed
141 : because completion is posted immediately.
142 : */
143 :
144 : namespace boost::corosio {
145 :
146 : namespace detail {
147 :
148 : /** Signal service for POSIX backends.
149 :
150 : Manages signal registrations via sigaction() and dispatches signal
151 : completions through the scheduler. One instance per execution_context.
152 : */
153 : class BOOST_COROSIO_DECL posix_signal_service final
154 : : public capy::execution_context::service
155 : , public io_object::io_service
156 : {
157 : public:
158 : using key_type = posix_signal_service;
159 :
160 : posix_signal_service(capy::execution_context& ctx, scheduler& sched);
161 : ~posix_signal_service() override;
162 :
163 : posix_signal_service(posix_signal_service const&) = delete;
164 : posix_signal_service& operator=(posix_signal_service const&) = delete;
165 :
166 : io_object::implementation* construct() override;
167 :
168 HIT 141 : void destroy(io_object::implementation* p) override
169 : {
170 141 : auto& impl = static_cast<posix_signal&>(*p);
171 141 : [[maybe_unused]] auto n = impl.clear();
172 141 : impl.cancel();
173 141 : destroy_impl(impl);
174 141 : }
175 :
176 : /** Shut down the service.
177 :
178 : Destroys every implementation the service still owns and gives
179 : each of their registrations back to the process-global table.
180 : */
181 : void shutdown() override;
182 :
183 : void destroy_impl(posix_signal& impl);
184 :
185 : std::error_code add_signal(
186 : posix_signal& impl, int signal_number, signal_set::flags_t flags);
187 :
188 : std::error_code remove_signal(posix_signal& impl, int signal_number);
189 :
190 : std::error_code clear_signals(posix_signal& impl);
191 :
192 : void cancel_wait(posix_signal& impl);
193 : void start_wait(posix_signal& impl, signal_op* op);
194 :
195 : static void deliver_signal(int signal_number);
196 :
197 : void work_started() noexcept;
198 : void work_finished() noexcept;
199 : void post(signal_op* op);
200 :
201 : private:
202 : static void add_service(posix_signal_service* service);
203 : static void remove_service(posix_signal_service* service);
204 :
205 : scheduler* sched_;
206 : std::mutex mutex_;
207 :
208 : // Registers the signal self-pipe's read end with sched_ exactly once per
209 : // service, so every io_context that waits on a signal can drain the pipe.
210 : // A once_flag (not a bool under mutex_) because registration must run
211 : // without holding mutex_ or the signal-state mutex — see add_signal.
212 : std::mutex reader_mutex_;
213 : bool reader_registered_ = false;
214 :
215 : intrusive_list<posix_signal> impl_list_;
216 :
217 : // Per-signal registration table
218 : signal_registration* registrations_[max_signal_number];
219 :
220 : // Registration counts for each signal
221 : std::size_t registration_count_[max_signal_number];
222 :
223 : // Linked list of all posix_signal_service services for signal delivery
224 : posix_signal_service* next_ = nullptr;
225 : posix_signal_service* prev_ = nullptr;
226 : };
227 :
228 : /** Get or create the signal service for the given context.
229 :
230 : This function is called by the concrete scheduler during initialization
231 : to create the signal service with a reference to itself.
232 :
233 : @param ctx Reference to the owning execution_context.
234 : @param sched Reference to the scheduler for posting completions.
235 : @return Reference to the signal service.
236 : */
237 : posix_signal_service&
238 : get_signal_service(capy::execution_context& ctx, scheduler& sched);
239 :
240 : } // namespace detail
241 :
242 : } // namespace boost::corosio
243 :
244 : // ---------------------------------------------------------------------------
245 : // Inline implementation
246 : // ---------------------------------------------------------------------------
247 :
248 : namespace boost::corosio {
249 :
250 : namespace detail {
251 :
252 : namespace posix_signal_detail {
253 :
254 : struct signal_state
255 : {
256 : std::mutex mutex;
257 : posix_signal_service* service_list = nullptr;
258 : std::size_t registration_count[max_signal_number] = {};
259 : signal_set::flags_t registered_flags[max_signal_number] = {};
260 :
261 : // Self-pipe used to defer signal delivery out of handler context.
262 : // The C handler writes the signal number to write_fd (async-signal-
263 : // safe); a backend event loop drains read_fd and calls deliver_signal()
264 : // in normal context. Created once (on the first signal registration) and
265 : // kept for the process lifetime. Each posix_signal_service registers the
266 : // read end with its own scheduler (see reader_once_) so every running
267 : // io_context can drain it; multiple readers on one pipe are safe because
268 : // each signal is a fixed sizeof(int) record read atomically.
269 : int read_fd = -1;
270 : int write_fd = -1;
271 : };
272 :
273 : BOOST_COROSIO_DECL signal_state* get_signal_state();
274 :
275 : // Check if requested flags are supported on this platform.
276 : // Returns true if all flags are supported, false otherwise.
277 : inline bool
278 162 : flags_supported([[maybe_unused]] signal_set::flags_t flags)
279 : {
280 : #ifndef SA_NOCLDWAIT
281 : if (flags & signal_set::no_child_wait)
282 : return false;
283 : #endif
284 162 : return true;
285 : }
286 :
287 : // Map abstract flags to sigaction() flags.
288 : // Caller must ensure flags_supported() returns true first.
289 : inline int
290 126 : to_sigaction_flags(signal_set::flags_t flags)
291 : {
292 126 : int sa_flags = 0;
293 126 : if (flags & signal_set::restart)
294 23 : sa_flags |= SA_RESTART;
295 126 : if (flags & signal_set::no_child_stop)
296 1 : sa_flags |= SA_NOCLDSTOP;
297 : #ifdef SA_NOCLDWAIT
298 126 : if (flags & signal_set::no_child_wait)
299 MIS 0 : sa_flags |= SA_NOCLDWAIT;
300 : #endif
301 HIT 126 : if (flags & signal_set::no_defer)
302 4 : sa_flags |= SA_NODEFER;
303 126 : if (flags & signal_set::reset_handler)
304 MIS 0 : sa_flags |= SA_RESETHAND;
305 HIT 126 : return sa_flags;
306 : }
307 :
308 : // Check if two flag values are compatible
309 : inline bool
310 27 : flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
311 : {
312 : // dont_care is always compatible
313 52 : if ((existing & signal_set::dont_care) ||
314 25 : (requested & signal_set::dont_care))
315 7 : return true;
316 :
317 : // Mask out dont_care bit for comparison
318 20 : constexpr auto mask = ~signal_set::dont_care;
319 20 : return (existing & mask) == (requested & mask);
320 : }
321 :
322 : // Lazily create the global signal self-pipe. Idempotent; call under
323 : // state->mutex before installing the first signal handler so write_fd is
324 : // valid by the time the handler can fire. Both ends are non-blocking and
325 : // close-on-exec (mirrors the reactor self-pipe setup in select_scheduler).
326 : // Returns the failing call's errno and leaves the fds at -1 if creation
327 : // fails: an exhausted descriptor table and a rejected fcntl are different
328 : // problems to the caller of add().
329 : [[nodiscard]] inline std::error_code
330 162 : open_signal_pipe(signal_state* state)
331 : {
332 162 : if (state->read_fd >= 0)
333 148 : return {};
334 :
335 : int fds[2];
336 14 : if (::pipe(fds) < 0)
337 1 : return make_err(errno);
338 :
339 30 : for (int i = 0; i < 2; ++i)
340 : {
341 23 : int fl = ::fcntl(fds[i], F_GETFL, 0);
342 42 : if (fl == -1 || ::fcntl(fds[i], F_SETFL, fl | O_NONBLOCK) == -1 ||
343 19 : ::fcntl(fds[i], F_SETFD, FD_CLOEXEC) == -1)
344 : {
345 6 : auto ec = make_err(errno);
346 6 : ::close(fds[0]);
347 6 : ::close(fds[1]);
348 6 : return ec;
349 : }
350 : }
351 :
352 7 : state->read_fd = fds[0];
353 7 : state->write_fd = fds[1];
354 7 : return {};
355 : }
356 :
357 : // C signal handler. Async-signal-safe: it touches only the single global
358 : // write_fd (an int set before any handler is installed) and calls write(),
359 : // which POSIX lists as async-signal-safe. errno is saved and restored so an
360 : // interrupted foreground syscall is unaffected. A full pipe (write returns
361 : // EAGAIN) or a short write is intentionally dropped — the reactor still
362 : // coalesces because deliver_signal reports the signal to every waiting set.
363 : inline void
364 306 : corosio_posix_signal_handler(int signal_number)
365 : {
366 306 : int saved_errno = errno;
367 306 : signal_state* state = get_signal_state();
368 : [[maybe_unused]] ssize_t r =
369 306 : ::write(state->write_fd, &signal_number, sizeof(int));
370 306 : errno = saved_errno;
371 : // With sigaction(), the handler persists automatically (unlike some
372 : // signal() implementations that reset to SIG_DFL).
373 306 : }
374 :
375 : // Drain the signal self-pipe and deliver each pending signal. Runs in normal
376 : // thread context from the backend event loop, so deliver_signal()'s mutex
377 : // locking and scheduler post are safe here. Reads until EAGAIN (edge-
378 : // triggered backends require a full drain per readiness event).
379 : inline void
380 306 : drain_signal_pipe()
381 : {
382 306 : signal_state* state = get_signal_state();
383 : int signal_number;
384 612 : while (::read(state->read_fd, &signal_number, sizeof(int)) ==
385 : static_cast<ssize_t>(sizeof(int)))
386 : {
387 306 : posix_signal_service::deliver_signal(signal_number);
388 : }
389 306 : }
390 :
391 : } // namespace posix_signal_detail
392 :
393 : // signal_op implementation
394 :
395 : inline void
396 308 : signal_op::operator()()
397 : {
398 308 : if (ec_out)
399 308 : *ec_out = {};
400 308 : if (signal_out)
401 308 : *signal_out = signal_number;
402 :
403 : // Capture svc before resuming (coro may destroy us)
404 308 : auto* service = svc;
405 308 : svc = nullptr;
406 :
407 308 : cont.h = h;
408 308 : d.post(cont);
409 :
410 : // Balance the work_started() from start_wait
411 308 : if (service)
412 308 : service->work_finished();
413 308 : }
414 :
415 : inline void
416 MIS 0 : signal_op::destroy()
417 : {
418 : // No-op: signal_op is embedded in posix_signal
419 0 : }
420 :
421 : // posix_signal implementation
422 :
423 HIT 147 : inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
424 147 : : svc_(svc)
425 : {
426 147 : }
427 :
428 : inline std::coroutine_handle<>
429 323 : posix_signal::wait(
430 : std::coroutine_handle<> h,
431 : capy::executor_ref d,
432 : std::stop_token token,
433 : std::error_code* ec,
434 : int* signal_out)
435 : {
436 323 : pending_op_.h = h;
437 323 : pending_op_.d = d;
438 323 : pending_op_.ec_out = ec;
439 323 : pending_op_.signal_out = signal_out;
440 323 : pending_op_.signal_number = 0;
441 :
442 323 : if (token.stop_requested())
443 : {
444 2 : if (ec)
445 2 : *ec = make_error_code(capy::error::canceled);
446 2 : if (signal_out)
447 2 : *signal_out = 0;
448 2 : pending_op_.cont.h = h;
449 2 : d.post(pending_op_.cont);
450 : // completion is always posted to scheduler queue, never inline.
451 2 : return std::noop_coroutine();
452 : }
453 :
454 321 : svc_.start_wait(*this, &pending_op_);
455 : // completion is always posted to scheduler queue, never inline.
456 321 : return std::noop_coroutine();
457 : }
458 :
459 : inline std::error_code
460 166 : posix_signal::add(int signal_number, signal_set::flags_t flags)
461 : {
462 166 : return svc_.add_signal(*this, signal_number, flags);
463 : }
464 :
465 : inline std::error_code
466 10 : posix_signal::remove(int signal_number)
467 : {
468 10 : return svc_.remove_signal(*this, signal_number);
469 : }
470 :
471 : inline std::error_code
472 151 : posix_signal::clear()
473 : {
474 151 : return svc_.clear_signals(*this);
475 : }
476 :
477 : inline void
478 156 : posix_signal::cancel() noexcept
479 : {
480 156 : svc_.cancel_wait(*this);
481 156 : }
482 :
483 : // posix_signal_service implementation
484 :
485 1790 : inline posix_signal_service::posix_signal_service(
486 1790 : capy::execution_context&, scheduler& sched)
487 1790 : : sched_(&sched)
488 : {
489 116350 : for (int i = 0; i < max_signal_number; ++i)
490 : {
491 114560 : registrations_[i] = nullptr;
492 114560 : registration_count_[i] = 0;
493 : }
494 1790 : add_service(this);
495 1790 : }
496 :
497 3580 : inline posix_signal_service::~posix_signal_service()
498 : {
499 1790 : remove_service(this);
500 3580 : }
501 :
502 : inline void
503 1790 : posix_signal_service::shutdown()
504 : {
505 : posix_signal_detail::signal_state* state =
506 1790 : posix_signal_detail::get_signal_state();
507 1790 : std::lock_guard state_lock(state->mutex);
508 1790 : std::lock_guard lock(mutex_);
509 :
510 1796 : for (auto* impl = impl_list_.pop_front(); impl != nullptr;
511 6 : impl = impl_list_.pop_front())
512 : {
513 12 : while (auto* reg = impl->signals_)
514 : {
515 6 : int const signal_number = reg->signal_number;
516 :
517 : // The registration table outlives every io_context, so a set
518 : // still registered here has to give its count and disposition
519 : // back the way clear() would: otherwise the signal stays
520 : // installed with these flags and the next add() of it is
521 : // refused. The per-node table unlink clear() also does is
522 : // skipped in favour of the wholesale null-out below.
523 6 : if (state->registration_count[signal_number] == 1)
524 : {
525 4 : struct sigaction sa = {};
526 4 : sa.sa_handler = SIG_DFL;
527 4 : sigemptyset(&sa.sa_mask);
528 4 : sa.sa_flags = 0;
529 4 : std::ignore = ::sigaction(signal_number, &sa, nullptr);
530 4 : state->registered_flags[signal_number] = signal_set::none;
531 : }
532 :
533 6 : --state->registration_count[signal_number];
534 6 : --registration_count_[signal_number];
535 :
536 6 : impl->signals_ = reg->next_in_set;
537 6 : delete reg;
538 6 : }
539 6 : delete impl;
540 : }
541 :
542 : // Every live registration hung off an implementation in impl_list_,
543 : // so the whole table goes stale at once and can be dropped wholesale
544 : // rather than node by node. It has to be dropped: deliver_signal()
545 : // walks this service until the destructor unlinks it from the global
546 : // list.
547 116350 : for (int i = 0; i < max_signal_number; ++i)
548 114560 : registrations_[i] = nullptr;
549 1790 : }
550 :
551 : inline io_object::implementation*
552 147 : posix_signal_service::construct()
553 : {
554 147 : auto* impl = new posix_signal(*this);
555 :
556 : {
557 147 : std::lock_guard lock(mutex_);
558 147 : impl_list_.push_back(impl);
559 147 : }
560 :
561 147 : return impl;
562 : }
563 :
564 : inline void
565 141 : posix_signal_service::destroy_impl(posix_signal& impl)
566 : {
567 : {
568 141 : std::lock_guard lock(mutex_);
569 141 : impl_list_.remove(&impl);
570 141 : }
571 :
572 141 : delete &impl;
573 141 : }
574 :
575 : inline std::error_code
576 166 : posix_signal_service::add_signal(
577 : posix_signal& impl, int signal_number, signal_set::flags_t flags)
578 : {
579 166 : if (signal_number < 0 || signal_number >= max_signal_number)
580 4 : return make_error_code(std::errc::invalid_argument);
581 :
582 : // Validate that requested flags are supported on this platform
583 : // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
584 162 : if (!posix_signal_detail::flags_supported(flags))
585 MIS 0 : return make_error_code(std::errc::operation_not_supported);
586 :
587 : posix_signal_detail::signal_state* state =
588 HIT 162 : posix_signal_detail::get_signal_state();
589 :
590 : // Ensure the global self-pipe exists and this service's scheduler is
591 : // watching its read end, BEFORE taking the registration locks. The
592 : // reactor drain path locks the descriptor mutex and then the signal-state
593 : // and service mutexes; register_signal_reader locks the descriptor mutex
594 : // (via register_descriptor), so it must run holding neither of those or
595 : // the lock order would invert (a real deadlock, caught by TSan). call_once
596 : // makes the once-per-service registration safe when two signal_sets on
597 : // this context race add() from different threads.
598 : {
599 162 : std::lock_guard state_lock(state->mutex);
600 162 : if (auto ec = posix_signal_detail::open_signal_pipe(state))
601 7 : return ec;
602 162 : }
603 : {
604 : // Success-latched so a failed environmental registration
605 : // (epoll_ctl ENOMEM/ENOSPC) is retried by the next add()
606 : // instead of being lost; the code travels the return channel.
607 155 : std::lock_guard reg_lock(reader_mutex_);
608 155 : if (!reader_registered_)
609 : {
610 108 : if (auto ec = sched_->register_signal_reader(state->read_fd))
611 2 : return ec;
612 106 : reader_registered_ = true;
613 : }
614 155 : }
615 :
616 153 : std::lock_guard state_lock(state->mutex);
617 153 : std::lock_guard lock(mutex_);
618 :
619 : // Find insertion point (list is sorted by signal number)
620 153 : signal_registration** insertion_point = &impl.signals_;
621 153 : signal_registration* reg = impl.signals_;
622 174 : while (reg && reg->signal_number < signal_number)
623 : {
624 21 : insertion_point = ®->next_in_set;
625 21 : reg = reg->next_in_set;
626 : }
627 :
628 : // Already registered in this set - check flag compatibility
629 : // (same signal_set adding same signal twice with different flags)
630 153 : if (reg && reg->signal_number == signal_number)
631 : {
632 13 : if (!posix_signal_detail::flags_compatible(reg->flags, flags))
633 4 : return make_error_code(std::errc::invalid_argument);
634 9 : return {};
635 : }
636 :
637 : // Check flag compatibility with global registration
638 : // (different signal_set already registered this signal with different flags)
639 140 : if (state->registration_count[signal_number] > 0)
640 : {
641 14 : if (!posix_signal_detail::flags_compatible(
642 : state->registered_flags[signal_number], flags))
643 2 : return make_error_code(std::errc::invalid_argument);
644 : }
645 :
646 138 : auto* new_reg = new signal_registration;
647 138 : new_reg->signal_number = signal_number;
648 138 : new_reg->flags = flags;
649 138 : new_reg->owner = &impl;
650 138 : new_reg->undelivered = 0;
651 :
652 : // Install signal handler on first global registration
653 138 : if (state->registration_count[signal_number] == 0)
654 : {
655 126 : struct sigaction sa = {};
656 126 : sa.sa_handler = posix_signal_detail::corosio_posix_signal_handler;
657 126 : sigemptyset(&sa.sa_mask);
658 126 : sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
659 :
660 126 : if (::sigaction(signal_number, &sa, nullptr) < 0)
661 : {
662 1 : delete new_reg;
663 1 : return make_error_code(std::errc::invalid_argument);
664 : }
665 :
666 : // Store the flags used for first registration
667 125 : state->registered_flags[signal_number] = flags;
668 : }
669 :
670 137 : new_reg->next_in_set = reg;
671 137 : *insertion_point = new_reg;
672 :
673 137 : new_reg->next_in_table = registrations_[signal_number];
674 137 : new_reg->prev_in_table = nullptr;
675 137 : if (registrations_[signal_number])
676 10 : registrations_[signal_number]->prev_in_table = new_reg;
677 137 : registrations_[signal_number] = new_reg;
678 :
679 137 : ++state->registration_count[signal_number];
680 137 : ++registration_count_[signal_number];
681 :
682 137 : return {};
683 153 : }
684 :
685 : inline std::error_code
686 10 : posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
687 : {
688 10 : if (signal_number < 0 || signal_number >= max_signal_number)
689 2 : return make_error_code(std::errc::invalid_argument);
690 :
691 : posix_signal_detail::signal_state* state =
692 8 : posix_signal_detail::get_signal_state();
693 8 : std::lock_guard state_lock(state->mutex);
694 8 : std::lock_guard lock(mutex_);
695 :
696 8 : signal_registration** deletion_point = &impl.signals_;
697 8 : signal_registration* reg = impl.signals_;
698 8 : while (reg && reg->signal_number < signal_number)
699 : {
700 MIS 0 : deletion_point = ®->next_in_set;
701 0 : reg = reg->next_in_set;
702 : }
703 :
704 HIT 8 : if (!reg || reg->signal_number != signal_number)
705 3 : return {};
706 :
707 : // Restore default handler on last global unregistration
708 5 : if (state->registration_count[signal_number] == 1)
709 : {
710 5 : struct sigaction sa = {};
711 5 : sa.sa_handler = SIG_DFL;
712 5 : sigemptyset(&sa.sa_mask);
713 5 : sa.sa_flags = 0;
714 :
715 5 : if (::sigaction(signal_number, &sa, nullptr) < 0)
716 1 : return make_error_code(std::errc::invalid_argument);
717 :
718 : // Clear stored flags
719 4 : state->registered_flags[signal_number] = signal_set::none;
720 : }
721 :
722 4 : *deletion_point = reg->next_in_set;
723 :
724 4 : if (registrations_[signal_number] == reg)
725 4 : registrations_[signal_number] = reg->next_in_table;
726 4 : if (reg->prev_in_table)
727 MIS 0 : reg->prev_in_table->next_in_table = reg->next_in_table;
728 HIT 4 : if (reg->next_in_table)
729 MIS 0 : reg->next_in_table->prev_in_table = reg->prev_in_table;
730 :
731 HIT 4 : --state->registration_count[signal_number];
732 4 : --registration_count_[signal_number];
733 :
734 4 : delete reg;
735 4 : return {};
736 8 : }
737 :
738 : inline std::error_code
739 151 : posix_signal_service::clear_signals(posix_signal& impl)
740 : {
741 : posix_signal_detail::signal_state* state =
742 151 : posix_signal_detail::get_signal_state();
743 151 : std::lock_guard state_lock(state->mutex);
744 151 : std::lock_guard lock(mutex_);
745 :
746 151 : std::error_code first_error;
747 :
748 278 : while (signal_registration* reg = impl.signals_)
749 : {
750 127 : int signal_number = reg->signal_number;
751 :
752 127 : if (state->registration_count[signal_number] == 1)
753 : {
754 117 : struct sigaction sa = {};
755 117 : sa.sa_handler = SIG_DFL;
756 117 : sigemptyset(&sa.sa_mask);
757 117 : sa.sa_flags = 0;
758 :
759 117 : if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
760 1 : first_error = make_error_code(std::errc::invalid_argument);
761 :
762 : // Clear stored flags
763 117 : state->registered_flags[signal_number] = signal_set::none;
764 : }
765 :
766 127 : impl.signals_ = reg->next_in_set;
767 :
768 127 : if (registrations_[signal_number] == reg)
769 127 : registrations_[signal_number] = reg->next_in_table;
770 127 : if (reg->prev_in_table)
771 MIS 0 : reg->prev_in_table->next_in_table = reg->next_in_table;
772 HIT 127 : if (reg->next_in_table)
773 10 : reg->next_in_table->prev_in_table = reg->prev_in_table;
774 :
775 127 : --state->registration_count[signal_number];
776 127 : --registration_count_[signal_number];
777 :
778 127 : delete reg;
779 127 : }
780 :
781 151 : if (first_error)
782 1 : return first_error;
783 150 : return {};
784 151 : }
785 :
786 : inline void
787 156 : posix_signal_service::cancel_wait(posix_signal& impl)
788 : {
789 156 : bool was_waiting = false;
790 156 : signal_op* op = nullptr;
791 :
792 : {
793 156 : std::lock_guard lock(mutex_);
794 156 : impl.cancelled_ = true;
795 156 : if (impl.waiting_)
796 : {
797 5 : was_waiting = true;
798 5 : impl.waiting_ = false;
799 5 : op = &impl.pending_op_;
800 : }
801 156 : }
802 :
803 156 : if (was_waiting)
804 : {
805 5 : if (op->ec_out)
806 5 : *op->ec_out = make_error_code(capy::error::canceled);
807 5 : if (op->signal_out)
808 5 : *op->signal_out = 0;
809 5 : op->cont.h = op->h;
810 5 : op->d.post(op->cont);
811 5 : sched_->work_finished();
812 : }
813 156 : }
814 :
815 : inline void
816 321 : posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
817 : {
818 : {
819 321 : std::lock_guard lock(mutex_);
820 :
821 : // Check if cancel() was called before this wait started
822 321 : if (impl.cancelled_)
823 : {
824 2 : impl.cancelled_ = false;
825 2 : if (op->ec_out)
826 2 : *op->ec_out = make_error_code(capy::error::canceled);
827 2 : if (op->signal_out)
828 2 : *op->signal_out = 0;
829 2 : op->cont.h = op->h;
830 2 : op->d.post(op->cont);
831 2 : return;
832 : }
833 :
834 : // Check for queued signals first (signal arrived before wait started)
835 319 : signal_registration* reg = impl.signals_;
836 642 : while (reg)
837 : {
838 323 : if (reg->undelivered > 0)
839 : {
840 MIS 0 : --reg->undelivered;
841 0 : op->signal_number = reg->signal_number;
842 : // svc=nullptr: no work_finished needed since we never called work_started
843 0 : op->svc = nullptr;
844 0 : sched_->post(op);
845 0 : return;
846 : }
847 HIT 323 : reg = reg->next_in_set;
848 : }
849 :
850 : // No queued signals - wait for delivery
851 319 : impl.waiting_ = true;
852 : // svc=this: signal_op::operator() will call work_finished() to balance this
853 319 : op->svc = this;
854 319 : sched_->work_started();
855 321 : }
856 : }
857 :
858 : inline void
859 306 : posix_signal_service::deliver_signal(int signal_number)
860 : {
861 306 : if (signal_number < 0 || signal_number >= max_signal_number)
862 MIS 0 : return;
863 :
864 : posix_signal_detail::signal_state* state =
865 HIT 306 : posix_signal_detail::get_signal_state();
866 306 : std::lock_guard lock(state->mutex);
867 :
868 306 : posix_signal_service* service = state->service_list;
869 612 : while (service)
870 : {
871 306 : std::lock_guard svc_lock(service->mutex_);
872 :
873 306 : signal_registration* reg = service->registrations_[signal_number];
874 614 : while (reg)
875 : {
876 308 : posix_signal* impl = static_cast<posix_signal*>(reg->owner);
877 :
878 308 : if (impl->waiting_)
879 : {
880 308 : impl->waiting_ = false;
881 308 : impl->pending_op_.signal_number = signal_number;
882 308 : service->post(&impl->pending_op_);
883 : }
884 : else
885 : {
886 MIS 0 : ++reg->undelivered;
887 : }
888 :
889 HIT 308 : reg = reg->next_in_table;
890 : }
891 :
892 306 : service = service->next_;
893 306 : }
894 306 : }
895 :
896 : inline void
897 : posix_signal_service::work_started() noexcept
898 : {
899 : sched_->work_started();
900 : }
901 :
902 : inline void
903 308 : posix_signal_service::work_finished() noexcept
904 : {
905 308 : sched_->work_finished();
906 308 : }
907 :
908 : inline void
909 308 : posix_signal_service::post(signal_op* op)
910 : {
911 308 : sched_->post(op);
912 308 : }
913 :
914 : inline void
915 1790 : posix_signal_service::add_service(posix_signal_service* service)
916 : {
917 : posix_signal_detail::signal_state* state =
918 1790 : posix_signal_detail::get_signal_state();
919 1790 : std::lock_guard lock(state->mutex);
920 :
921 1790 : service->next_ = state->service_list;
922 1790 : service->prev_ = nullptr;
923 1790 : if (state->service_list)
924 7 : state->service_list->prev_ = service;
925 1790 : state->service_list = service;
926 1790 : }
927 :
928 : inline void
929 1790 : posix_signal_service::remove_service(posix_signal_service* service)
930 : {
931 : posix_signal_detail::signal_state* state =
932 1790 : posix_signal_detail::get_signal_state();
933 1790 : std::lock_guard lock(state->mutex);
934 :
935 1790 : if (service->next_ || service->prev_ || state->service_list == service)
936 : {
937 1790 : if (state->service_list == service)
938 1790 : state->service_list = service->next_;
939 1790 : if (service->prev_)
940 MIS 0 : service->prev_->next_ = service->next_;
941 HIT 1790 : if (service->next_)
942 7 : service->next_->prev_ = service->prev_;
943 1790 : service->next_ = nullptr;
944 1790 : service->prev_ = nullptr;
945 : }
946 1790 : }
947 :
948 : // get_signal_service - factory function
949 :
950 : inline posix_signal_service&
951 1790 : get_signal_service(capy::execution_context& ctx, scheduler& sched)
952 : {
953 1790 : return ctx.make_service<posix_signal_service>(sched);
954 : }
955 :
956 : } // namespace detail
957 : } // namespace boost::corosio
958 :
959 : #endif // BOOST_COROSIO_POSIX
960 :
961 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
|