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