include/boost/corosio/native/detail/posix/posix_signal_service.hpp

99.0% Lines (395 / 399) 97.1% Functions (33 / 34)
posix_signal_service.hpp
f(x) Functions (34)
Function Calls Lines Blocks
boost::corosio::detail::posix_signal_service::destroy(boost::corosio::io_object::implementation*) :168 184x 100.0% 100.0% boost::corosio::detail::posix_signal_service::reset_token_cancel(boost::corosio::detail::posix_signal&) :210 985x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::flags_supported(boost::corosio::signal_set::flags_t) :299 207x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::to_sigaction_flags(boost::corosio::signal_set::flags_t) :311 159x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::flags_compatible(boost::corosio::signal_set::flags_t, boost::corosio::signal_set::flags_t) :331 39x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::open_signal_pipe(boost::corosio::detail::posix_signal_detail::signal_state*) :351 207x 100.0% 97.0% boost::corosio::detail::posix_signal_detail::corosio_posix_signal_handler(int) :385 317x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::drain_signal_pipe() :401 317x 100.0% 100.0% boost::corosio::detail::signal_op::operator()() :417 321x 100.0% 100.0% boost::corosio::detail::signal_op::destroy() :437 0 0.0% 0.0% boost::corosio::detail::posix_signal::posix_signal(boost::corosio::detail::posix_signal_service&) :444 190x 100.0% 100.0% boost::corosio::detail::posix_signal::wait(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, std::stop_token, std::error_code*, int*) :450 1106x 100.0% 100.0% boost::corosio::detail::posix_signal::add(int, boost::corosio::signal_set::flags_t) :496 211x 100.0% 100.0% boost::corosio::detail::posix_signal::remove(int) :502 26x 100.0% 100.0% boost::corosio::detail::posix_signal::clear() :508 198x 100.0% 100.0% boost::corosio::detail::posix_signal::cancel() :514 201x 100.0% 100.0% boost::corosio::detail::posix_signal_service::posix_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :521 2241x 100.0% 79.0% boost::corosio::detail::posix_signal_service::~posix_signal_service() :533 4482x 100.0% 100.0% boost::corosio::detail::posix_signal_service::shutdown() :539 2241x 100.0% 91.0% boost::corosio::detail::posix_signal_service::construct() :603 190x 100.0% 100.0% boost::corosio::detail::posix_signal_service::destroy_impl(boost::corosio::detail::posix_signal&) :616 184x 100.0% 100.0% boost::corosio::detail::posix_signal_service::add_signal(boost::corosio::detail::posix_signal&, int, boost::corosio::signal_set::flags_t) :627 211x 98.2% 84.0% boost::corosio::detail::posix_signal_service::remove_signal(boost::corosio::detail::posix_signal&, int) :737 26x 100.0% 95.0% boost::corosio::detail::posix_signal_service::clear_signals(boost::corosio::detail::posix_signal&) :790 198x 100.0% 95.0% boost::corosio::detail::posix_signal_service::cancel_wait(boost::corosio::detail::posix_signal&) :838 201x 100.0% 100.0% boost::corosio::detail::posix_signal_service::cancel_wait_token(boost::corosio::detail::posix_signal&) :867 651x 100.0% 100.0% boost::corosio::detail::posix_signal::token_canceller::operator()() const :899 651x 100.0% 100.0% boost::corosio::detail::posix_signal_service::start_wait(boost::corosio::detail::posix_signal&, boost::corosio::detail::signal_op*) :905 985x 100.0% 94.0% boost::corosio::detail::posix_signal_service::deliver_signal(int) :962 317x 95.0% 76.0% boost::corosio::detail::posix_signal_service::work_finished() :1006 319x 100.0% 100.0% boost::corosio::detail::posix_signal_service::post(boost::corosio::detail::signal_op*) :1012 319x 100.0% 100.0% boost::corosio::detail::posix_signal_service::add_service(boost::corosio::detail::posix_signal_service*) :1018 2241x 100.0% 100.0% boost::corosio::detail::posix_signal_service::remove_service(boost::corosio::detail::posix_signal_service*) :1032 2241x 100.0% 100.0% boost::corosio::detail::get_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :1054 2241x 100.0% 100.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
3 // Copyright (c) 2026 Michael Vandeberg
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
12 #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
13
14 #include <boost/corosio/detail/platform.hpp>
15
16 #if BOOST_COROSIO_POSIX
17
18 #include <boost/corosio/native/detail/posix/posix_signal.hpp>
19
20 #include <boost/corosio/detail/config.hpp>
21 #include <boost/capy/ex/execution_context.hpp>
22 #include <boost/corosio/detail/scheduler.hpp>
23 #include <boost/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 184x void destroy(io_object::implementation* p) override
169 {
170 184x auto& impl = static_cast<posix_signal&>(*p);
171 184x [[maybe_unused]] auto n = impl.clear();
172 184x impl.disarm_stop();
173 184x impl.cancel();
174 184x destroy_impl(impl);
175 184x }
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 985x void reset_token_cancel(posix_signal& impl) noexcept
211 {
212 985x std::lock_guard lock(mutex_);
213 985x impl.token_cancelled_ = false;
214 985x }
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 207x 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 207x return true;
306 }
307
308 // Map abstract flags to sigaction() flags.
309 // Caller must ensure flags_supported() returns true first.
310 inline int
311 159x to_sigaction_flags(signal_set::flags_t flags)
312 {
313 159x int sa_flags = 0;
314 159x if (flags & signal_set::restart)
315 23x sa_flags |= SA_RESTART;
316 159x if (flags & signal_set::no_child_stop)
317 3x sa_flags |= SA_NOCLDSTOP;
318 #ifdef SA_NOCLDWAIT
319 159x if (flags & signal_set::no_child_wait)
320 2x sa_flags |= SA_NOCLDWAIT;
321 #endif
322 159x if (flags & signal_set::no_defer)
323 4x sa_flags |= SA_NODEFER;
324 159x if (flags & signal_set::reset_handler)
325 2x sa_flags |= SA_RESETHAND;
326 159x return sa_flags;
327 }
328
329 // Check if two flag values are compatible
330 inline bool
331 39x flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
332 {
333 // dont_care is always compatible
334 76x if ((existing & signal_set::dont_care) ||
335 37x (requested & signal_set::dont_care))
336 7x return true;
337
338 // Mask out dont_care bit for comparison
339 32x constexpr auto mask = ~signal_set::dont_care;
340 32x 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 207x open_signal_pipe(signal_state* state)
352 {
353 207x if (state->read_fd >= 0)
354 193x return {};
355
356 int fds[2];
357 14x if (::pipe(fds) < 0)
358 1x return make_err(errno);
359
360 30x for (int i = 0; i < 2; ++i)
361 {
362 23x int fl = ::fcntl(fds[i], F_GETFL, 0);
363 42x if (fl == -1 || ::fcntl(fds[i], F_SETFL, fl | O_NONBLOCK) == -1 ||
364 19x ::fcntl(fds[i], F_SETFD, FD_CLOEXEC) == -1)
365 {
366 6x auto ec = make_err(errno);
367 6x ::close(fds[0]);
368 6x ::close(fds[1]);
369 6x return ec;
370 }
371 }
372
373 7x state->read_fd = fds[0];
374 7x state->write_fd = fds[1];
375 7x 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 317x corosio_posix_signal_handler(int signal_number)
386 {
387 317x int saved_errno = errno;
388 317x signal_state* state = get_signal_state();
389 [[maybe_unused]] ssize_t r =
390 317x ::write(state->write_fd, &signal_number, sizeof(int));
391 317x errno = saved_errno;
392 // With sigaction(), the handler persists automatically (unlike some
393 // signal() implementations that reset to SIG_DFL).
394 317x }
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 317x drain_signal_pipe()
402 {
403 317x signal_state* state = get_signal_state();
404 int signal_number;
405 634x while (::read(state->read_fd, &signal_number, sizeof(int)) ==
406 static_cast<ssize_t>(sizeof(int)))
407 {
408 317x posix_signal_service::deliver_signal(signal_number);
409 }
410 317x }
411
412 } // namespace posix_signal_detail
413
414 // signal_op implementation
415
416 inline void
417 321x signal_op::operator()()
418 {
419 321x if (ec_out)
420 321x *ec_out = {};
421 321x if (signal_out)
422 321x *signal_out = signal_number;
423
424 // Capture svc before resuming (coro may destroy us)
425 321x auto* service = svc;
426 321x svc = nullptr;
427
428 321x cont.h = h;
429 321x d.post(cont);
430
431 // Balance the work_started() from start_wait
432 321x if (service)
433 319x service->work_finished();
434 321x }
435
436 inline void
437 ✗ signal_op::destroy()
438 {
439 // No-op: signal_op is embedded in posix_signal
440 ✗ }
441
442 // posix_signal implementation
443
444 190x inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
445 190x : svc_(svc)
446 {
447 190x }
448
449 inline std::coroutine_handle<>
450 1106x 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 1106x pending_op_.h = h;
458 1106x pending_op_.d = d;
459 1106x pending_op_.ec_out = ec;
460 1106x pending_op_.signal_out = signal_out;
461 1106x 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 1106x stop_cb_.reset();
470
471 1106x if (token.stop_requested())
472 {
473 121x if (ec)
474 121x *ec = make_error_code(capy::error::canceled);
475 121x if (signal_out)
476 121x *signal_out = 0;
477 121x pending_op_.cont.h = h;
478 121x d.post(pending_op_.cont);
479 // completion is always posted to scheduler queue, never inline.
480 121x 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 985x svc_.reset_token_cancel(*this);
487 985x if (token.stop_possible())
488 655x stop_cb_.emplace(token, token_canceller{this});
489
490 985x svc_.start_wait(*this, &pending_op_);
491 // completion is always posted to scheduler queue, never inline.
492 985x return std::noop_coroutine();
493 }
494
495 inline std::error_code
496 211x posix_signal::add(int signal_number, signal_set::flags_t flags)
497 {
498 211x return svc_.add_signal(*this, signal_number, flags);
499 }
500
501 inline std::error_code
502 26x posix_signal::remove(int signal_number)
503 {
504 26x return svc_.remove_signal(*this, signal_number);
505 }
506
507 inline std::error_code
508 198x posix_signal::clear()
509 {
510 198x return svc_.clear_signals(*this);
511 }
512
513 inline void
514 201x posix_signal::cancel() noexcept
515 {
516 201x svc_.cancel_wait(*this);
517 201x }
518
519 // posix_signal_service implementation
520
521 2241x inline posix_signal_service::posix_signal_service(
522 2241x capy::execution_context&, scheduler& sched)
523 2241x : sched_(&sched)
524 {
525 145665x for (int i = 0; i < max_signal_number; ++i)
526 {
527 143424x registrations_[i] = nullptr;
528 143424x registration_count_[i] = 0;
529 }
530 2241x add_service(this);
531 2241x }
532
533 4482x inline posix_signal_service::~posix_signal_service()
534 {
535 2241x remove_service(this);
536 4482x }
537
538 inline void
539 2241x 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 2241x intrusive_list<posix_signal> doomed;
547
548 {
549 posix_signal_detail::signal_state* state =
550 2241x posix_signal_detail::get_signal_state();
551 2241x std::lock_guard state_lock(state->mutex);
552 2241x std::lock_guard lock(mutex_);
553
554 2247x for (auto* impl = impl_list_.pop_front(); impl != nullptr;
555 6x impl = impl_list_.pop_front())
556 {
557 12x while (auto* reg = impl->signals_)
558 {
559 6x 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 6x if (state->registration_count[signal_number] == 1)
568 {
569 4x struct sigaction sa = {};
570 4x sa.sa_handler = SIG_DFL;
571 4x sigemptyset(&sa.sa_mask);
572 4x sa.sa_flags = 0;
573 4x std::ignore = ::sigaction(signal_number, &sa, nullptr);
574 4x state->registered_flags[signal_number] = signal_set::none;
575 }
576
577 6x --state->registration_count[signal_number];
578 6x --registration_count_[signal_number];
579
580 6x impl->signals_ = reg->next_in_set;
581 6x delete reg;
582 6x }
583 6x 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 145665x for (int i = 0; i < max_signal_number; ++i)
592 143424x registrations_[i] = nullptr;
593 2241x }
594
595 2247x for (auto* impl = doomed.pop_front(); impl != nullptr;
596 6x impl = doomed.pop_front())
597 {
598 6x delete impl;
599 }
600 2241x }
601
602 inline io_object::implementation*
603 190x posix_signal_service::construct()
604 {
605 190x auto* impl = new posix_signal(*this);
606
607 {
608 190x std::lock_guard lock(mutex_);
609 190x impl_list_.push_back(impl);
610 190x }
611
612 190x return impl;
613 }
614
615 inline void
616 184x posix_signal_service::destroy_impl(posix_signal& impl)
617 {
618 {
619 184x std::lock_guard lock(mutex_);
620 184x impl_list_.remove(&impl);
621 184x }
622
623 184x delete &impl;
624 184x }
625
626 inline std::error_code
627 211x posix_signal_service::add_signal(
628 posix_signal& impl, int signal_number, signal_set::flags_t flags)
629 {
630 211x if (signal_number < 0 || signal_number >= max_signal_number)
631 4x 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 207x if (!posix_signal_detail::flags_supported(flags))
636 ✗ return make_error_code(std::errc::operation_not_supported);
637
638 posix_signal_detail::signal_state* state =
639 207x 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 207x std::lock_guard state_lock(state->mutex);
651 207x if (auto ec = posix_signal_detail::open_signal_pipe(state))
652 7x return ec;
653 207x }
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 200x std::lock_guard reg_lock(reader_mutex_);
659 200x if (!reader_registered_)
660 {
661 137x if (auto ec = sched_->register_signal_reader(state->read_fd))
662 2x return ec;
663 135x reader_registered_ = true;
664 }
665 200x }
666
667 198x std::lock_guard state_lock(state->mutex);
668 198x std::lock_guard lock(mutex_);
669
670 // Find insertion point (list is sorted by signal number)
671 198x signal_registration** insertion_point = &impl.signals_;
672 198x signal_registration* reg = impl.signals_;
673 221x while (reg && reg->signal_number < signal_number)
674 {
675 23x insertion_point = &reg->next_in_set;
676 23x 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 198x if (reg && reg->signal_number == signal_number)
682 {
683 13x if (!posix_signal_detail::flags_compatible(reg->flags, flags))
684 4x return make_error_code(std::errc::invalid_argument);
685 9x return {};
686 }
687
688 // Check flag compatibility with global registration
689 // (different signal_set already registered this signal with different flags)
690 185x if (state->registration_count[signal_number] > 0)
691 {
692 26x if (!posix_signal_detail::flags_compatible(
693 state->registered_flags[signal_number], flags))
694 2x return make_error_code(std::errc::invalid_argument);
695 }
696
697 183x auto* new_reg = new signal_registration;
698 183x new_reg->signal_number = signal_number;
699 183x new_reg->flags = flags;
700 183x new_reg->owner = &impl;
701 183x new_reg->undelivered = 0;
702
703 // Install signal handler on first global registration
704 183x if (state->registration_count[signal_number] == 0)
705 {
706 159x struct sigaction sa = {};
707 159x sa.sa_handler = posix_signal_detail::corosio_posix_signal_handler;
708 159x sigemptyset(&sa.sa_mask);
709 159x sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
710
711 159x if (::sigaction(signal_number, &sa, nullptr) < 0)
712 {
713 1x delete new_reg;
714 1x return make_error_code(std::errc::invalid_argument);
715 }
716
717 // Store the flags used for first registration
718 158x state->registered_flags[signal_number] = flags;
719 }
720
721 182x new_reg->next_in_set = reg;
722 182x *insertion_point = new_reg;
723
724 182x new_reg->next_in_table = registrations_[signal_number];
725 182x new_reg->prev_in_table = nullptr;
726 182x if (registrations_[signal_number])
727 18x registrations_[signal_number]->prev_in_table = new_reg;
728 182x registrations_[signal_number] = new_reg;
729
730 182x ++state->registration_count[signal_number];
731 182x ++registration_count_[signal_number];
732
733 182x return {};
734 198x }
735
736 inline std::error_code
737 26x posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
738 {
739 26x if (signal_number < 0 || signal_number >= max_signal_number)
740 2x return make_error_code(std::errc::invalid_argument);
741
742 posix_signal_detail::signal_state* state =
743 24x posix_signal_detail::get_signal_state();
744 24x std::lock_guard state_lock(state->mutex);
745 24x std::lock_guard lock(mutex_);
746
747 24x signal_registration** deletion_point = &impl.signals_;
748 24x signal_registration* reg = impl.signals_;
749 26x while (reg && reg->signal_number < signal_number)
750 {
751 2x deletion_point = &reg->next_in_set;
752 2x reg = reg->next_in_set;
753 }
754
755 24x if (!reg || reg->signal_number != signal_number)
756 3x return {};
757
758 // Restore default handler on last global unregistration
759 21x if (state->registration_count[signal_number] == 1)
760 {
761 17x struct sigaction sa = {};
762 17x sa.sa_handler = SIG_DFL;
763 17x sigemptyset(&sa.sa_mask);
764 17x sa.sa_flags = 0;
765
766 17x if (::sigaction(signal_number, &sa, nullptr) < 0)
767 1x return make_error_code(std::errc::invalid_argument);
768
769 // Clear stored flags
770 16x state->registered_flags[signal_number] = signal_set::none;
771 }
772
773 20x *deletion_point = reg->next_in_set;
774
775 20x if (registrations_[signal_number] == reg)
776 18x registrations_[signal_number] = reg->next_in_table;
777 20x if (reg->prev_in_table)
778 2x reg->prev_in_table->next_in_table = reg->next_in_table;
779 20x if (reg->next_in_table)
780 2x reg->next_in_table->prev_in_table = reg->prev_in_table;
781
782 20x --state->registration_count[signal_number];
783 20x --registration_count_[signal_number];
784
785 20x delete reg;
786 20x return {};
787 24x }
788
789 inline std::error_code
790 198x posix_signal_service::clear_signals(posix_signal& impl)
791 {
792 posix_signal_detail::signal_state* state =
793 198x posix_signal_detail::get_signal_state();
794 198x std::lock_guard state_lock(state->mutex);
795 198x std::lock_guard lock(mutex_);
796
797 198x std::error_code first_error;
798
799 354x while (signal_registration* reg = impl.signals_)
800 {
801 156x int signal_number = reg->signal_number;
802
803 156x if (state->registration_count[signal_number] == 1)
804 {
805 138x struct sigaction sa = {};
806 138x sa.sa_handler = SIG_DFL;
807 138x sigemptyset(&sa.sa_mask);
808 138x sa.sa_flags = 0;
809
810 138x if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
811 1x first_error = make_error_code(std::errc::invalid_argument);
812
813 // Clear stored flags
814 138x state->registered_flags[signal_number] = signal_set::none;
815 }
816
817 156x impl.signals_ = reg->next_in_set;
818
819 156x if (registrations_[signal_number] == reg)
820 154x registrations_[signal_number] = reg->next_in_table;
821 156x if (reg->prev_in_table)
822 2x reg->prev_in_table->next_in_table = reg->next_in_table;
823 156x if (reg->next_in_table)
824 12x reg->next_in_table->prev_in_table = reg->prev_in_table;
825
826 156x --state->registration_count[signal_number];
827 156x --registration_count_[signal_number];
828
829 156x delete reg;
830 156x }
831
832 198x if (first_error)
833 1x return first_error;
834 197x return {};
835 198x }
836
837 inline void
838 201x posix_signal_service::cancel_wait(posix_signal& impl)
839 {
840 201x bool was_waiting = false;
841 201x signal_op* op = nullptr;
842
843 {
844 201x std::lock_guard lock(mutex_);
845 201x impl.cancelled_ = true;
846 201x if (impl.waiting_)
847 {
848 7x was_waiting = true;
849 7x impl.waiting_ = false;
850 7x op = &impl.pending_op_;
851 }
852 201x }
853
854 201x if (was_waiting)
855 {
856 7x if (op->ec_out)
857 7x *op->ec_out = make_error_code(capy::error::canceled);
858 7x if (op->signal_out)
859 7x *op->signal_out = 0;
860 7x op->cont.h = op->h;
861 7x op->d.post(op->cont);
862 7x sched_->work_finished();
863 }
864 201x }
865
866 inline void
867 651x posix_signal_service::cancel_wait_token(posix_signal& impl) noexcept
868 {
869 651x bool was_waiting = false;
870 651x signal_op* op = nullptr;
871
872 {
873 651x 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 651x impl.token_cancelled_ = true;
878 651x if (impl.waiting_)
879 {
880 514x was_waiting = true;
881 514x impl.waiting_ = false;
882 514x op = &impl.pending_op_;
883 }
884 651x }
885
886 651x if (was_waiting)
887 {
888 514x if (op->ec_out)
889 514x *op->ec_out = make_error_code(capy::error::canceled);
890 514x if (op->signal_out)
891 514x *op->signal_out = 0;
892 514x op->cont.h = op->h;
893 514x op->d.post(op->cont);
894 514x sched_->work_finished();
895 }
896 651x }
897
898 inline void
899 651x posix_signal::token_canceller::operator()() const noexcept
900 {
901 651x self->svc_.cancel_wait_token(*self);
902 651x }
903
904 inline void
905 985x posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
906 {
907 {
908 985x std::lock_guard lock(mutex_);
909
910 // Check if cancel() was called before this wait started
911 985x if (impl.cancelled_)
912 {
913 2x impl.cancelled_ = false;
914 2x if (op->ec_out)
915 2x *op->ec_out = make_error_code(capy::error::canceled);
916 2x if (op->signal_out)
917 2x *op->signal_out = 0;
918 2x op->cont.h = op->h;
919 2x op->d.post(op->cont);
920 2x 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 983x if (impl.token_cancelled_)
926 {
927 135x impl.token_cancelled_ = false;
928 135x if (op->ec_out)
929 135x *op->ec_out = make_error_code(capy::error::canceled);
930 135x if (op->signal_out)
931 135x *op->signal_out = 0;
932 135x op->cont.h = op->h;
933 135x op->d.post(op->cont);
934 135x return;
935 }
936
937 // Check for queued signals first (signal arrived before wait started)
938 848x signal_registration* reg = impl.signals_;
939 1698x while (reg)
940 {
941 852x if (reg->undelivered > 0)
942 {
943 2x --reg->undelivered;
944 2x op->signal_number = reg->signal_number;
945 // svc=nullptr: no work_finished needed since we never called work_started
946 2x op->svc = nullptr;
947 2x sched_->post(op);
948 2x return;
949 }
950 850x reg = reg->next_in_set;
951 }
952
953 // No queued signals - wait for delivery
954 846x impl.waiting_ = true;
955 // svc=this: signal_op::operator() will call work_finished() to balance this
956 846x op->svc = this;
957 846x sched_->work_started();
958 985x }
959 }
960
961 inline void
962 317x posix_signal_service::deliver_signal(int signal_number)
963 {
964 317x if (signal_number < 0 || signal_number >= max_signal_number)
965 ✗ return;
966
967 posix_signal_detail::signal_state* state =
968 317x posix_signal_detail::get_signal_state();
969 317x std::lock_guard lock(state->mutex);
970
971 317x posix_signal_service* service = state->service_list;
972 634x while (service)
973 {
974 317x std::lock_guard svc_lock(service->mutex_);
975
976 317x signal_registration* reg = service->registrations_[signal_number];
977 638x while (reg)
978 {
979 321x posix_signal* impl = static_cast<posix_signal*>(reg->owner);
980
981 321x if (impl->waiting_)
982 {
983 319x impl->waiting_ = false;
984 319x impl->pending_op_.signal_number = signal_number;
985 319x service->post(&impl->pending_op_);
986 }
987 else
988 {
989 2x ++reg->undelivered;
990 }
991
992 321x reg = reg->next_in_table;
993 }
994
995 317x service = service->next_;
996 317x }
997 317x }
998
999 inline void
1000 posix_signal_service::work_started() noexcept
1001 {
1002 sched_->work_started();
1003 }
1004
1005 inline void
1006 319x posix_signal_service::work_finished() noexcept
1007 {
1008 319x sched_->work_finished();
1009 319x }
1010
1011 inline void
1012 319x posix_signal_service::post(signal_op* op)
1013 {
1014 319x sched_->post(op);
1015 319x }
1016
1017 inline void
1018 2241x posix_signal_service::add_service(posix_signal_service* service)
1019 {
1020 posix_signal_detail::signal_state* state =
1021 2241x posix_signal_detail::get_signal_state();
1022 2241x std::lock_guard lock(state->mutex);
1023
1024 2241x service->next_ = state->service_list;
1025 2241x service->prev_ = nullptr;
1026 2241x if (state->service_list)
1027 11x state->service_list->prev_ = service;
1028 2241x state->service_list = service;
1029 2241x }
1030
1031 inline void
1032 2241x posix_signal_service::remove_service(posix_signal_service* service)
1033 {
1034 posix_signal_detail::signal_state* state =
1035 2241x posix_signal_detail::get_signal_state();
1036 2241x std::lock_guard lock(state->mutex);
1037
1038 2241x if (service->next_ || service->prev_ || state->service_list == service)
1039 {
1040 2241x if (state->service_list == service)
1041 2239x state->service_list = service->next_;
1042 2241x if (service->prev_)
1043 2x service->prev_->next_ = service->next_;
1044 2241x if (service->next_)
1045 9x service->next_->prev_ = service->prev_;
1046 2241x service->next_ = nullptr;
1047 2241x service->prev_ = nullptr;
1048 }
1049 2241x }
1050
1051 // get_signal_service - factory function
1052
1053 inline posix_signal_service&
1054 2241x get_signal_service(capy::execution_context& ctx, scheduler& sched)
1055 {
1056 2241x 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
1065