include/boost/corosio/signal_set.hpp

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