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