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 : co_return;
80 : if (!ec)
81 : std::cout << "Received signal " << signum << std::endl;
82 : @endcode
83 : */
84 : class BOOST_COROSIO_DECL signal_set : public io_signal_set
85 : {
86 : public:
87 : /** Flags for signal registration.
88 :
89 : These flags control the behavior of signal handling. Multiple
90 : flags can be combined using the bitwise OR operator.
91 :
92 : @note Flags only have effect on POSIX systems. On Windows,
93 : only `none` and `dont_care` are supported; other flags return
94 : `operation_not_supported`.
95 : */
96 : enum flags_t : unsigned
97 : {
98 : /// Use existing flags if signal is already registered.
99 : /// When adding a signal that's already registered by another
100 : /// signal_set, this flag indicates acceptance of whatever
101 : /// flags were used for the existing registration.
102 : dont_care = 1u << 16,
103 :
104 : /// No special flags.
105 : none = 0,
106 :
107 : /// Restart interrupted system calls.
108 : /// Equivalent to SA_RESTART on POSIX systems.
109 : restart = 1u << 0,
110 :
111 : /// Don't generate SIGCHLD when children stop.
112 : /// Equivalent to SA_NOCLDSTOP on POSIX systems.
113 : no_child_stop = 1u << 1,
114 :
115 : /// Don't create zombie processes on child termination.
116 : /// Equivalent to SA_NOCLDWAIT on POSIX systems.
117 : no_child_wait = 1u << 2,
118 :
119 : /// Don't block the signal while its handler runs.
120 : /// Equivalent to SA_NODEFER on POSIX systems.
121 : no_defer = 1u << 3,
122 :
123 : /// Reset handler to SIG_DFL after one invocation.
124 : /// Equivalent to SA_RESETHAND on POSIX systems.
125 : reset_handler = 1u << 4
126 : };
127 :
128 : /// Combine two flag values.
129 HIT 5 : friend constexpr flags_t operator|(flags_t a, flags_t b) noexcept
130 : {
131 : return static_cast<flags_t>(
132 5 : static_cast<unsigned>(a) | static_cast<unsigned>(b));
133 : }
134 :
135 : /// Mask two flag values.
136 659 : friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
137 : {
138 : return static_cast<flags_t>(
139 659 : static_cast<unsigned>(a) & static_cast<unsigned>(b));
140 : }
141 :
142 : /// Compound assignment OR.
143 2 : friend constexpr flags_t& operator|=(flags_t& a, flags_t b) noexcept
144 : {
145 2 : return a = a | b;
146 : }
147 :
148 : /// Compound assignment AND.
149 : friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
150 : {
151 : return a = a & b;
152 : }
153 :
154 : /// Bitwise NOT (complement).
155 : friend constexpr flags_t operator~(flags_t a) noexcept
156 : {
157 : return static_cast<flags_t>(~static_cast<unsigned>(a));
158 : }
159 :
160 : /** Define backend hooks for signal set operations.
161 :
162 : Platform backends derive from this to provide signal
163 : registration via sigaction (POSIX) or the C runtime
164 : signal() function (Windows).
165 : */
166 : struct implementation : io_signal_set::implementation
167 : {
168 : /** Register a signal with the given flags.
169 :
170 : @param signal_number The signal to register.
171 : @param flags Platform-specific signal handling flags.
172 :
173 : @return Error code on failure, empty on success.
174 : */
175 : virtual std::error_code add(int signal_number, flags_t flags) = 0;
176 :
177 : /** Unregister a signal.
178 :
179 : @param signal_number The signal to remove.
180 :
181 : @return Error code on failure, empty on success.
182 : */
183 : virtual std::error_code remove(int signal_number) = 0;
184 :
185 : /** Unregister all signals.
186 :
187 : @return Error code on failure, empty on success.
188 : */
189 : virtual std::error_code clear() = 0;
190 : };
191 :
192 : /** Destructor.
193 :
194 : Cancels any pending operations and releases signal resources.
195 : */
196 : ~signal_set() override;
197 :
198 : /** Construct an empty signal set.
199 :
200 : @param ctx The execution context that will own this signal set.
201 : */
202 : explicit signal_set(capy::execution_context& ctx);
203 :
204 : /** Construct a signal set with initial signals.
205 :
206 : @param ctx The execution context that will own this signal set.
207 : @param signal First signal number to add.
208 : @param signals Additional signal numbers to add.
209 :
210 : @throws std::system_error Thrown on failure.
211 :
212 : @see add for the non-throwing form: construct with the
213 : context alone, then `add()` each signal.
214 : */
215 : template<std::convertible_to<int>... Signals>
216 60 : signal_set(capy::execution_context& ctx, int signal, Signals... signals)
217 60 : : signal_set(ctx)
218 : {
219 78 : auto check = [](std::error_code ec) {
220 78 : if (ec)
221 MIS 0 : throw std::system_error(ec);
222 : };
223 HIT 60 : check(add(signal));
224 15 : (check(add(signals)), ...);
225 60 : }
226 :
227 : /** Construct an empty signal set from an executor.
228 :
229 : The signal set is associated with the executor's context.
230 :
231 : @param ex The executor whose context will own this signal set.
232 : */
233 : template<class Ex>
234 : requires(!std::same_as<std::remove_cvref_t<Ex>, signal_set>) &&
235 : capy::Executor<Ex>
236 2 : explicit signal_set(Ex const& ex) : signal_set(ex.context())
237 : {
238 2 : }
239 :
240 : /** Construct a signal set with initial signals from an executor.
241 :
242 : The signal set is associated with the executor's context.
243 :
244 : @param ex The executor whose context will own this signal set.
245 : @param signal First signal number to add.
246 : @param signals Additional signal numbers to add.
247 :
248 : @throws std::system_error Thrown on failure.
249 :
250 : @see add for the non-throwing form: construct with the
251 : executor alone, then `add()` each signal.
252 : */
253 : template<class Ex, std::convertible_to<int>... Signals>
254 : requires capy::Executor<Ex>
255 2 : signal_set(Ex const& ex, int signal, Signals... signals)
256 2 : : signal_set(ex.context(), signal, signals...)
257 : {
258 2 : }
259 :
260 : /** Move constructor.
261 :
262 : Transfers ownership of the signal set resources.
263 :
264 : @param other The signal set to move from.
265 :
266 : @pre No awaitables returned by @p other's methods exist.
267 : @pre The execution context associated with @p other must
268 : outlive this signal set.
269 : */
270 : signal_set(signal_set&& other) noexcept;
271 :
272 : /** Move assignment operator.
273 :
274 : Closes any existing signal set and transfers ownership.
275 :
276 : @param other The signal set to move from.
277 :
278 : @pre No awaitables returned by either `*this` or @p other's
279 : methods exist.
280 : @pre The execution context associated with @p other must
281 : outlive this signal set.
282 :
283 : @return Reference to this signal set.
284 : */
285 : signal_set& operator=(signal_set&& other) noexcept;
286 :
287 : signal_set(signal_set const&) = delete;
288 : signal_set& operator=(signal_set const&) = delete;
289 :
290 : /** Add a signal to the signal set.
291 :
292 : This function adds the specified signal to the set with the
293 : specified flags. It has no effect if the signal is already
294 : in the set with the same flags.
295 :
296 : If the signal is already registered globally (by another
297 : signal_set) and the flags differ, an error is returned
298 : unless one of them has the `dont_care` flag.
299 :
300 : The first signal registration on an execution context
301 : installs the process signal-delivery pipe; if that
302 : installation fails the error is returned, and the next
303 : call retries it.
304 :
305 : @param signal_number The signal to be added to the set.
306 : @param flags The flags to apply when registering the signal.
307 : On POSIX systems, these map to sigaction() flags.
308 : On Windows, only `none` and `dont_care` are supported;
309 : other flags cause `errc::operation_not_supported` to
310 : be returned.
311 :
312 : @return Success, or an error if the signal could not be added.
313 : Returns `errc::invalid_argument` if the signal is already
314 : registered with different flags.
315 : */
316 : [[nodiscard]] std::error_code add(int signal_number, flags_t flags);
317 :
318 : /** Add a signal to the signal set with default flags.
319 :
320 : This is equivalent to calling `add(signal_number, none)`.
321 :
322 : @param signal_number The signal to be added to the set.
323 :
324 : @return Success, or an error if the signal could not be added.
325 : */
326 97 : [[nodiscard]] std::error_code add(int signal_number)
327 : {
328 97 : return add(signal_number, none);
329 : }
330 :
331 : /** Remove a signal from the signal set.
332 :
333 : This function removes the specified signal from the set. It has
334 : no effect if the signal is not in the set.
335 :
336 : @param signal_number The signal to be removed from the set.
337 :
338 : @return Success, or an error if the signal could not be removed.
339 : */
340 : [[nodiscard]] std::error_code remove(int signal_number);
341 :
342 : /** Remove all signals from the signal set.
343 :
344 : This function removes all signals from the set. It has no effect
345 : if the set is already empty.
346 :
347 : @return Success, or an error if resetting any signal handler fails.
348 : */
349 : [[nodiscard]] std::error_code clear();
350 :
351 : protected:
352 : explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
353 :
354 : private:
355 : void do_cancel() noexcept override;
356 :
357 170 : implementation& get() const noexcept
358 : {
359 170 : return *static_cast<implementation*>(h_.get());
360 : }
361 : };
362 :
363 : } // namespace boost::corosio
364 :
365 : #endif
|