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

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