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 184 : void destroy(io_object::implementation* p) override
169 : {
170 184 : auto& impl = static_cast<posix_signal&>(*p);
171 184 : [[maybe_unused]] auto n = impl.clear();
172 184 : impl.disarm_stop();
173 184 : impl.cancel();
174 184 : destroy_impl(impl);
175 184 : }
176 :
177 : /** Shut down the service.
178 :
179 : Destroys every implementation the service still owns and gives
180 : each of their registrations back to the process-global table.
181 : */
182 : void shutdown() override;
183 :
184 : void destroy_impl(posix_signal& impl);
185 :
186 : std::error_code add_signal(
187 : posix_signal& impl, int signal_number, signal_set::flags_t flags);
188 :
189 : std::error_code remove_signal(posix_signal& impl, int signal_number);
190 :
191 : std::error_code clear_signals(posix_signal& impl);
192 :
193 : void cancel_wait(posix_signal& impl);
194 : void start_wait(posix_signal& impl, signal_op* op);
195 :
196 : /** Cancel an in-flight wait on behalf of a stop token.
197 :
198 : Identical to @ref cancel_wait except that it does not set the
199 : sticky `cancelled_` latch: a stop token scopes to one operation,
200 : so a request arriving after the wait completed must do nothing.
201 : */
202 : void cancel_wait_token(posix_signal& impl) noexcept;
203 :
204 : /** Clear the per-operation stop flag before a new wait arms.
205 :
206 : Lives here rather than on the implementation because `mutex_` is
207 : the service's; the service is a friend of `posix_signal`, not the
208 : reverse.
209 : */
210 1023 : void reset_token_cancel(posix_signal& impl) noexcept
211 : {
212 1023 : std::lock_guard lock(mutex_);
213 1023 : impl.token_cancelled_ = false;
214 1023 : }
215 :
216 : static void deliver_signal(int signal_number);
217 :
218 : void work_started() noexcept;
219 : void work_finished() noexcept;
220 : void post(signal_op* op);
221 :
222 : private:
223 : static void add_service(posix_signal_service* service);
224 : static void remove_service(posix_signal_service* service);
225 :
226 : scheduler* sched_;
227 : std::mutex mutex_;
228 :
229 : // Registers the signal self-pipe's read end with sched_ exactly once per
230 : // service, so every io_context that waits on a signal can drain the pipe.
231 : // A once_flag (not a bool under mutex_) because registration must run
232 : // without holding mutex_ or the signal-state mutex — see add_signal.
233 : std::mutex reader_mutex_;
234 : bool reader_registered_ = false;
235 :
236 : intrusive_list<posix_signal> impl_list_;
237 :
238 : // Per-signal registration table
239 : signal_registration* registrations_[max_signal_number];
240 :
241 : // Registration counts for each signal
242 : std::size_t registration_count_[max_signal_number];
243 :
244 : // Linked list of all posix_signal_service services for signal delivery
245 : posix_signal_service* next_ = nullptr;
246 : posix_signal_service* prev_ = nullptr;
247 : };
248 :
249 : /** Get or create the signal service for the given context.
250 :
251 : This function is called by the concrete scheduler during initialization
252 : to create the signal service with a reference to itself.
253 :
254 : @param ctx Reference to the owning execution_context.
255 : @param sched Reference to the scheduler for posting completions.
256 : @return Reference to the signal service.
257 : */
258 : posix_signal_service&
259 : get_signal_service(capy::execution_context& ctx, scheduler& sched);
260 :
261 : } // namespace detail
262 :
263 : } // namespace boost::corosio
264 :
265 : // ---------------------------------------------------------------------------
266 : // Inline implementation
267 : // ---------------------------------------------------------------------------
268 :
269 : namespace boost::corosio {
270 :
271 : namespace detail {
272 :
273 : namespace posix_signal_detail {
274 :
275 : struct signal_state
276 : {
277 : std::mutex mutex;
278 : posix_signal_service* service_list = nullptr;
279 : std::size_t registration_count[max_signal_number] = {};
280 : signal_set::flags_t registered_flags[max_signal_number] = {};
281 :
282 : // Self-pipe used to defer signal delivery out of handler context.
283 : // The C handler writes the signal number to write_fd (async-signal-
284 : // safe); a backend event loop drains read_fd and calls deliver_signal()
285 : // in normal context. Created once (on the first signal registration) and
286 : // kept for the process lifetime. Each posix_signal_service registers the
287 : // read end with its own scheduler (see reader_once_) so every running
288 : // io_context can drain it; multiple readers on one pipe are safe because
289 : // each signal is a fixed sizeof(int) record read atomically.
290 : int read_fd = -1;
291 : int write_fd = -1;
292 : };
293 :
294 : BOOST_COROSIO_DECL signal_state* get_signal_state();
295 :
296 : // Check if requested flags are supported on this platform.
297 : // Returns true if all flags are supported, false otherwise.
298 : inline bool
299 207 : flags_supported([[maybe_unused]] signal_set::flags_t flags)
300 : {
301 : #ifndef SA_NOCLDWAIT
302 : if (flags & signal_set::no_child_wait)
303 : return false;
304 : #endif
305 207 : return true;
306 : }
307 :
308 : // Map abstract flags to sigaction() flags.
309 : // Caller must ensure flags_supported() returns true first.
310 : inline int
311 159 : to_sigaction_flags(signal_set::flags_t flags)
312 : {
313 159 : int sa_flags = 0;
314 159 : if (flags & signal_set::restart)
315 23 : sa_flags |= SA_RESTART;
316 159 : if (flags & signal_set::no_child_stop)
317 3 : sa_flags |= SA_NOCLDSTOP;
318 : #ifdef SA_NOCLDWAIT
319 159 : if (flags & signal_set::no_child_wait)
320 2 : sa_flags |= SA_NOCLDWAIT;
321 : #endif
322 159 : if (flags & signal_set::no_defer)
323 4 : sa_flags |= SA_NODEFER;
324 159 : if (flags & signal_set::reset_handler)
325 2 : sa_flags |= SA_RESETHAND;
326 159 : return sa_flags;
327 : }
328 :
329 : // Check if two flag values are compatible
330 : inline bool
331 39 : flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
332 : {
333 : // dont_care is always compatible
334 76 : if ((existing & signal_set::dont_care) ||
335 37 : (requested & signal_set::dont_care))
336 7 : return true;
337 :
338 : // Mask out dont_care bit for comparison
339 32 : constexpr auto mask = ~signal_set::dont_care;
340 32 : return (existing & mask) == (requested & mask);
341 : }
342 :
343 : // Lazily create the global signal self-pipe. Idempotent; call under
344 : // state->mutex before installing the first signal handler so write_fd is
345 : // valid by the time the handler can fire. Both ends are non-blocking and
346 : // close-on-exec (mirrors the reactor self-pipe setup in select_scheduler).
347 : // Returns the failing call's errno and leaves the fds at -1 if creation
348 : // fails: an exhausted descriptor table and a rejected fcntl are different
349 : // problems to the caller of add().
350 : [[nodiscard]] inline std::error_code
351 207 : open_signal_pipe(signal_state* state)
352 : {
353 207 : if (state->read_fd >= 0)
354 193 : return {};
355 :
356 : int fds[2];
357 14 : if (::pipe(fds) < 0)
358 1 : return make_err(errno);
359 :
360 30 : for (int i = 0; i < 2; ++i)
361 : {
362 23 : int fl = ::fcntl(fds[i], F_GETFL, 0);
363 42 : if (fl == -1 || ::fcntl(fds[i], F_SETFL, fl | O_NONBLOCK) == -1 ||
364 19 : ::fcntl(fds[i], F_SETFD, FD_CLOEXEC) == -1)
365 : {
366 6 : auto ec = make_err(errno);
367 6 : ::close(fds[0]);
368 6 : ::close(fds[1]);
369 6 : return ec;
370 : }
371 : }
372 :
373 7 : state->read_fd = fds[0];
374 7 : state->write_fd = fds[1];
375 7 : return {};
376 : }
377 :
378 : // C signal handler. Async-signal-safe: it touches only the single global
379 : // write_fd (an int set before any handler is installed) and calls write(),
380 : // which POSIX lists as async-signal-safe. errno is saved and restored so an
381 : // interrupted foreground syscall is unaffected. A full pipe (write returns
382 : // EAGAIN) or a short write is intentionally dropped — the reactor still
383 : // coalesces because deliver_signal reports the signal to every waiting set.
384 : inline void
385 317 : corosio_posix_signal_handler(int signal_number)
386 : {
387 317 : int saved_errno = errno;
388 317 : signal_state* state = get_signal_state();
389 : [[maybe_unused]] ssize_t r =
390 317 : ::write(state->write_fd, &signal_number, sizeof(int));
391 317 : errno = saved_errno;
392 : // With sigaction(), the handler persists automatically (unlike some
393 : // signal() implementations that reset to SIG_DFL).
394 317 : }
395 :
396 : // Drain the signal self-pipe and deliver each pending signal. Runs in normal
397 : // thread context from the backend event loop, so deliver_signal()'s mutex
398 : // locking and scheduler post are safe here. Reads until EAGAIN (edge-
399 : // triggered backends require a full drain per readiness event).
400 : inline void
401 317 : drain_signal_pipe()
402 : {
403 317 : signal_state* state = get_signal_state();
404 : int signal_number;
405 634 : while (::read(state->read_fd, &signal_number, sizeof(int)) ==
406 : static_cast<ssize_t>(sizeof(int)))
407 : {
408 317 : posix_signal_service::deliver_signal(signal_number);
409 : }
410 317 : }
411 :
412 : } // namespace posix_signal_detail
413 :
414 : // signal_op implementation
415 :
416 : inline void
417 321 : signal_op::operator()()
418 : {
419 321 : if (ec_out)
420 321 : *ec_out = {};
421 321 : if (signal_out)
422 321 : *signal_out = signal_number;
423 :
424 : // Capture svc before resuming (coro may destroy us)
425 321 : auto* service = svc;
426 321 : svc = nullptr;
427 :
428 321 : cont.h = h;
429 321 : d.post(cont);
430 :
431 : // Balance the work_started() from start_wait
432 321 : if (service)
433 319 : service->work_finished();
434 321 : }
435 :
436 : inline void
437 MIS 0 : signal_op::destroy()
438 : {
439 : // No-op: signal_op is embedded in posix_signal
440 0 : }
441 :
442 : // posix_signal implementation
443 :
444 HIT 190 : inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
445 190 : : svc_(svc)
446 : {
447 190 : }
448 :
449 : inline std::coroutine_handle<>
450 1113 : posix_signal::wait(
451 : std::coroutine_handle<> h,
452 : capy::executor_ref d,
453 : std::stop_token token,
454 : std::error_code* ec,
455 : int* signal_out)
456 : {
457 1113 : pending_op_.h = h;
458 1113 : pending_op_.d = d;
459 1113 : pending_op_.ec_out = ec;
460 1113 : pending_op_.signal_out = signal_out;
461 1113 : pending_op_.signal_number = 0;
462 :
463 : // Disarm any callback left over from a previous wait before doing
464 : // anything else, including the early return below: otherwise that
465 : // path leaves this object owning a callback it no longer uses.
466 : // Outside start_wait's lock on purpose: ~stop_callback blocks until a
467 : // concurrently running callback returns, and that callback takes
468 : // posix_signal_service::mutex_.
469 1113 : stop_cb_.reset();
470 :
471 1113 : if (token.stop_requested())
472 : {
473 90 : if (ec)
474 90 : *ec = make_error_code(capy::error::canceled);
475 90 : if (signal_out)
476 90 : *signal_out = 0;
477 90 : pending_op_.cont.h = h;
478 90 : d.post(pending_op_.cont);
479 : // completion is always posted to scheduler queue, never inline.
480 90 : return std::noop_coroutine();
481 : }
482 :
483 : // Clearing the flag before arming is load-bearing: reset_token_cancel
484 : // must run immediately before emplace, not before the early return
485 : // above.
486 1023 : svc_.reset_token_cancel(*this);
487 1023 : if (token.stop_possible())
488 693 : stop_cb_.emplace(token, token_canceller{this});
489 :
490 1023 : svc_.start_wait(*this, &pending_op_);
491 : // completion is always posted to scheduler queue, never inline.
492 1023 : return std::noop_coroutine();
493 : }
494 :
495 : inline std::error_code
496 211 : posix_signal::add(int signal_number, signal_set::flags_t flags)
497 : {
498 211 : return svc_.add_signal(*this, signal_number, flags);
499 : }
500 :
501 : inline std::error_code
502 26 : posix_signal::remove(int signal_number)
503 : {
504 26 : return svc_.remove_signal(*this, signal_number);
505 : }
506 :
507 : inline std::error_code
508 198 : posix_signal::clear()
509 : {
510 198 : return svc_.clear_signals(*this);
511 : }
512 :
513 : inline void
514 201 : posix_signal::cancel() noexcept
515 : {
516 201 : svc_.cancel_wait(*this);
517 201 : }
518 :
519 : // posix_signal_service implementation
520 :
521 2241 : inline posix_signal_service::posix_signal_service(
522 2241 : capy::execution_context&, scheduler& sched)
523 2241 : : sched_(&sched)
524 : {
525 145665 : for (int i = 0; i < max_signal_number; ++i)
526 : {
527 143424 : registrations_[i] = nullptr;
528 143424 : registration_count_[i] = 0;
529 : }
530 2241 : add_service(this);
531 2241 : }
532 :
533 4482 : inline posix_signal_service::~posix_signal_service()
534 : {
535 2241 : remove_service(this);
536 4482 : }
537 :
538 : inline void
539 2241 : posix_signal_service::shutdown()
540 : {
541 : // Collected under the locks below and deleted after they are released:
542 : // ~posix_signal destroys an armed stop_cb_, and ~stop_callback blocks
543 : // until a concurrently running token_canceller returns -- which takes
544 : // mutex_. Deleting while still holding mutex_ would self-deadlock the
545 : // same way disarm_stop() would if called inside the locked loop.
546 2241 : intrusive_list<posix_signal> doomed;
547 :
548 : {
549 : posix_signal_detail::signal_state* state =
550 2241 : posix_signal_detail::get_signal_state();
551 2241 : std::lock_guard state_lock(state->mutex);
552 2241 : std::lock_guard lock(mutex_);
553 :
554 2247 : for (auto* impl = impl_list_.pop_front(); impl != nullptr;
555 6 : impl = impl_list_.pop_front())
556 : {
557 12 : while (auto* reg = impl->signals_)
558 : {
559 6 : int const signal_number = reg->signal_number;
560 :
561 : // The registration table outlives every io_context, so a set
562 : // still registered here has to give its count and disposition
563 : // back the way clear() would: otherwise the signal stays
564 : // installed with these flags and the next add() of it is
565 : // refused. The per-node table unlink clear() also does is
566 : // skipped in favour of the wholesale null-out below.
567 6 : if (state->registration_count[signal_number] == 1)
568 : {
569 4 : struct sigaction sa = {};
570 4 : sa.sa_handler = SIG_DFL;
571 4 : sigemptyset(&sa.sa_mask);
572 4 : sa.sa_flags = 0;
573 4 : std::ignore = ::sigaction(signal_number, &sa, nullptr);
574 4 : state->registered_flags[signal_number] = signal_set::none;
575 : }
576 :
577 6 : --state->registration_count[signal_number];
578 6 : --registration_count_[signal_number];
579 :
580 6 : impl->signals_ = reg->next_in_set;
581 6 : delete reg;
582 6 : }
583 6 : doomed.push_back(impl);
584 : }
585 :
586 : // Every live registration hung off an implementation in impl_list_,
587 : // so the whole table goes stale at once and can be dropped wholesale
588 : // rather than node by node. It has to be dropped: deliver_signal()
589 : // walks this service until the destructor unlinks it from the global
590 : // list.
591 145665 : for (int i = 0; i < max_signal_number; ++i)
592 143424 : registrations_[i] = nullptr;
593 2241 : }
594 :
595 2247 : for (auto* impl = doomed.pop_front(); impl != nullptr;
596 6 : impl = doomed.pop_front())
597 : {
598 6 : delete impl;
599 : }
600 2241 : }
601 :
602 : inline io_object::implementation*
603 190 : posix_signal_service::construct()
604 : {
605 190 : auto* impl = new posix_signal(*this);
606 :
607 : {
608 190 : std::lock_guard lock(mutex_);
609 190 : impl_list_.push_back(impl);
610 190 : }
611 :
612 190 : return impl;
613 : }
614 :
615 : inline void
616 184 : posix_signal_service::destroy_impl(posix_signal& impl)
617 : {
618 : {
619 184 : std::lock_guard lock(mutex_);
620 184 : impl_list_.remove(&impl);
621 184 : }
622 :
623 184 : delete &impl;
624 184 : }
625 :
626 : inline std::error_code
627 211 : posix_signal_service::add_signal(
628 : posix_signal& impl, int signal_number, signal_set::flags_t flags)
629 : {
630 211 : if (signal_number < 0 || signal_number >= max_signal_number)
631 4 : return make_error_code(std::errc::invalid_argument);
632 :
633 : // Validate that requested flags are supported on this platform
634 : // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
635 207 : if (!posix_signal_detail::flags_supported(flags))
636 MIS 0 : return make_error_code(std::errc::operation_not_supported);
637 :
638 : posix_signal_detail::signal_state* state =
639 HIT 207 : posix_signal_detail::get_signal_state();
640 :
641 : // Ensure the global self-pipe exists and this service's scheduler is
642 : // watching its read end, BEFORE taking the registration locks. The
643 : // reactor drain path locks the descriptor mutex and then the signal-state
644 : // and service mutexes; register_signal_reader locks the descriptor mutex
645 : // (via register_descriptor), so it must run holding neither of those or
646 : // the lock order would invert (a real deadlock, caught by TSan). call_once
647 : // makes the once-per-service registration safe when two signal_sets on
648 : // this context race add() from different threads.
649 : {
650 207 : std::lock_guard state_lock(state->mutex);
651 207 : if (auto ec = posix_signal_detail::open_signal_pipe(state))
652 7 : return ec;
653 207 : }
654 : {
655 : // Success-latched so a failed environmental registration
656 : // (epoll_ctl ENOMEM/ENOSPC) is retried by the next add()
657 : // instead of being lost; the code travels the return channel.
658 200 : std::lock_guard reg_lock(reader_mutex_);
659 200 : if (!reader_registered_)
660 : {
661 137 : if (auto ec = sched_->register_signal_reader(state->read_fd))
662 2 : return ec;
663 135 : reader_registered_ = true;
664 : }
665 200 : }
666 :
667 198 : std::lock_guard state_lock(state->mutex);
668 198 : std::lock_guard lock(mutex_);
669 :
670 : // Find insertion point (list is sorted by signal number)
671 198 : signal_registration** insertion_point = &impl.signals_;
672 198 : signal_registration* reg = impl.signals_;
673 221 : while (reg && reg->signal_number < signal_number)
674 : {
675 23 : insertion_point = ®->next_in_set;
676 23 : reg = reg->next_in_set;
677 : }
678 :
679 : // Already registered in this set - check flag compatibility
680 : // (same signal_set adding same signal twice with different flags)
681 198 : if (reg && reg->signal_number == signal_number)
682 : {
683 13 : if (!posix_signal_detail::flags_compatible(reg->flags, flags))
684 4 : return make_error_code(std::errc::invalid_argument);
685 9 : return {};
686 : }
687 :
688 : // Check flag compatibility with global registration
689 : // (different signal_set already registered this signal with different flags)
690 185 : if (state->registration_count[signal_number] > 0)
691 : {
692 26 : if (!posix_signal_detail::flags_compatible(
693 : state->registered_flags[signal_number], flags))
694 2 : return make_error_code(std::errc::invalid_argument);
695 : }
696 :
697 183 : auto* new_reg = new signal_registration;
698 183 : new_reg->signal_number = signal_number;
699 183 : new_reg->flags = flags;
700 183 : new_reg->owner = &impl;
701 183 : new_reg->undelivered = 0;
702 :
703 : // Install signal handler on first global registration
704 183 : if (state->registration_count[signal_number] == 0)
705 : {
706 159 : struct sigaction sa = {};
707 159 : sa.sa_handler = posix_signal_detail::corosio_posix_signal_handler;
708 159 : sigemptyset(&sa.sa_mask);
709 159 : sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
710 :
711 159 : if (::sigaction(signal_number, &sa, nullptr) < 0)
712 : {
713 1 : delete new_reg;
714 1 : return make_error_code(std::errc::invalid_argument);
715 : }
716 :
717 : // Store the flags used for first registration
718 158 : state->registered_flags[signal_number] = flags;
719 : }
720 :
721 182 : new_reg->next_in_set = reg;
722 182 : *insertion_point = new_reg;
723 :
724 182 : new_reg->next_in_table = registrations_[signal_number];
725 182 : new_reg->prev_in_table = nullptr;
726 182 : if (registrations_[signal_number])
727 18 : registrations_[signal_number]->prev_in_table = new_reg;
728 182 : registrations_[signal_number] = new_reg;
729 :
730 182 : ++state->registration_count[signal_number];
731 182 : ++registration_count_[signal_number];
732 :
733 182 : return {};
734 198 : }
735 :
736 : inline std::error_code
737 26 : posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
738 : {
739 26 : if (signal_number < 0 || signal_number >= max_signal_number)
740 2 : return make_error_code(std::errc::invalid_argument);
741 :
742 : posix_signal_detail::signal_state* state =
743 24 : posix_signal_detail::get_signal_state();
744 24 : std::lock_guard state_lock(state->mutex);
745 24 : std::lock_guard lock(mutex_);
746 :
747 24 : signal_registration** deletion_point = &impl.signals_;
748 24 : signal_registration* reg = impl.signals_;
749 26 : while (reg && reg->signal_number < signal_number)
750 : {
751 2 : deletion_point = ®->next_in_set;
752 2 : reg = reg->next_in_set;
753 : }
754 :
755 24 : if (!reg || reg->signal_number != signal_number)
756 3 : return {};
757 :
758 : // Restore default handler on last global unregistration
759 21 : if (state->registration_count[signal_number] == 1)
760 : {
761 17 : struct sigaction sa = {};
762 17 : sa.sa_handler = SIG_DFL;
763 17 : sigemptyset(&sa.sa_mask);
764 17 : sa.sa_flags = 0;
765 :
766 17 : if (::sigaction(signal_number, &sa, nullptr) < 0)
767 1 : return make_error_code(std::errc::invalid_argument);
768 :
769 : // Clear stored flags
770 16 : state->registered_flags[signal_number] = signal_set::none;
771 : }
772 :
773 20 : *deletion_point = reg->next_in_set;
774 :
775 20 : if (registrations_[signal_number] == reg)
776 18 : registrations_[signal_number] = reg->next_in_table;
777 20 : if (reg->prev_in_table)
778 2 : reg->prev_in_table->next_in_table = reg->next_in_table;
779 20 : if (reg->next_in_table)
780 2 : reg->next_in_table->prev_in_table = reg->prev_in_table;
781 :
782 20 : --state->registration_count[signal_number];
783 20 : --registration_count_[signal_number];
784 :
785 20 : delete reg;
786 20 : return {};
787 24 : }
788 :
789 : inline std::error_code
790 198 : posix_signal_service::clear_signals(posix_signal& impl)
791 : {
792 : posix_signal_detail::signal_state* state =
793 198 : posix_signal_detail::get_signal_state();
794 198 : std::lock_guard state_lock(state->mutex);
795 198 : std::lock_guard lock(mutex_);
796 :
797 198 : std::error_code first_error;
798 :
799 354 : while (signal_registration* reg = impl.signals_)
800 : {
801 156 : int signal_number = reg->signal_number;
802 :
803 156 : if (state->registration_count[signal_number] == 1)
804 : {
805 138 : struct sigaction sa = {};
806 138 : sa.sa_handler = SIG_DFL;
807 138 : sigemptyset(&sa.sa_mask);
808 138 : sa.sa_flags = 0;
809 :
810 138 : if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
811 1 : first_error = make_error_code(std::errc::invalid_argument);
812 :
813 : // Clear stored flags
814 138 : state->registered_flags[signal_number] = signal_set::none;
815 : }
816 :
817 156 : impl.signals_ = reg->next_in_set;
818 :
819 156 : if (registrations_[signal_number] == reg)
820 154 : registrations_[signal_number] = reg->next_in_table;
821 156 : if (reg->prev_in_table)
822 2 : reg->prev_in_table->next_in_table = reg->next_in_table;
823 156 : if (reg->next_in_table)
824 12 : reg->next_in_table->prev_in_table = reg->prev_in_table;
825 :
826 156 : --state->registration_count[signal_number];
827 156 : --registration_count_[signal_number];
828 :
829 156 : delete reg;
830 156 : }
831 :
832 198 : if (first_error)
833 1 : return first_error;
834 197 : return {};
835 198 : }
836 :
837 : inline void
838 201 : posix_signal_service::cancel_wait(posix_signal& impl)
839 : {
840 201 : bool was_waiting = false;
841 201 : signal_op* op = nullptr;
842 :
843 : {
844 201 : std::lock_guard lock(mutex_);
845 201 : impl.cancelled_ = true;
846 201 : if (impl.waiting_)
847 : {
848 7 : was_waiting = true;
849 7 : impl.waiting_ = false;
850 7 : op = &impl.pending_op_;
851 : }
852 201 : }
853 :
854 201 : if (was_waiting)
855 : {
856 7 : if (op->ec_out)
857 7 : *op->ec_out = make_error_code(capy::error::canceled);
858 7 : if (op->signal_out)
859 7 : *op->signal_out = 0;
860 7 : op->cont.h = op->h;
861 7 : op->d.post(op->cont);
862 7 : sched_->work_finished();
863 : }
864 201 : }
865 :
866 : inline void
867 689 : posix_signal_service::cancel_wait_token(posix_signal& impl) noexcept
868 : {
869 689 : bool was_waiting = false;
870 689 : signal_op* op = nullptr;
871 :
872 : {
873 689 : std::lock_guard lock(mutex_);
874 : // Persist the request even when no wait is parked yet: wait()
875 : // arms the callback before start_wait takes this lock, and
876 : // start_wait consumes this flag.
877 689 : impl.token_cancelled_ = true;
878 689 : if (impl.waiting_)
879 : {
880 607 : was_waiting = true;
881 607 : impl.waiting_ = false;
882 607 : op = &impl.pending_op_;
883 : }
884 689 : }
885 :
886 689 : if (was_waiting)
887 : {
888 607 : if (op->ec_out)
889 607 : *op->ec_out = make_error_code(capy::error::canceled);
890 607 : if (op->signal_out)
891 607 : *op->signal_out = 0;
892 607 : op->cont.h = op->h;
893 607 : op->d.post(op->cont);
894 607 : sched_->work_finished();
895 : }
896 689 : }
897 :
898 : inline void
899 689 : posix_signal::token_canceller::operator()() const noexcept
900 : {
901 689 : self->svc_.cancel_wait_token(*self);
902 689 : }
903 :
904 : inline void
905 1023 : posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
906 : {
907 : {
908 1023 : std::lock_guard lock(mutex_);
909 :
910 : // Check if cancel() was called before this wait started
911 1023 : if (impl.cancelled_)
912 : {
913 2 : impl.cancelled_ = false;
914 2 : if (op->ec_out)
915 2 : *op->ec_out = make_error_code(capy::error::canceled);
916 2 : if (op->signal_out)
917 2 : *op->signal_out = 0;
918 2 : op->cont.h = op->h;
919 2 : op->d.post(op->cont);
920 2 : return;
921 : }
922 :
923 : // A stop request that arrived between wait() arming the callback
924 : // and this lock: complete now rather than parking forever.
925 1021 : if (impl.token_cancelled_)
926 : {
927 80 : impl.token_cancelled_ = false;
928 80 : if (op->ec_out)
929 80 : *op->ec_out = make_error_code(capy::error::canceled);
930 80 : if (op->signal_out)
931 80 : *op->signal_out = 0;
932 80 : op->cont.h = op->h;
933 80 : op->d.post(op->cont);
934 80 : return;
935 : }
936 :
937 : // Check for queued signals first (signal arrived before wait started)
938 941 : signal_registration* reg = impl.signals_;
939 1884 : while (reg)
940 : {
941 945 : if (reg->undelivered > 0)
942 : {
943 2 : --reg->undelivered;
944 2 : op->signal_number = reg->signal_number;
945 : // svc=nullptr: no work_finished needed since we never called work_started
946 2 : op->svc = nullptr;
947 2 : sched_->post(op);
948 2 : return;
949 : }
950 943 : reg = reg->next_in_set;
951 : }
952 :
953 : // No queued signals - wait for delivery
954 939 : impl.waiting_ = true;
955 : // svc=this: signal_op::operator() will call work_finished() to balance this
956 939 : op->svc = this;
957 939 : sched_->work_started();
958 1023 : }
959 : }
960 :
961 : inline void
962 317 : posix_signal_service::deliver_signal(int signal_number)
963 : {
964 317 : if (signal_number < 0 || signal_number >= max_signal_number)
965 MIS 0 : return;
966 :
967 : posix_signal_detail::signal_state* state =
968 HIT 317 : posix_signal_detail::get_signal_state();
969 317 : std::lock_guard lock(state->mutex);
970 :
971 317 : posix_signal_service* service = state->service_list;
972 634 : while (service)
973 : {
974 317 : std::lock_guard svc_lock(service->mutex_);
975 :
976 317 : signal_registration* reg = service->registrations_[signal_number];
977 638 : while (reg)
978 : {
979 321 : posix_signal* impl = static_cast<posix_signal*>(reg->owner);
980 :
981 321 : if (impl->waiting_)
982 : {
983 319 : impl->waiting_ = false;
984 319 : impl->pending_op_.signal_number = signal_number;
985 319 : service->post(&impl->pending_op_);
986 : }
987 : else
988 : {
989 2 : ++reg->undelivered;
990 : }
991 :
992 321 : reg = reg->next_in_table;
993 : }
994 :
995 317 : service = service->next_;
996 317 : }
997 317 : }
998 :
999 : inline void
1000 : posix_signal_service::work_started() noexcept
1001 : {
1002 : sched_->work_started();
1003 : }
1004 :
1005 : inline void
1006 319 : posix_signal_service::work_finished() noexcept
1007 : {
1008 319 : sched_->work_finished();
1009 319 : }
1010 :
1011 : inline void
1012 319 : posix_signal_service::post(signal_op* op)
1013 : {
1014 319 : sched_->post(op);
1015 319 : }
1016 :
1017 : inline void
1018 2241 : posix_signal_service::add_service(posix_signal_service* service)
1019 : {
1020 : posix_signal_detail::signal_state* state =
1021 2241 : posix_signal_detail::get_signal_state();
1022 2241 : std::lock_guard lock(state->mutex);
1023 :
1024 2241 : service->next_ = state->service_list;
1025 2241 : service->prev_ = nullptr;
1026 2241 : if (state->service_list)
1027 11 : state->service_list->prev_ = service;
1028 2241 : state->service_list = service;
1029 2241 : }
1030 :
1031 : inline void
1032 2241 : posix_signal_service::remove_service(posix_signal_service* service)
1033 : {
1034 : posix_signal_detail::signal_state* state =
1035 2241 : posix_signal_detail::get_signal_state();
1036 2241 : std::lock_guard lock(state->mutex);
1037 :
1038 2241 : if (service->next_ || service->prev_ || state->service_list == service)
1039 : {
1040 2241 : if (state->service_list == service)
1041 2239 : state->service_list = service->next_;
1042 2241 : if (service->prev_)
1043 2 : service->prev_->next_ = service->next_;
1044 2241 : if (service->next_)
1045 9 : service->next_->prev_ = service->prev_;
1046 2241 : service->next_ = nullptr;
1047 2241 : service->prev_ = nullptr;
1048 : }
1049 2241 : }
1050 :
1051 : // get_signal_service - factory function
1052 :
1053 : inline posix_signal_service&
1054 2241 : get_signal_service(capy::execution_context& ctx, scheduler& sched)
1055 : {
1056 2241 : return ctx.make_service<posix_signal_service>(sched);
1057 : }
1058 :
1059 : } // namespace detail
1060 : } // namespace boost::corosio
1061 :
1062 : #endif // BOOST_COROSIO_POSIX
1063 :
1064 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
|