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 : /** An asynchronous signal set for coroutine I/O.
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 880 : friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
131 : {
132 : return static_cast<flags_t>(
133 880 : 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 will own 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 will own 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 62 : signal_set(capy::execution_context& ctx, int signal, Signals... signals)
211 62 : : signal_set(ctx)
212 : {
213 80 : auto check = [](std::error_code ec) {
214 80 : if (ec)
215 MIS 0 : throw std::system_error(ec);
216 : };
217 HIT 62 : check(add(signal));
218 15 : (check(add(signals)), ...);
219 62 : }
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 will own 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 will own 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 : signal_set(signal_set const&) = delete;
282 : signal_set& operator=(signal_set const&) = delete;
283 :
284 : /** Add a signal to the signal set.
285 :
286 : This function adds the specified signal to the set with the
287 : specified flags. It has no effect if the signal is already
288 : in the set with the same flags.
289 :
290 : If the signal is already registered globally (by another
291 : signal_set) and the flags differ, an error is returned
292 : unless one of them has the `dont_care` flag.
293 :
294 : The first signal registration on an execution context
295 : installs the process signal-delivery pipe; if that
296 : installation fails the error is returned, and the next
297 : call retries it.
298 :
299 : @param signal_number The signal to be added to the set.
300 : @param flags The flags to apply when registering the signal.
301 : On POSIX systems, these map to sigaction() flags.
302 : On Windows, only `none` and `dont_care` are supported;
303 : other flags cause `errc::operation_not_supported` to
304 : be returned.
305 :
306 : @return Success, or an error if the signal could not be added.
307 : Returns `errc::invalid_argument` if the signal is already
308 : registered with different flags.
309 : */
310 : [[nodiscard]] std::error_code add(int signal_number, flags_t flags);
311 :
312 : /** Add a signal to the signal set with default flags.
313 :
314 : This is equivalent to calling `add(signal_number, none)`.
315 :
316 : @param signal_number The signal to be added to the set.
317 :
318 : @return Success, or an error if the signal could not be added.
319 : */
320 145 : [[nodiscard]] std::error_code add(int signal_number)
321 : {
322 145 : return add(signal_number, none);
323 : }
324 :
325 : /** Remove a signal from the signal set.
326 :
327 : This function removes the specified signal from the set. It has
328 : no effect if the signal is not in the set.
329 :
330 : @param signal_number The signal to be removed from the set.
331 :
332 : @return Success, or an error if the signal could not be removed.
333 : */
334 : [[nodiscard]] std::error_code remove(int signal_number);
335 :
336 : /** Remove all signals from the signal set.
337 :
338 : This function removes all signals from the set. It has no effect
339 : if the set is already empty.
340 :
341 : @return Success, or an error if resetting any signal handler fails.
342 : */
343 : [[nodiscard]] std::error_code clear();
344 :
345 : protected:
346 : explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
347 :
348 : private:
349 : void do_cancel() noexcept override;
350 :
351 253 : implementation& get() const noexcept
352 : {
353 253 : return *static_cast<implementation*>(h_.get());
354 : }
355 : };
356 :
357 : } // namespace boost::corosio
358 :
359 : #endif
|