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

94.9% Lines (336/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 141x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::flags_supported(boost::corosio::signal_set::flags_t) :278 162x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::to_sigaction_flags(boost::corosio::signal_set::flags_t) :290 126x 84.6% 82.0% boost::corosio::detail::posix_signal_detail::flags_compatible(boost::corosio::signal_set::flags_t, boost::corosio::signal_set::flags_t) :310 27x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::open_signal_pipe(boost::corosio::detail::posix_signal_detail::signal_state*) :330 162x 100.0% 97.0% boost::corosio::detail::posix_signal_detail::corosio_posix_signal_handler(int) :364 306x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::drain_signal_pipe() :380 306x 100.0% 100.0% boost::corosio::detail::signal_op::operator()() :396 308x 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 147x 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 323x 100.0% 100.0% boost::corosio::detail::posix_signal::add(int, boost::corosio::signal_set::flags_t) :460 166x 100.0% 100.0% boost::corosio::detail::posix_signal::remove(int) :466 10x 100.0% 100.0% boost::corosio::detail::posix_signal::clear() :472 151x 100.0% 100.0% boost::corosio::detail::posix_signal::cancel() :478 156x 100.0% 100.0% boost::corosio::detail::posix_signal_service::posix_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :485 1790x 100.0% 79.0% boost::corosio::detail::posix_signal_service::~posix_signal_service() :497 3580x 100.0% 100.0% boost::corosio::detail::posix_signal_service::shutdown() :503 1790x 100.0% 93.0% boost::corosio::detail::posix_signal_service::construct() :552 147x 100.0% 100.0% boost::corosio::detail::posix_signal_service::destroy_impl(boost::corosio::detail::posix_signal&) :565 141x 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 166x 98.2% 84.0% boost::corosio::detail::posix_signal_service::remove_signal(boost::corosio::detail::posix_signal&, int) :686 10x 87.9% 88.0% boost::corosio::detail::posix_signal_service::clear_signals(boost::corosio::detail::posix_signal&) :739 151x 96.7% 92.0% boost::corosio::detail::posix_signal_service::cancel_wait(boost::corosio::detail::posix_signal&) :787 156x 100.0% 100.0% boost::corosio::detail::posix_signal_service::start_wait(boost::corosio::detail::posix_signal&, boost::corosio::detail::signal_op*) :816 321x 79.2% 84.0% boost::corosio::detail::posix_signal_service::deliver_signal(int) :859 306x 90.0% 72.0% boost::corosio::detail::posix_signal_service::work_finished() :903 308x 100.0% 100.0% boost::corosio::detail::posix_signal_service::post(boost::corosio::detail::signal_op*) :909 308x 100.0% 100.0% boost::corosio::detail::posix_signal_service::add_service(boost::corosio::detail::posix_signal_service*) :915 1790x 100.0% 100.0% boost::corosio::detail::posix_signal_service::remove_service(boost::corosio::detail::posix_signal_service*) :929 1790x 92.3% 93.0% boost::corosio::detail::get_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :951 1790x 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 141x void destroy(io_object::implementation* p) override
169 {
170 141x auto& impl = static_cast<posix_signal&>(*p);
171 141x [[maybe_unused]] auto n = impl.clear();
172 141x impl.cancel();
173 141x destroy_impl(impl);
174 141x }
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 162x 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 162x return true;
285 }
286
287 // Map abstract flags to sigaction() flags.
288 // Caller must ensure flags_supported() returns true first.
289 inline int
290 126x to_sigaction_flags(signal_set::flags_t flags)
291 {
292 126x int sa_flags = 0;
293 126x if (flags & signal_set::restart)
294 23x sa_flags |= SA_RESTART;
295 126x if (flags & signal_set::no_child_stop)
296 1x sa_flags |= SA_NOCLDSTOP;
297 #ifdef SA_NOCLDWAIT
298 126x if (flags & signal_set::no_child_wait)
299 sa_flags |= SA_NOCLDWAIT;
300 #endif
301 126x if (flags & signal_set::no_defer)
302 4x sa_flags |= SA_NODEFER;
303 126x if (flags & signal_set::reset_handler)
304 sa_flags |= SA_RESETHAND;
305 126x return sa_flags;
306 }
307
308 // Check if two flag values are compatible
309 inline bool
310 27x flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
311 {
312 // dont_care is always compatible
313 52x if ((existing & signal_set::dont_care) ||
314 25x (requested & signal_set::dont_care))
315 7x return true;
316
317 // Mask out dont_care bit for comparison
318 20x constexpr auto mask = ~signal_set::dont_care;
319 20x 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 162x open_signal_pipe(signal_state* state)
331 {
332 162x if (state->read_fd >= 0)
333 148x 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 306x corosio_posix_signal_handler(int signal_number)
365 {
366 306x int saved_errno = errno;
367 306x signal_state* state = get_signal_state();
368 [[maybe_unused]] ssize_t r =
369 306x ::write(state->write_fd, &signal_number, sizeof(int));
370 306x errno = saved_errno;
371 // With sigaction(), the handler persists automatically (unlike some
372 // signal() implementations that reset to SIG_DFL).
373 306x }
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 306x drain_signal_pipe()
381 {
382 306x signal_state* state = get_signal_state();
383 int signal_number;
384 612x while (::read(state->read_fd, &signal_number, sizeof(int)) ==
385 static_cast<ssize_t>(sizeof(int)))
386 {
387 306x posix_signal_service::deliver_signal(signal_number);
388 }
389 306x }
390
391 } // namespace posix_signal_detail
392
393 // signal_op implementation
394
395 inline void
396 308x signal_op::operator()()
397 {
398 308x if (ec_out)
399 308x *ec_out = {};
400 308x if (signal_out)
401 308x *signal_out = signal_number;
402
403 // Capture svc before resuming (coro may destroy us)
404 308x auto* service = svc;
405 308x svc = nullptr;
406
407 308x cont.h = h;
408 308x d.post(cont);
409
410 // Balance the work_started() from start_wait
411 308x if (service)
412 308x service->work_finished();
413 308x }
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 147x inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
424 147x : svc_(svc)
425 {
426 147x }
427
428 inline std::coroutine_handle<>
429 323x 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 323x pending_op_.h = h;
437 323x pending_op_.d = d;
438 323x pending_op_.ec_out = ec;
439 323x pending_op_.signal_out = signal_out;
440 323x pending_op_.signal_number = 0;
441
442 323x 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 321x svc_.start_wait(*this, &pending_op_);
455 // completion is always posted to scheduler queue, never inline.
456 321x return std::noop_coroutine();
457 }
458
459 inline std::error_code
460 166x posix_signal::add(int signal_number, signal_set::flags_t flags)
461 {
462 166x return svc_.add_signal(*this, signal_number, flags);
463 }
464
465 inline std::error_code
466 10x posix_signal::remove(int signal_number)
467 {
468 10x return svc_.remove_signal(*this, signal_number);
469 }
470
471 inline std::error_code
472 151x posix_signal::clear()
473 {
474 151x return svc_.clear_signals(*this);
475 }
476
477 inline void
478 156x posix_signal::cancel() noexcept
479 {
480 156x svc_.cancel_wait(*this);
481 156x }
482
483 // posix_signal_service implementation
484
485 1790x inline posix_signal_service::posix_signal_service(
486 1790x capy::execution_context&, scheduler& sched)
487 1790x : sched_(&sched)
488 {
489 116350x for (int i = 0; i < max_signal_number; ++i)
490 {
491 114560x registrations_[i] = nullptr;
492 114560x registration_count_[i] = 0;
493 }
494 1790x add_service(this);
495 1790x }
496
497 3580x inline posix_signal_service::~posix_signal_service()
498 {
499 1790x remove_service(this);
500 3580x }
501
502 inline void
503 1790x posix_signal_service::shutdown()
504 {
505 posix_signal_detail::signal_state* state =
506 1790x posix_signal_detail::get_signal_state();
507 1790x std::lock_guard state_lock(state->mutex);
508 1790x std::lock_guard lock(mutex_);
509
510 1796x 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 116350x for (int i = 0; i < max_signal_number; ++i)
548 114560x registrations_[i] = nullptr;
549 1790x }
550
551 inline io_object::implementation*
552 147x posix_signal_service::construct()
553 {
554 147x auto* impl = new posix_signal(*this);
555
556 {
557 147x std::lock_guard lock(mutex_);
558 147x impl_list_.push_back(impl);
559 147x }
560
561 147x return impl;
562 }
563
564 inline void
565 141x posix_signal_service::destroy_impl(posix_signal& impl)
566 {
567 {
568 141x std::lock_guard lock(mutex_);
569 141x impl_list_.remove(&impl);
570 141x }
571
572 141x delete &impl;
573 141x }
574
575 inline std::error_code
576 166x posix_signal_service::add_signal(
577 posix_signal& impl, int signal_number, signal_set::flags_t flags)
578 {
579 166x 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 162x 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 162x 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 162x std::lock_guard state_lock(state->mutex);
600 162x if (auto ec = posix_signal_detail::open_signal_pipe(state))
601 7x return ec;
602 162x }
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 155x std::lock_guard reg_lock(reader_mutex_);
608 155x if (!reader_registered_)
609 {
610 108x if (auto ec = sched_->register_signal_reader(state->read_fd))
611 2x return ec;
612 106x reader_registered_ = true;
613 }
614 155x }
615
616 153x std::lock_guard state_lock(state->mutex);
617 153x std::lock_guard lock(mutex_);
618
619 // Find insertion point (list is sorted by signal number)
620 153x signal_registration** insertion_point = &impl.signals_;
621 153x signal_registration* reg = impl.signals_;
622 174x while (reg && reg->signal_number < signal_number)
623 {
624 21x insertion_point = &reg->next_in_set;
625 21x 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 153x 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 140x if (state->registration_count[signal_number] > 0)
640 {
641 14x 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 138x auto* new_reg = new signal_registration;
647 138x new_reg->signal_number = signal_number;
648 138x new_reg->flags = flags;
649 138x new_reg->owner = &impl;
650 138x new_reg->undelivered = 0;
651
652 // Install signal handler on first global registration
653 138x if (state->registration_count[signal_number] == 0)
654 {
655 126x struct sigaction sa = {};
656 126x sa.sa_handler = posix_signal_detail::corosio_posix_signal_handler;
657 126x sigemptyset(&sa.sa_mask);
658 126x sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
659
660 126x 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 125x state->registered_flags[signal_number] = flags;
668 }
669
670 137x new_reg->next_in_set = reg;
671 137x *insertion_point = new_reg;
672
673 137x new_reg->next_in_table = registrations_[signal_number];
674 137x new_reg->prev_in_table = nullptr;
675 137x if (registrations_[signal_number])
676 10x registrations_[signal_number]->prev_in_table = new_reg;
677 137x registrations_[signal_number] = new_reg;
678
679 137x ++state->registration_count[signal_number];
680 137x ++registration_count_[signal_number];
681
682 137x return {};
683 153x }
684
685 inline std::error_code
686 10x posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
687 {
688 10x 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 8x posix_signal_detail::get_signal_state();
693 8x std::lock_guard state_lock(state->mutex);
694 8x std::lock_guard lock(mutex_);
695
696 8x signal_registration** deletion_point = &impl.signals_;
697 8x signal_registration* reg = impl.signals_;
698 8x while (reg && reg->signal_number < signal_number)
699 {
700 deletion_point = &reg->next_in_set;
701 reg = reg->next_in_set;
702 }
703
704 8x if (!reg || reg->signal_number != signal_number)
705 3x return {};
706
707 // Restore default handler on last global unregistration
708 5x if (state->registration_count[signal_number] == 1)
709 {
710 5x struct sigaction sa = {};
711 5x sa.sa_handler = SIG_DFL;
712 5x sigemptyset(&sa.sa_mask);
713 5x sa.sa_flags = 0;
714
715 5x if (::sigaction(signal_number, &sa, nullptr) < 0)
716 1x return make_error_code(std::errc::invalid_argument);
717
718 // Clear stored flags
719 4x state->registered_flags[signal_number] = signal_set::none;
720 }
721
722 4x *deletion_point = reg->next_in_set;
723
724 4x if (registrations_[signal_number] == reg)
725 4x registrations_[signal_number] = reg->next_in_table;
726 4x if (reg->prev_in_table)
727 reg->prev_in_table->next_in_table = reg->next_in_table;
728 4x if (reg->next_in_table)
729 reg->next_in_table->prev_in_table = reg->prev_in_table;
730
731 4x --state->registration_count[signal_number];
732 4x --registration_count_[signal_number];
733
734 4x delete reg;
735 4x return {};
736 8x }
737
738 inline std::error_code
739 151x posix_signal_service::clear_signals(posix_signal& impl)
740 {
741 posix_signal_detail::signal_state* state =
742 151x posix_signal_detail::get_signal_state();
743 151x std::lock_guard state_lock(state->mutex);
744 151x std::lock_guard lock(mutex_);
745
746 151x std::error_code first_error;
747
748 278x while (signal_registration* reg = impl.signals_)
749 {
750 127x int signal_number = reg->signal_number;
751
752 127x if (state->registration_count[signal_number] == 1)
753 {
754 117x struct sigaction sa = {};
755 117x sa.sa_handler = SIG_DFL;
756 117x sigemptyset(&sa.sa_mask);
757 117x sa.sa_flags = 0;
758
759 117x 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 117x state->registered_flags[signal_number] = signal_set::none;
764 }
765
766 127x impl.signals_ = reg->next_in_set;
767
768 127x if (registrations_[signal_number] == reg)
769 127x registrations_[signal_number] = reg->next_in_table;
770 127x if (reg->prev_in_table)
771 reg->prev_in_table->next_in_table = reg->next_in_table;
772 127x if (reg->next_in_table)
773 10x reg->next_in_table->prev_in_table = reg->prev_in_table;
774
775 127x --state->registration_count[signal_number];
776 127x --registration_count_[signal_number];
777
778 127x delete reg;
779 127x }
780
781 151x if (first_error)
782 1x return first_error;
783 150x return {};
784 151x }
785
786 inline void
787 156x posix_signal_service::cancel_wait(posix_signal& impl)
788 {
789 156x bool was_waiting = false;
790 156x signal_op* op = nullptr;
791
792 {
793 156x std::lock_guard lock(mutex_);
794 156x impl.cancelled_ = true;
795 156x if (impl.waiting_)
796 {
797 5x was_waiting = true;
798 5x impl.waiting_ = false;
799 5x op = &impl.pending_op_;
800 }
801 156x }
802
803 156x 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 156x }
814
815 inline void
816 321x posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
817 {
818 {
819 321x std::lock_guard lock(mutex_);
820
821 // Check if cancel() was called before this wait started
822 321x 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 319x signal_registration* reg = impl.signals_;
836 642x while (reg)
837 {
838 323x if (reg->undelivered > 0)
839 {
840 --reg->undelivered;
841 op->signal_number = reg->signal_number;
842 // svc=nullptr: no work_finished needed since we never called work_started
843 op->svc = nullptr;
844 sched_->post(op);
845 return;
846 }
847 323x reg = reg->next_in_set;
848 }
849
850 // No queued signals - wait for delivery
851 319x impl.waiting_ = true;
852 // svc=this: signal_op::operator() will call work_finished() to balance this
853 319x op->svc = this;
854 319x sched_->work_started();
855 321x }
856 }
857
858 inline void
859 306x posix_signal_service::deliver_signal(int signal_number)
860 {
861 306x if (signal_number < 0 || signal_number >= max_signal_number)
862 return;
863
864 posix_signal_detail::signal_state* state =
865 306x posix_signal_detail::get_signal_state();
866 306x std::lock_guard lock(state->mutex);
867
868 306x posix_signal_service* service = state->service_list;
869 612x while (service)
870 {
871 306x std::lock_guard svc_lock(service->mutex_);
872
873 306x signal_registration* reg = service->registrations_[signal_number];
874 614x while (reg)
875 {
876 308x posix_signal* impl = static_cast<posix_signal*>(reg->owner);
877
878 308x if (impl->waiting_)
879 {
880 308x impl->waiting_ = false;
881 308x impl->pending_op_.signal_number = signal_number;
882 308x service->post(&impl->pending_op_);
883 }
884 else
885 {
886 ++reg->undelivered;
887 }
888
889 308x reg = reg->next_in_table;
890 }
891
892 306x service = service->next_;
893 306x }
894 306x }
895
896 inline void
897 posix_signal_service::work_started() noexcept
898 {
899 sched_->work_started();
900 }
901
902 inline void
903 308x posix_signal_service::work_finished() noexcept
904 {
905 308x sched_->work_finished();
906 308x }
907
908 inline void
909 308x posix_signal_service::post(signal_op* op)
910 {
911 308x sched_->post(op);
912 308x }
913
914 inline void
915 1790x posix_signal_service::add_service(posix_signal_service* service)
916 {
917 posix_signal_detail::signal_state* state =
918 1790x posix_signal_detail::get_signal_state();
919 1790x std::lock_guard lock(state->mutex);
920
921 1790x service->next_ = state->service_list;
922 1790x service->prev_ = nullptr;
923 1790x if (state->service_list)
924 7x state->service_list->prev_ = service;
925 1790x state->service_list = service;
926 1790x }
927
928 inline void
929 1790x posix_signal_service::remove_service(posix_signal_service* service)
930 {
931 posix_signal_detail::signal_state* state =
932 1790x posix_signal_detail::get_signal_state();
933 1790x std::lock_guard lock(state->mutex);
934
935 1790x if (service->next_ || service->prev_ || state->service_list == service)
936 {
937 1790x if (state->service_list == service)
938 1790x state->service_list = service->next_;
939 1790x if (service->prev_)
940 service->prev_->next_ = service->next_;
941 1790x if (service->next_)
942 7x service->next_->prev_ = service->prev_;
943 1790x service->next_ = nullptr;
944 1790x service->prev_ = nullptr;
945 }
946 1790x }
947
948 // get_signal_service - factory function
949
950 inline posix_signal_service&
951 1790x get_signal_service(capy::execution_context& ctx, scheduler& sched)
952 {
953 1790x 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