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

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