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 945 : friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
131 : {
132 : return static_cast<flags_t>(
133 945 : 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 : @param ctx The execution context that owns this signal set.
201 : @param signal First signal number to add.
202 : @param signals Additional signal numbers to add.
203 :
204 : @throws std::system_error Thrown on failure.
205 :
206 : @see add for the non-throwing form: construct with the
207 : context alone, then `add()` each signal.
208 : */
209 : template<std::convertible_to<int>... Signals>
210 75 : signal_set(capy::execution_context& ctx, int signal, Signals... signals)
211 75 : : signal_set(ctx)
212 : {
213 93 : auto check = [](std::error_code ec) {
214 93 : if (ec)
215 MIS 0 : throw std::system_error(ec);
216 : };
217 HIT 75 : check(add(signal));
218 15 : (check(add(signals)), ...);
219 75 : }
220 :
221 : /** Construct an empty signal set from an executor.
222 :
223 : The signal set is associated with the executor's context.
224 :
225 : @param ex The executor whose context owns this signal set.
226 : */
227 : template<class Ex>
228 : requires(!std::same_as<std::remove_cvref_t<Ex>, signal_set>) &&
229 : capy::Executor<Ex>
230 2 : explicit signal_set(Ex const& ex) : signal_set(ex.context())
231 : {
232 2 : }
233 :
234 : /** Construct a signal set with initial signals from an executor.
235 :
236 : The signal set is associated with the executor's context.
237 :
238 : @param ex The executor whose context owns this signal set.
239 : @param signal First signal number to add.
240 : @param signals Additional signal numbers to add.
241 :
242 : @throws std::system_error Thrown on failure.
243 :
244 : @see add for the non-throwing form: construct with the
245 : executor alone, then `add()` each signal.
246 : */
247 : template<class Ex, std::convertible_to<int>... Signals>
248 : requires capy::Executor<Ex>
249 2 : signal_set(Ex const& ex, int signal, Signals... signals)
250 2 : : signal_set(ex.context(), signal, signals...)
251 : {
252 2 : }
253 :
254 : /** Move constructor.
255 :
256 : Transfers ownership of the signal set resources.
257 :
258 : @param other The signal set to move from.
259 :
260 : @pre No awaitables returned by @p other's methods exist.
261 : @pre The execution context associated with @p other must
262 : outlive this signal set.
263 : */
264 : signal_set(signal_set&& other) noexcept;
265 :
266 : /** Move assignment operator.
267 :
268 : Closes any existing signal set and transfers ownership.
269 :
270 : @param other The signal set to move from.
271 :
272 : @pre No awaitables returned by either `*this` or @p other's
273 : methods exist.
274 : @pre The execution context associated with @p other must
275 : outlive this signal set.
276 :
277 : @return Reference to this signal set.
278 : */
279 : signal_set& operator=(signal_set&& other) noexcept;
280 :
281 : /// Copy construction is disabled; the handle is uniquely owned.
282 : signal_set(signal_set const&) = delete;
283 : /// Copy assignment is disabled; the handle is uniquely owned.
284 : signal_set& operator=(signal_set const&) = delete;
285 :
286 : /** Add a signal to the signal set.
287 :
288 : This function adds the specified signal to the set with the
289 : specified flags. It has no effect if the signal is already
290 : in the set with the same flags.
291 :
292 : Another `signal_set` may already have registered the signal
293 : globally. If the flags then differ, an error is returned unless
294 : one of them has the `dont_care` flag.
295 :
296 : The first signal registration on an execution context installs
297 : the process signal-delivery pipe. If that installation fails,
298 : the error is returned. The next call retries it.
299 :
300 : @param signal_number The signal to be added to the set.
301 : @param flags The flags to apply when registering the signal.
302 : On POSIX systems, these map to sigaction() flags.
303 : On Windows, only `none` and `dont_care` are supported.
304 : Other flags cause `errc::operation_not_supported` to
305 : be returned.
306 :
307 : @return Success, or an error if the signal could not be added.
308 : Returns `errc::invalid_argument` if the signal is already
309 : registered with different flags.
310 : */
311 : [[nodiscard]] std::error_code add(int signal_number, flags_t flags);
312 :
313 : /** Add a signal to the signal set with default flags.
314 :
315 : This is equivalent to calling `add(signal_number, none)`.
316 :
317 : @param signal_number The signal to be added to the set.
318 :
319 : @return Success, or an error if the signal could not be added.
320 : */
321 158 : [[nodiscard]] std::error_code add(int signal_number)
322 : {
323 158 : return add(signal_number, none);
324 : }
325 :
326 : /** Remove a signal from the signal set.
327 :
328 : This function removes the specified signal from the set. It has
329 : no effect if the signal is not in the set.
330 :
331 : @param signal_number The signal to be removed from the set.
332 :
333 : @return Success, or an error if the signal could not be removed.
334 : */
335 : [[nodiscard]] std::error_code remove(int signal_number);
336 :
337 : /** Remove all signals from the signal set.
338 :
339 : This function removes all signals from the set. It has no effect
340 : if the set is already empty.
341 :
342 : @return Success, or an error if resetting any signal handler fails.
343 : */
344 : [[nodiscard]] std::error_code clear();
345 :
346 : protected:
347 : /** Adopt an existing handle.
348 :
349 : @param h The handle the signal set takes ownership of.
350 : */
351 : explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
352 :
353 : private:
354 : void do_cancel() noexcept override;
355 :
356 268 : implementation& get() const noexcept
357 : {
358 268 : return *static_cast<implementation*>(h_.get());
359 : }
360 : };
361 :
362 : } // namespace boost::corosio
363 :
364 : #endif
|