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

100.0% Lines (10 / 10) 100.0% Functions (2 / 2)
posix_signal.hpp
f(x) Functions (2)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
3 // Copyright (c) 2026 Michael Vandeberg
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/corosio
9 //
10
11 #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_HPP
12 #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_HPP
13
14 #include <boost/corosio/detail/platform.hpp>
15
16 #if BOOST_COROSIO_POSIX
17
18 #include <boost/corosio/detail/config.hpp>
19 #include <boost/corosio/signal_set.hpp>
20 #include <boost/corosio/detail/intrusive.hpp>
21 #include <boost/corosio/detail/object_ref.hpp>
22 #include <boost/corosio/detail/scheduler_op.hpp>
23 #include <boost/capy/continuation.hpp>
24 #include <boost/capy/ex/executor_ref.hpp>
25
26 #include <coroutine>
27 #include <cstddef>
28 #include <optional>
29 #include <stop_token>
30 #include <system_error>
31
32 namespace boost::corosio {
33
34 namespace detail {
35
36 // Forward declarations
37 class posix_signal_service;
38
39 // Maximum signal number supported (NSIG is typically 64 on Linux)
40 enum
41 {
42 max_signal_number = 64
43 };
44
45 // signal_op - pending wait operation
46
47 struct signal_op : scheduler_op
48 {
49 /// The awaitable's continuation; see coro_op::cont.
50 capy::continuation* cont = nullptr;
51 capy::executor_ref d;
52 /// Holds the owning set while this op sits in the scheduler queue.
53 detail::object_ref keep;
54 std::error_code* ec_out = nullptr;
55 int* signal_out = nullptr;
56 int signal_number = 0;
57 posix_signal_service* svc = nullptr; // For work_finished callback
58
59 void operator()() noexcept override;
60 void destroy() override;
61 };
62
63 // signal_registration - per-signal registration tracking
64
65 struct signal_registration
66 {
67 int signal_number = 0;
68 signal_set::flags_t flags = signal_set::none;
69 signal_set::implementation* owner = nullptr;
70 std::size_t undelivered = 0;
71 signal_registration* next_in_table = nullptr;
72 signal_registration* prev_in_table = nullptr;
73 signal_registration* next_in_set = nullptr;
74 };
75
76 // posix_signal - per-signal_set implementation
77
78 class posix_signal final
79 : public signal_set::implementation
80 , public intrusive_list<posix_signal>::node
81 {
82 friend class posix_signal_service;
83
84 posix_signal_service& svc_;
85 signal_registration* signals_ = nullptr;
86 signal_op pending_op_;
87 bool waiting_ = false;
88 bool cancelled_ = false;
89
90 /** Routes a stop request into the service's per-operation cancel.
91
92 Deliberately NOT `cancel_wait`: that sets the sticky `cancelled_`
93 latch, which belongs to `cancel()` and scopes to the whole set. A
94 stop token scopes to one wait, so a late fire must be a no-op
95 rather than poisoning the next wait.
96 */
97 struct token_canceller
98 {
99 posix_signal* self;
100 void operator()() const noexcept;
101 };
102
103 /** Armed for the duration of one wait; see wait().
104
105 Never reset while `posix_signal_service::mutex_` is held:
106 `~stop_callback` blocks until a concurrently running callback
107 returns, and that callback takes the same mutex.
108 */
109 std::optional<std::stop_callback<token_canceller>> stop_cb_;
110
111 /** Set when a stop request arrives for the current wait.
112
113 Closes a lost-wakeup race. The callback is armed before
114 `start_wait` takes the lock, so a request landing in that window
115 finds `waiting_ == false` and would otherwise return having done
116 nothing, leaving `start_wait` to park the wait forever. Every
117 other op survives this because `coro_op::on_cancel()` defaults to
118 `request_cancel()`, which sets a persistent flag the completion
119 decode reads later; this is that flag for the signal path.
120
121 Distinct from `cancelled_` on purpose: `cancelled_` is the sticky
122 per-set latch `cancel()` owns, this is per-operation.
123 */
124 bool token_cancelled_ = false;
125
126 public:
127 explicit posix_signal(posix_signal_service& svc) noexcept;
128
129 /// Recycle into the owning service's pool. Defined out-of-line
130 /// after posix_signal_service for its complete type.
131 void retire() noexcept override;
132
133 /** Reset for recycling.
134
135 `destroy()` already calls `clear()` (empties the registration
136 list) and `disarm_stop()` before the refcount reaches zero, so
137 those are asserted rather than re-cleared. `destroy()` also
138 calls `cancel()`, which deliberately latches `cancelled_` to
139 abort any wait still unwinding — that latch is this object's
140 to clear for its next, unrelated use, not something reset()
141 can skip by asserting it already false.
142
143 @pre refs_ == 0, registration list empty, no wait in flight.
144 */
145 72x void reuse() noexcept
146 {
147 72x BOOST_COROSIO_ASSERT(signals_ == nullptr);
148 72x BOOST_COROSIO_ASSERT(!waiting_);
149 72x BOOST_COROSIO_ASSERT(!stop_cb_);
150 72x cancelled_ = false;
151 72x token_cancelled_ = false;
152 72x }
153
154 std::coroutine_handle<> wait(
155 capy::continuation&,
156 capy::executor_ref,
157 std::stop_token,
158 std::error_code*,
159 int*) override;
160
161 std::error_code add(int signal_number, signal_set::flags_t flags) override;
162 std::error_code remove(int signal_number) override;
163 std::error_code clear() override;
164 void cancel() noexcept override;
165
166 /// Disarm the wait's stop callback. Called before teardown.
167 3476x void disarm_stop() noexcept
168 {
169 3476x stop_cb_.reset();
170 3476x }
171 };
172
173 } // namespace detail
174
175 } // namespace boost::corosio
176
177 #endif // BOOST_COROSIO_POSIX
178
179 #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_HPP
180