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