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

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