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 248x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::flags_supported(boost::corosio::signal_set::flags_t) :278 278x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::to_sigaction_flags(boost::corosio::signal_set::flags_t) :290 212x 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 55x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::open_signal_pipe(boost::corosio::detail::posix_signal_detail::signal_state*) :330 278x 100.0% 97.0% boost::corosio::detail::posix_signal_detail::corosio_posix_signal_handler(int) :364 464x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::drain_signal_pipe() :380 463x 100.0% 100.0% boost::corosio::detail::signal_op::operator()() :396 469x 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 257x 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 491x 100.0% 100.0% boost::corosio::detail::posix_signal::add(int, boost::corosio::signal_set::flags_t) :460 284x 100.0% 100.0% boost::corosio::detail::posix_signal::remove(int) :466 37x 100.0% 100.0% boost::corosio::detail::posix_signal::clear() :472 268x 100.0% 100.0% boost::corosio::detail::posix_signal::cancel() :478 270x 100.0% 100.0% boost::corosio::detail::posix_signal_service::posix_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :485 3295x 100.0% 79.0% boost::corosio::detail::posix_signal_service::~posix_signal_service() :497 6590x 100.0% 100.0% boost::corosio::detail::posix_signal_service::shutdown() :503 3295x 100.0% 93.0% boost::corosio::detail::posix_signal_service::construct() :552 257x 100.0% 100.0% boost::corosio::detail::posix_signal_service::destroy_impl(boost::corosio::detail::posix_signal&) :565 248x 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 284x 98.2% 84.0% boost::corosio::detail::posix_signal_service::remove_signal(boost::corosio::detail::posix_signal&, int) :686 37x 100.0% 95.0% boost::corosio::detail::posix_signal_service::clear_signals(boost::corosio::detail::posix_signal&) :739 268x 100.0% 95.0% boost::corosio::detail::posix_signal_service::cancel_wait(boost::corosio::detail::posix_signal&) :787 270x 100.0% 100.0% boost::corosio::detail::posix_signal_service::start_wait(boost::corosio::detail::posix_signal&, boost::corosio::detail::signal_op*) :816 488x 100.0% 92.0% boost::corosio::detail::posix_signal_service::deliver_signal(int) :859 463x 95.0% 76.0% boost::corosio::detail::posix_signal_service::work_finished() :903 466x 100.0% 100.0% boost::corosio::detail::posix_signal_service::post(boost::corosio::detail::signal_op*) :909 466x 100.0% 100.0% boost::corosio::detail::posix_signal_service::add_service(boost::corosio::detail::posix_signal_service*) :915 3295x 100.0% 100.0% boost::corosio::detail::posix_signal_service::remove_service(boost::corosio::detail::posix_signal_service*) :929 3295x 100.0% 100.0% boost::corosio::detail::get_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :951 3295x 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 248x void destroy(io_object::implementation* p) override
169 {
170 248x auto& impl = static_cast<posix_signal&>(*p);
171 248x [[maybe_unused]] auto n = impl.clear();
172 248x impl.cancel();
173 248x destroy_impl(impl);
174 248x }
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 278x 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 278x return true;
285 }
286
287 // Map abstract flags to sigaction() flags.
288 // Caller must ensure flags_supported() returns true first.
289 inline int
290 212x to_sigaction_flags(signal_set::flags_t flags)
291 {
292 212x int sa_flags = 0;
293 212x if (flags & signal_set::restart)
294 33x sa_flags |= SA_RESTART;
295 212x if (flags & signal_set::no_child_stop)
296 4x sa_flags |= SA_NOCLDSTOP;
297 #ifdef SA_NOCLDWAIT
298 212x if (flags & signal_set::no_child_wait)
299 3x sa_flags |= SA_NOCLDWAIT;
300 #endif
301 212x if (flags & signal_set::no_defer)
302 6x sa_flags |= SA_NODEFER;
303 212x if (flags & signal_set::reset_handler)
304 3x sa_flags |= SA_RESETHAND;
305 212x return sa_flags;
306 }
307
308 // Check if two flag values are compatible
309 inline bool
310 55x flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
311 {
312 // dont_care is always compatible
313 107x if ((existing & signal_set::dont_care) ||
314 52x (requested & signal_set::dont_care))
315 10x return true;
316
317 // Mask out dont_care bit for comparison
318 45x constexpr auto mask = ~signal_set::dont_care;
319 45x 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 278x open_signal_pipe(signal_state* state)
331 {
332 278x if (state->read_fd >= 0)
333 261x return {};
334
335 int fds[2];
336 17x if (::pipe(fds) < 0)
337 1x return make_err(errno);
338
339 39x for (int i = 0; i < 2; ++i)
340 {
341 29x int fl = ::fcntl(fds[i], F_GETFL, 0);
342 54x if (fl == -1 || ::fcntl(fds[i], F_SETFL, fl | O_NONBLOCK) == -1 ||
343 25x ::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 10x state->read_fd = fds[0];
353 10x state->write_fd = fds[1];
354 10x 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 464x corosio_posix_signal_handler(int signal_number)
365 {
366 464x int saved_errno = errno;
367 464x signal_state* state = get_signal_state();
368 [[maybe_unused]] ssize_t r =
369 464x ::write(state->write_fd, &signal_number, sizeof(int));
370 464x errno = saved_errno;
371 // With sigaction(), the handler persists automatically (unlike some
372 // signal() implementations that reset to SIG_DFL).
373 464x }
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 463x drain_signal_pipe()
381 {
382 463x signal_state* state = get_signal_state();
383 int signal_number;
384 926x while (::read(state->read_fd, &signal_number, sizeof(int)) ==
385 static_cast<ssize_t>(sizeof(int)))
386 {
387 463x posix_signal_service::deliver_signal(signal_number);
388 }
389 463x }
390
391 } // namespace posix_signal_detail
392
393 // signal_op implementation
394
395 inline void
396 469x signal_op::operator()()
397 {
398 469x if (ec_out)
399 469x *ec_out = {};
400 469x if (signal_out)
401 469x *signal_out = signal_number;
402
403 // Capture svc before resuming (coro may destroy us)
404 469x auto* service = svc;
405 469x svc = nullptr;
406
407 469x cont.h = h;
408 469x d.post(cont);
409
410 // Balance the work_started() from start_wait
411 469x if (service)
412 466x service->work_finished();
413 469x }
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 257x inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
424 257x : svc_(svc)
425 {
426 257x }
427
428 inline std::coroutine_handle<>
429 491x 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 491x pending_op_.h = h;
437 491x pending_op_.d = d;
438 491x pending_op_.ec_out = ec;
439 491x pending_op_.signal_out = signal_out;
440 491x pending_op_.signal_number = 0;
441
442 491x if (token.stop_requested())
443 {
444 3x if (ec)
445 3x *ec = make_error_code(capy::error::canceled);
446 3x if (signal_out)
447 3x *signal_out = 0;
448 3x pending_op_.cont.h = h;
449 3x d.post(pending_op_.cont);
450 // completion is always posted to scheduler queue, never inline.
451 3x return std::noop_coroutine();
452 }
453
454 488x svc_.start_wait(*this, &pending_op_);
455 // completion is always posted to scheduler queue, never inline.
456 488x return std::noop_coroutine();
457 }
458
459 inline std::error_code
460 284x posix_signal::add(int signal_number, signal_set::flags_t flags)
461 {
462 284x return svc_.add_signal(*this, signal_number, flags);
463 }
464
465 inline std::error_code
466 37x posix_signal::remove(int signal_number)
467 {
468 37x return svc_.remove_signal(*this, signal_number);
469 }
470
471 inline std::error_code
472 268x posix_signal::clear()
473 {
474 268x return svc_.clear_signals(*this);
475 }
476
477 inline void
478 270x posix_signal::cancel() noexcept
479 {
480 270x svc_.cancel_wait(*this);
481 270x }
482
483 // posix_signal_service implementation
484
485 3295x inline posix_signal_service::posix_signal_service(
486 3295x capy::execution_context&, scheduler& sched)
487 3295x : sched_(&sched)
488 {
489 214175x for (int i = 0; i < max_signal_number; ++i)
490 {
491 210880x registrations_[i] = nullptr;
492 210880x registration_count_[i] = 0;
493 }
494 3295x add_service(this);
495 3295x }
496
497 6590x inline posix_signal_service::~posix_signal_service()
498 {
499 3295x remove_service(this);
500 6590x }
501
502 inline void
503 3295x posix_signal_service::shutdown()
504 {
505 posix_signal_detail::signal_state* state =
506 3295x posix_signal_detail::get_signal_state();
507 3295x std::lock_guard state_lock(state->mutex);
508 3295x std::lock_guard lock(mutex_);
509
510 3304x for (auto* impl = impl_list_.pop_front(); impl != nullptr;
511 9x impl = impl_list_.pop_front())
512 {
513 18x while (auto* reg = impl->signals_)
514 {
515 9x 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 9x if (state->registration_count[signal_number] == 1)
524 {
525 6x struct sigaction sa = {};
526 6x sa.sa_handler = SIG_DFL;
527 6x sigemptyset(&sa.sa_mask);
528 6x sa.sa_flags = 0;
529 6x std::ignore = ::sigaction(signal_number, &sa, nullptr);
530 6x state->registered_flags[signal_number] = signal_set::none;
531 }
532
533 9x --state->registration_count[signal_number];
534 9x --registration_count_[signal_number];
535
536 9x impl->signals_ = reg->next_in_set;
537 9x delete reg;
538 9x }
539 9x 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 214175x for (int i = 0; i < max_signal_number; ++i)
548 210880x registrations_[i] = nullptr;
549 3295x }
550
551 inline io_object::implementation*
552 257x posix_signal_service::construct()
553 {
554 257x auto* impl = new posix_signal(*this);
555
556 {
557 257x std::lock_guard lock(mutex_);
558 257x impl_list_.push_back(impl);
559 257x }
560
561 257x return impl;
562 }
563
564 inline void
565 248x posix_signal_service::destroy_impl(posix_signal& impl)
566 {
567 {
568 248x std::lock_guard lock(mutex_);
569 248x impl_list_.remove(&impl);
570 248x }
571
572 248x delete &impl;
573 248x }
574
575 inline std::error_code
576 284x posix_signal_service::add_signal(
577 posix_signal& impl, int signal_number, signal_set::flags_t flags)
578 {
579 284x if (signal_number < 0 || signal_number >= max_signal_number)
580 6x 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 278x 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 278x 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 278x std::lock_guard state_lock(state->mutex);
600 278x if (auto ec = posix_signal_detail::open_signal_pipe(state))
601 7x return ec;
602 278x }
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 271x std::lock_guard reg_lock(reader_mutex_);
608 271x if (!reader_registered_)
609 {
610 184x if (auto ec = sched_->register_signal_reader(state->read_fd))
611 4x return ec;
612 180x reader_registered_ = true;
613 }
614 271x }
615
616 267x std::lock_guard state_lock(state->mutex);
617 267x std::lock_guard lock(mutex_);
618
619 // Find insertion point (list is sorted by signal number)
620 267x signal_registration** insertion_point = &impl.signals_;
621 267x signal_registration* reg = impl.signals_;
622 298x while (reg && reg->signal_number < signal_number)
623 {
624 31x insertion_point = &reg->next_in_set;
625 31x 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 267x if (reg && reg->signal_number == signal_number)
631 {
632 18x if (!posix_signal_detail::flags_compatible(reg->flags, flags))
633 5x return make_error_code(std::errc::invalid_argument);
634 13x return {};
635 }
636
637 // Check flag compatibility with global registration
638 // (different signal_set already registered this signal with different flags)
639 249x if (state->registration_count[signal_number] > 0)
640 {
641 37x if (!posix_signal_detail::flags_compatible(
642 state->registered_flags[signal_number], flags))
643 3x return make_error_code(std::errc::invalid_argument);
644 }
645
646 246x auto* new_reg = new signal_registration;
647 246x new_reg->signal_number = signal_number;
648 246x new_reg->flags = flags;
649 246x new_reg->owner = &impl;
650 246x new_reg->undelivered = 0;
651
652 // Install signal handler on first global registration
653 246x if (state->registration_count[signal_number] == 0)
654 {
655 212x struct sigaction sa = {};
656 212x sa.sa_handler = posix_signal_detail::corosio_posix_signal_handler;
657 212x sigemptyset(&sa.sa_mask);
658 212x sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
659
660 212x 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 211x state->registered_flags[signal_number] = flags;
668 }
669
670 245x new_reg->next_in_set = reg;
671 245x *insertion_point = new_reg;
672
673 245x new_reg->next_in_table = registrations_[signal_number];
674 245x new_reg->prev_in_table = nullptr;
675 245x if (registrations_[signal_number])
676 25x registrations_[signal_number]->prev_in_table = new_reg;
677 245x registrations_[signal_number] = new_reg;
678
679 245x ++state->registration_count[signal_number];
680 245x ++registration_count_[signal_number];
681
682 245x return {};
683 267x }
684
685 inline std::error_code
686 37x posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
687 {
688 37x if (signal_number < 0 || signal_number >= max_signal_number)
689 3x return make_error_code(std::errc::invalid_argument);
690
691 posix_signal_detail::signal_state* state =
692 34x posix_signal_detail::get_signal_state();
693 34x std::lock_guard state_lock(state->mutex);
694 34x std::lock_guard lock(mutex_);
695
696 34x signal_registration** deletion_point = &impl.signals_;
697 34x signal_registration* reg = impl.signals_;
698 37x while (reg && reg->signal_number < signal_number)
699 {
700 3x deletion_point = &reg->next_in_set;
701 3x reg = reg->next_in_set;
702 }
703
704 34x if (!reg || reg->signal_number != signal_number)
705 4x return {};
706
707 // Restore default handler on last global unregistration
708 30x if (state->registration_count[signal_number] == 1)
709 {
710 24x struct sigaction sa = {};
711 24x sa.sa_handler = SIG_DFL;
712 24x sigemptyset(&sa.sa_mask);
713 24x sa.sa_flags = 0;
714
715 24x if (::sigaction(signal_number, &sa, nullptr) < 0)
716 1x return make_error_code(std::errc::invalid_argument);
717
718 // Clear stored flags
719 23x state->registered_flags[signal_number] = signal_set::none;
720 }
721
722 29x *deletion_point = reg->next_in_set;
723
724 29x if (registrations_[signal_number] == reg)
725 26x registrations_[signal_number] = reg->next_in_table;
726 29x if (reg->prev_in_table)
727 3x reg->prev_in_table->next_in_table = reg->next_in_table;
728 29x if (reg->next_in_table)
729 3x reg->next_in_table->prev_in_table = reg->prev_in_table;
730
731 29x --state->registration_count[signal_number];
732 29x --registration_count_[signal_number];
733
734 29x delete reg;
735 29x return {};
736 34x }
737
738 inline std::error_code
739 268x posix_signal_service::clear_signals(posix_signal& impl)
740 {
741 posix_signal_detail::signal_state* state =
742 268x posix_signal_detail::get_signal_state();
743 268x std::lock_guard state_lock(state->mutex);
744 268x std::lock_guard lock(mutex_);
745
746 268x std::error_code first_error;
747
748 475x while (signal_registration* reg = impl.signals_)
749 {
750 207x int signal_number = reg->signal_number;
751
752 207x if (state->registration_count[signal_number] == 1)
753 {
754 182x struct sigaction sa = {};
755 182x sa.sa_handler = SIG_DFL;
756 182x sigemptyset(&sa.sa_mask);
757 182x sa.sa_flags = 0;
758
759 182x 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 182x state->registered_flags[signal_number] = signal_set::none;
764 }
765
766 207x impl.signals_ = reg->next_in_set;
767
768 207x if (registrations_[signal_number] == reg)
769 204x registrations_[signal_number] = reg->next_in_table;
770 207x if (reg->prev_in_table)
771 3x reg->prev_in_table->next_in_table = reg->next_in_table;
772 207x if (reg->next_in_table)
773 16x reg->next_in_table->prev_in_table = reg->prev_in_table;
774
775 207x --state->registration_count[signal_number];
776 207x --registration_count_[signal_number];
777
778 207x delete reg;
779 207x }
780
781 268x if (first_error)
782 1x return first_error;
783 267x return {};
784 268x }
785
786 inline void
787 270x posix_signal_service::cancel_wait(posix_signal& impl)
788 {
789 270x bool was_waiting = false;
790 270x signal_op* op = nullptr;
791
792 {
793 270x std::lock_guard lock(mutex_);
794 270x impl.cancelled_ = true;
795 270x if (impl.waiting_)
796 {
797 7x was_waiting = true;
798 7x impl.waiting_ = false;
799 7x op = &impl.pending_op_;
800 }
801 270x }
802
803 270x if (was_waiting)
804 {
805 7x if (op->ec_out)
806 7x *op->ec_out = make_error_code(capy::error::canceled);
807 7x if (op->signal_out)
808 7x *op->signal_out = 0;
809 7x op->cont.h = op->h;
810 7x op->d.post(op->cont);
811 7x sched_->work_finished();
812 }
813 270x }
814
815 inline void
816 488x posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
817 {
818 {
819 488x std::lock_guard lock(mutex_);
820
821 // Check if cancel() was called before this wait started
822 488x if (impl.cancelled_)
823 {
824 3x impl.cancelled_ = false;
825 3x if (op->ec_out)
826 3x *op->ec_out = make_error_code(capy::error::canceled);
827 3x if (op->signal_out)
828 3x *op->signal_out = 0;
829 3x op->cont.h = op->h;
830 3x op->d.post(op->cont);
831 3x return;
832 }
833
834 // Check for queued signals first (signal arrived before wait started)
835 485x signal_registration* reg = impl.signals_;
836 972x while (reg)
837 {
838 490x if (reg->undelivered > 0)
839 {
840 3x --reg->undelivered;
841 3x op->signal_number = reg->signal_number;
842 // svc=nullptr: no work_finished needed since we never called work_started
843 3x op->svc = nullptr;
844 3x sched_->post(op);
845 3x return;
846 }
847 487x reg = reg->next_in_set;
848 }
849
850 // No queued signals - wait for delivery
851 482x impl.waiting_ = true;
852 // svc=this: signal_op::operator() will call work_finished() to balance this
853 482x op->svc = this;
854 482x sched_->work_started();
855 488x }
856 }
857
858 inline void
859 463x posix_signal_service::deliver_signal(int signal_number)
860 {
861 463x if (signal_number < 0 || signal_number >= max_signal_number)
862 return;
863
864 posix_signal_detail::signal_state* state =
865 463x posix_signal_detail::get_signal_state();
866 463x std::lock_guard lock(state->mutex);
867
868 463x posix_signal_service* service = state->service_list;
869 926x while (service)
870 {
871 463x std::lock_guard svc_lock(service->mutex_);
872
873 463x signal_registration* reg = service->registrations_[signal_number];
874 932x while (reg)
875 {
876 469x posix_signal* impl = static_cast<posix_signal*>(reg->owner);
877
878 469x if (impl->waiting_)
879 {
880 466x impl->waiting_ = false;
881 466x impl->pending_op_.signal_number = signal_number;
882 466x service->post(&impl->pending_op_);
883 }
884 else
885 {
886 3x ++reg->undelivered;
887 }
888
889 469x reg = reg->next_in_table;
890 }
891
892 463x service = service->next_;
893 463x }
894 463x }
895
896 inline void
897 posix_signal_service::work_started() noexcept
898 {
899 sched_->work_started();
900 }
901
902 inline void
903 466x posix_signal_service::work_finished() noexcept
904 {
905 466x sched_->work_finished();
906 466x }
907
908 inline void
909 466x posix_signal_service::post(signal_op* op)
910 {
911 466x sched_->post(op);
912 466x }
913
914 inline void
915 3295x posix_signal_service::add_service(posix_signal_service* service)
916 {
917 posix_signal_detail::signal_state* state =
918 3295x posix_signal_detail::get_signal_state();
919 3295x std::lock_guard lock(state->mutex);
920
921 3295x service->next_ = state->service_list;
922 3295x service->prev_ = nullptr;
923 3295x if (state->service_list)
924 16x state->service_list->prev_ = service;
925 3295x state->service_list = service;
926 3295x }
927
928 inline void
929 3295x posix_signal_service::remove_service(posix_signal_service* service)
930 {
931 posix_signal_detail::signal_state* state =
932 3295x posix_signal_detail::get_signal_state();
933 3295x std::lock_guard lock(state->mutex);
934
935 3295x if (service->next_ || service->prev_ || state->service_list == service)
936 {
937 3295x if (state->service_list == service)
938 3292x state->service_list = service->next_;
939 3295x if (service->prev_)
940 3x service->prev_->next_ = service->next_;
941 3295x if (service->next_)
942 13x service->next_->prev_ = service->prev_;
943 3295x service->next_ = nullptr;
944 3295x service->prev_ = nullptr;
945 }
946 3295x }
947
948 // get_signal_service - factory function
949
950 inline posix_signal_service&
951 3295x get_signal_service(capy::execution_context& ctx, scheduler& sched)
952 {
953 3295x 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