TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
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_SIGNAL_SET_HPP
12 : #define BOOST_COROSIO_SIGNAL_SET_HPP
13 :
14 : #include <boost/corosio/detail/config.hpp>
15 : #include <boost/corosio/io/io_signal_set.hpp>
16 : #include <boost/capy/ex/execution_context.hpp>
17 : #include <boost/capy/concept/executor.hpp>
18 :
19 : #include <concepts>
20 : #include <system_error>
21 :
22 : /*
23 : Signal Set Public API
24 : =====================
25 :
26 : This header provides the public interface for asynchronous signal handling.
27 : The implementation is split across platform-specific files:
28 : - posix/signals.cpp: Uses sigaction() for robust signal handling
29 : - iocp/signals.cpp: Uses C runtime signal() (Windows lacks sigaction)
30 :
31 : Key design decisions:
32 :
33 : 1. Abstract flag values: The flags_t enum uses arbitrary bit positions
34 : (not SA_RESTART, etc.) to avoid including <signal.h> in public headers.
35 : The POSIX implementation maps these to actual SA_* constants internally.
36 :
37 : 2. Flag conflict detection: When multiple signal_sets register for the
38 : same signal, they must use compatible flags. The first registration
39 : establishes the flags; subsequent registrations must match or use
40 : dont_care.
41 :
42 : 3. Polymorphic implementation: implementation is an abstract base that
43 : platform-specific implementations (posix_signal, win_signal)
44 : derive from. This allows the public API to be platform-agnostic.
45 :
46 : 4. The inline add(int) overload avoids a virtual call for the common case
47 : of adding signals without flags (delegates to add(int, none)).
48 : */
49 :
50 : namespace boost::corosio {
51 :
52 : /** An asynchronous signal set for coroutine I/O.
53 :
54 : This class provides the ability to perform an asynchronous wait
55 : for one or more signals to occur. The signal set registers for
56 : signals using sigaction() on POSIX systems or the C runtime
57 : signal() function on Windows.
58 :
59 : @par Thread Safety
60 : Distinct objects: Safe.@n
61 : Shared objects: Unsafe. A signal_set must not have concurrent
62 : wait operations.
63 :
64 : @par Semantics
65 : Wraps platform signal handling (sigaction on POSIX, C runtime
66 : signal() on Windows). Operations dispatch to OS signal APIs
67 : via the io_context reactor.
68 :
69 : @par Supported Signals
70 : On Windows, the following signals are supported:
71 : SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGILL, SIGSEGV.
72 :
73 : @par Example
74 : @code
75 : signal_set signals(ctx, SIGINT, SIGTERM);
76 : auto [ec, signum] = co_await signals.wait();
77 : if (ec == capy::cond::canceled)
78 : {
79 : // Operation was cancelled via stop_token or cancel()
80 : }
81 : else if (!ec)
82 : {
83 : std::cout << "Received signal " << signum << std::endl;
84 : }
85 : @endcode
86 : */
87 : class BOOST_COROSIO_DECL signal_set : public io_signal_set
88 : {
89 : public:
90 : /** Flags for signal registration.
91 :
92 : These flags control the behavior of signal handling. Multiple
93 : flags can be combined using the bitwise OR operator.
94 :
95 : @note Flags only have effect on POSIX systems. On Windows,
96 : only `none` and `dont_care` are supported; other flags return
97 : `operation_not_supported`.
98 : */
99 : enum flags_t : unsigned
100 : {
101 : /// Use existing flags if signal is already registered.
102 : /// When adding a signal that's already registered by another
103 : /// signal_set, this flag indicates acceptance of whatever
104 : /// flags were used for the existing registration.
105 : dont_care = 1u << 16,
106 :
107 : /// No special flags.
108 : none = 0,
109 :
110 : /// Restart interrupted system calls.
111 : /// Equivalent to SA_RESTART on POSIX systems.
112 : restart = 1u << 0,
113 :
114 : /// Don't generate SIGCHLD when children stop.
115 : /// Equivalent to SA_NOCLDSTOP on POSIX systems.
116 : no_child_stop = 1u << 1,
117 :
118 : /// Don't create zombie processes on child termination.
119 : /// Equivalent to SA_NOCLDWAIT on POSIX systems.
120 : no_child_wait = 1u << 2,
121 :
122 : /// Don't block the signal while its handler runs.
123 : /// Equivalent to SA_NODEFER on POSIX systems.
124 : no_defer = 1u << 3,
125 :
126 : /// Reset handler to SIG_DFL after one invocation.
127 : /// Equivalent to SA_RESETHAND on POSIX systems.
128 : reset_handler = 1u << 4
129 : };
130 :
131 : /// Combine two flag values.
132 HIT 4 : friend constexpr flags_t operator|(flags_t a, flags_t b) noexcept
133 : {
134 : return static_cast<flags_t>(
135 4 : static_cast<unsigned>(a) | static_cast<unsigned>(b));
136 : }
137 :
138 : /// Mask two flag values.
139 448 : friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
140 : {
141 : return static_cast<flags_t>(
142 448 : static_cast<unsigned>(a) & static_cast<unsigned>(b));
143 : }
144 :
145 : /// Compound assignment OR.
146 2 : friend constexpr flags_t& operator|=(flags_t& a, flags_t b) noexcept
147 : {
148 2 : return a = a | b;
149 : }
150 :
151 : /// Compound assignment AND.
152 : friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
153 : {
154 : return a = a & b;
155 : }
156 :
157 : /// Bitwise NOT (complement).
158 : friend constexpr flags_t operator~(flags_t a) noexcept
159 : {
160 : return static_cast<flags_t>(~static_cast<unsigned>(a));
161 : }
162 :
163 : /** Define backend hooks for signal set operations.
164 :
165 : Platform backends derive from this to provide signal
166 : registration via sigaction (POSIX) or the C runtime
167 : signal() function (Windows).
168 : */
169 : struct implementation : io_signal_set::implementation
170 : {
171 : /** Register a signal with the given flags.
172 :
173 : @param signal_number The signal to register.
174 : @param flags Platform-specific signal handling flags.
175 :
176 : @return Error code on failure, empty on success.
177 : */
178 : virtual std::error_code add(int signal_number, flags_t flags) = 0;
179 :
180 : /** Unregister a signal.
181 :
182 : @param signal_number The signal to remove.
183 :
184 : @return Error code on failure, empty on success.
185 : */
186 : virtual std::error_code remove(int signal_number) = 0;
187 :
188 : /** Unregister all signals.
189 :
190 : @return Error code on failure, empty on success.
191 : */
192 : virtual std::error_code clear() = 0;
193 : };
194 :
195 : /** Destructor.
196 :
197 : Cancels any pending operations and releases signal resources.
198 : */
199 : ~signal_set() override;
200 :
201 : /** Construct an empty signal set.
202 :
203 : @param ctx The execution context that will own this signal set.
204 : */
205 : explicit signal_set(capy::execution_context& ctx);
206 :
207 : /** Construct a signal set with initial signals.
208 :
209 : @param ctx The execution context that will own this signal set.
210 : @param signal First signal number to add.
211 : @param signals Additional signal numbers to add.
212 :
213 : @throws std::system_error Thrown on failure.
214 : */
215 : template<std::convertible_to<int>... Signals>
216 36 : signal_set(capy::execution_context& ctx, int signal, Signals... signals)
217 36 : : signal_set(ctx)
218 : {
219 44 : auto check = [](std::error_code ec) {
220 44 : if (ec)
221 MIS 0 : throw std::system_error(ec);
222 : };
223 HIT 36 : check(add(signal));
224 6 : (check(add(signals)), ...);
225 36 : }
226 :
227 : /** Move constructor.
228 :
229 : Transfers ownership of the signal set resources.
230 :
231 : @param other The signal set to move from.
232 :
233 : @pre No awaitables returned by @p other's methods exist.
234 : @pre The execution context associated with @p other must
235 : outlive this signal set.
236 : */
237 : signal_set(signal_set&& other) noexcept;
238 :
239 : /** Move assignment operator.
240 :
241 : Closes any existing signal set and transfers ownership.
242 :
243 : @param other The signal set to move from.
244 :
245 : @pre No awaitables returned by either `*this` or @p other's
246 : methods exist.
247 : @pre The execution context associated with @p other must
248 : outlive this signal set.
249 :
250 : @return Reference to this signal set.
251 : */
252 : signal_set& operator=(signal_set&& other) noexcept;
253 :
254 : signal_set(signal_set const&) = delete;
255 : signal_set& operator=(signal_set const&) = delete;
256 :
257 : /** Add a signal to the signal set.
258 :
259 : This function adds the specified signal to the set with the
260 : specified flags. It has no effect if the signal is already
261 : in the set with the same flags.
262 :
263 : If the signal is already registered globally (by another
264 : signal_set) and the flags differ, an error is returned
265 : unless one of them has the `dont_care` flag.
266 :
267 : @param signal_number The signal to be added to the set.
268 : @param flags The flags to apply when registering the signal.
269 : On POSIX systems, these map to sigaction() flags.
270 : On Windows, flags are accepted but ignored.
271 :
272 : @return Success, or an error if the signal could not be added.
273 : Returns `errc::invalid_argument` if the signal is already
274 : registered with different flags.
275 : */
276 : std::error_code add(int signal_number, flags_t flags);
277 :
278 : /** Add a signal to the signal set with default flags.
279 :
280 : This is equivalent to calling `add(signal_number, none)`.
281 :
282 : @param signal_number The signal to be added to the set.
283 :
284 : @return Success, or an error if the signal could not be added.
285 : */
286 58 : std::error_code add(int signal_number)
287 : {
288 58 : return add(signal_number, none);
289 : }
290 :
291 : /** Remove a signal from the signal set.
292 :
293 : This function removes the specified signal from the set. It has
294 : no effect if the signal is not in the set.
295 :
296 : @param signal_number The signal to be removed from the set.
297 :
298 : @return Success, or an error if the signal could not be removed.
299 : */
300 : std::error_code remove(int signal_number);
301 :
302 : /** Remove all signals from the signal set.
303 :
304 : This function removes all signals from the set. It has no effect
305 : if the set is already empty.
306 :
307 : @return Success, or an error if resetting any signal handler fails.
308 : */
309 : std::error_code clear();
310 :
311 : protected:
312 : explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
313 :
314 : private:
315 : void do_cancel() override;
316 :
317 116 : implementation& get() const noexcept
318 : {
319 116 : return *static_cast<implementation*>(h_.get());
320 : }
321 : };
322 :
323 : } // namespace boost::corosio
324 :
325 : #endif
|