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