1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
// Copyright (c) 2026 Steve Gerbino
3  
// Copyright (c) 2026 Steve Gerbino
4  
//
4  
//
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
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)
6  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  
//
7  
//
8  
// Official repository: https://github.com/cppalliance/corosio
8  
// Official repository: https://github.com/cppalliance/corosio
9  
//
9  
//
10  

10  

11  
#ifndef BOOST_COROSIO_SIGNAL_SET_HPP
11  
#ifndef BOOST_COROSIO_SIGNAL_SET_HPP
12  
#define BOOST_COROSIO_SIGNAL_SET_HPP
12  
#define BOOST_COROSIO_SIGNAL_SET_HPP
13  

13  

14  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/config.hpp>
15  
#include <boost/corosio/io/io_signal_set.hpp>
15  
#include <boost/corosio/io/io_signal_set.hpp>
16  
#include <boost/capy/ex/execution_context.hpp>
16  
#include <boost/capy/ex/execution_context.hpp>
17  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/executor.hpp>
18  

18  

19  
#include <concepts>
19  
#include <concepts>
20  
#include <system_error>
20  
#include <system_error>
21  

21  

22  
/*
22  
/*
23  
    Signal Set Public API
23  
    Signal Set Public API
24  
    =====================
24  
    =====================
25  

25  

26  
    This header provides the public interface for asynchronous signal handling.
26  
    This header provides the public interface for asynchronous signal handling.
27  
    The implementation is split across platform-specific files:
27  
    The implementation is split across platform-specific files:
28  
      - posix/signals.cpp: Uses sigaction() for robust signal handling
28  
      - posix/signals.cpp: Uses sigaction() for robust signal handling
29  
      - iocp/signals.cpp: Uses C runtime signal() (Windows lacks sigaction)
29  
      - iocp/signals.cpp: Uses C runtime signal() (Windows lacks sigaction)
30  

30  

31  
    Key design decisions:
31  
    Key design decisions:
32  

32  

33  
    1. Abstract flag values: The flags_t enum uses arbitrary bit positions
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.
34  
       (not SA_RESTART, etc.) to avoid including <signal.h> in public headers.
35  
       The POSIX implementation maps these to actual SA_* constants internally.
35  
       The POSIX implementation maps these to actual SA_* constants internally.
36  

36  

37  
    2. Flag conflict detection: When multiple signal_sets register for the
37  
    2. Flag conflict detection: When multiple signal_sets register for the
38  
       same signal, they must use compatible flags. The first registration
38  
       same signal, they must use compatible flags. The first registration
39  
       establishes the flags; subsequent registrations must match or use
39  
       establishes the flags; subsequent registrations must match or use
40  
       dont_care.
40  
       dont_care.
41  

41  

42  
    3. Polymorphic implementation: implementation is an abstract base that
42  
    3. Polymorphic implementation: implementation is an abstract base that
43  
       platform-specific implementations (posix_signal, win_signal)
43  
       platform-specific implementations (posix_signal, win_signal)
44  
       derive from. This allows the public API to be platform-agnostic.
44  
       derive from. This allows the public API to be platform-agnostic.
45  

45  

46  
    4. The inline add(int) overload avoids a virtual call for the common case
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)).
47  
       of adding signals without flags (delegates to add(int, none)).
48  
*/
48  
*/
49  

49  

50  
namespace boost::corosio {
50  
namespace boost::corosio {
51  

51  

52  
/** An asynchronous signal set for coroutine I/O.
52  
/** An asynchronous signal set for coroutine I/O.
53  

53  

54  
    This class provides the ability to perform an asynchronous wait
54  
    This class provides the ability to perform an asynchronous wait
55  
    for one or more signals to occur. The signal set registers for
55  
    for one or more signals to occur. The signal set registers for
56  
    signals using sigaction() on POSIX systems or the C runtime
56  
    signals using sigaction() on POSIX systems or the C runtime
57  
    signal() function on Windows.
57  
    signal() function on Windows.
58  

58  

59  
    @par Thread Safety
59  
    @par Thread Safety
60  
    Distinct objects: Safe.@n
60  
    Distinct objects: Safe.@n
61  
    Shared objects: Unsafe. A signal_set must not have concurrent
61  
    Shared objects: Unsafe. A signal_set must not have concurrent
62  
    wait operations.
62  
    wait operations.
63  

63  

64  
    @par Semantics
64  
    @par Semantics
65  
    Wraps platform signal handling (sigaction on POSIX, C runtime
65  
    Wraps platform signal handling (sigaction on POSIX, C runtime
66  
    signal() on Windows). Operations dispatch to OS signal APIs
66  
    signal() on Windows). Operations dispatch to OS signal APIs
67  
    via the io_context reactor.
67  
    via the io_context reactor.
68  

68  

69  
    @par Supported Signals
69  
    @par Supported Signals
70  
    On Windows, the following signals are supported:
70  
    On Windows, the following signals are supported:
71  
    SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGILL, SIGSEGV.
71  
    SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGILL, SIGSEGV.
72  

72  

73  
    @par Example
73  
    @par Example
74  
    @code
74  
    @code
75  
    signal_set signals(ctx, SIGINT, SIGTERM);
75  
    signal_set signals(ctx, SIGINT, SIGTERM);
76  
    auto [ec, signum] = co_await signals.wait();
76  
    auto [ec, signum] = co_await signals.wait();
77  
    if (ec == capy::cond::canceled)
77  
    if (ec == capy::cond::canceled)
78  
    {
78  
    {
79  
        // Operation was cancelled via stop_token or cancel()
79  
        // Operation was cancelled via stop_token or cancel()
80  
    }
80  
    }
81  
    else if (!ec)
81  
    else if (!ec)
82  
    {
82  
    {
83  
        std::cout << "Received signal " << signum << std::endl;
83  
        std::cout << "Received signal " << signum << std::endl;
84  
    }
84  
    }
85  
    @endcode
85  
    @endcode
86  
*/
86  
*/
87  
class BOOST_COROSIO_DECL signal_set : public io_signal_set
87  
class BOOST_COROSIO_DECL signal_set : public io_signal_set
88  
{
88  
{
89  
public:
89  
public:
90  
    /** Flags for signal registration.
90  
    /** Flags for signal registration.
91  

91  

92  
        These flags control the behavior of signal handling. Multiple
92  
        These flags control the behavior of signal handling. Multiple
93  
        flags can be combined using the bitwise OR operator.
93  
        flags can be combined using the bitwise OR operator.
94  

94  

95  
        @note Flags only have effect on POSIX systems. On Windows,
95  
        @note Flags only have effect on POSIX systems. On Windows,
96  
        only `none` and `dont_care` are supported; other flags return
96  
        only `none` and `dont_care` are supported; other flags return
97  
        `operation_not_supported`.
97  
        `operation_not_supported`.
98  
    */
98  
    */
99  
    enum flags_t : unsigned
99  
    enum flags_t : unsigned
100  
    {
100  
    {
101  
        /// Use existing flags if signal is already registered.
101  
        /// Use existing flags if signal is already registered.
102  
        /// When adding a signal that's already registered by another
102  
        /// When adding a signal that's already registered by another
103  
        /// signal_set, this flag indicates acceptance of whatever
103  
        /// signal_set, this flag indicates acceptance of whatever
104  
        /// flags were used for the existing registration.
104  
        /// flags were used for the existing registration.
105  
        dont_care = 1u << 16,
105  
        dont_care = 1u << 16,
106  

106  

107  
        /// No special flags.
107  
        /// No special flags.
108  
        none = 0,
108  
        none = 0,
109  

109  

110  
        /// Restart interrupted system calls.
110  
        /// Restart interrupted system calls.
111  
        /// Equivalent to SA_RESTART on POSIX systems.
111  
        /// Equivalent to SA_RESTART on POSIX systems.
112  
        restart = 1u << 0,
112  
        restart = 1u << 0,
113  

113  

114  
        /// Don't generate SIGCHLD when children stop.
114  
        /// Don't generate SIGCHLD when children stop.
115  
        /// Equivalent to SA_NOCLDSTOP on POSIX systems.
115  
        /// Equivalent to SA_NOCLDSTOP on POSIX systems.
116  
        no_child_stop = 1u << 1,
116  
        no_child_stop = 1u << 1,
117  

117  

118  
        /// Don't create zombie processes on child termination.
118  
        /// Don't create zombie processes on child termination.
119  
        /// Equivalent to SA_NOCLDWAIT on POSIX systems.
119  
        /// Equivalent to SA_NOCLDWAIT on POSIX systems.
120  
        no_child_wait = 1u << 2,
120  
        no_child_wait = 1u << 2,
121  

121  

122  
        /// Don't block the signal while its handler runs.
122  
        /// Don't block the signal while its handler runs.
123  
        /// Equivalent to SA_NODEFER on POSIX systems.
123  
        /// Equivalent to SA_NODEFER on POSIX systems.
124  
        no_defer = 1u << 3,
124  
        no_defer = 1u << 3,
125  

125  

126  
        /// Reset handler to SIG_DFL after one invocation.
126  
        /// Reset handler to SIG_DFL after one invocation.
127  
        /// Equivalent to SA_RESETHAND on POSIX systems.
127  
        /// Equivalent to SA_RESETHAND on POSIX systems.
128  
        reset_handler = 1u << 4
128  
        reset_handler = 1u << 4
129  
    };
129  
    };
130  

130  

131  
    /// Combine two flag values.
131  
    /// Combine two flag values.
132  
    friend constexpr flags_t operator|(flags_t a, flags_t b) noexcept
132  
    friend constexpr flags_t operator|(flags_t a, flags_t b) noexcept
133  
    {
133  
    {
134  
        return static_cast<flags_t>(
134  
        return static_cast<flags_t>(
135  
            static_cast<unsigned>(a) | static_cast<unsigned>(b));
135  
            static_cast<unsigned>(a) | static_cast<unsigned>(b));
136  
    }
136  
    }
137  

137  

138  
    /// Mask two flag values.
138  
    /// Mask two flag values.
139  
    friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
139  
    friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
140  
    {
140  
    {
141  
        return static_cast<flags_t>(
141  
        return static_cast<flags_t>(
142  
            static_cast<unsigned>(a) & static_cast<unsigned>(b));
142  
            static_cast<unsigned>(a) & static_cast<unsigned>(b));
143  
    }
143  
    }
144  

144  

145  
    /// Compound assignment OR.
145  
    /// Compound assignment OR.
146  
    friend constexpr flags_t& operator|=(flags_t& a, flags_t b) noexcept
146  
    friend constexpr flags_t& operator|=(flags_t& a, flags_t b) noexcept
147  
    {
147  
    {
148  
        return a = a | b;
148  
        return a = a | b;
149  
    }
149  
    }
150  

150  

151  
    /// Compound assignment AND.
151  
    /// Compound assignment AND.
152  
    friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
152  
    friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
153  
    {
153  
    {
154  
        return a = a & b;
154  
        return a = a & b;
155  
    }
155  
    }
156  

156  

157  
    /// Bitwise NOT (complement).
157  
    /// Bitwise NOT (complement).
158  
    friend constexpr flags_t operator~(flags_t a) noexcept
158  
    friend constexpr flags_t operator~(flags_t a) noexcept
159  
    {
159  
    {
160  
        return static_cast<flags_t>(~static_cast<unsigned>(a));
160  
        return static_cast<flags_t>(~static_cast<unsigned>(a));
161  
    }
161  
    }
162  

162  

163  
    /** Define backend hooks for signal set operations.
163  
    /** Define backend hooks for signal set operations.
164  

164  

165  
        Platform backends derive from this to provide signal
165  
        Platform backends derive from this to provide signal
166  
        registration via sigaction (POSIX) or the C runtime
166  
        registration via sigaction (POSIX) or the C runtime
167  
        signal() function (Windows).
167  
        signal() function (Windows).
168  
    */
168  
    */
169  
    struct implementation : io_signal_set::implementation
169  
    struct implementation : io_signal_set::implementation
170  
    {
170  
    {
171  
        /** Register a signal with the given flags.
171  
        /** Register a signal with the given flags.
172  

172  

173  
            @param signal_number The signal to register.
173  
            @param signal_number The signal to register.
174  
            @param flags Platform-specific signal handling flags.
174  
            @param flags Platform-specific signal handling flags.
175  

175  

176  
            @return Error code on failure, empty on success.
176  
            @return Error code on failure, empty on success.
177  
        */
177  
        */
178  
        virtual std::error_code add(int signal_number, flags_t flags) = 0;
178  
        virtual std::error_code add(int signal_number, flags_t flags) = 0;
179  

179  

180  
        /** Unregister a signal.
180  
        /** Unregister a signal.
181  

181  

182  
            @param signal_number The signal to remove.
182  
            @param signal_number The signal to remove.
183  

183  

184  
            @return Error code on failure, empty on success.
184  
            @return Error code on failure, empty on success.
185  
        */
185  
        */
186  
        virtual std::error_code remove(int signal_number) = 0;
186  
        virtual std::error_code remove(int signal_number) = 0;
187  

187  

188  
        /** Unregister all signals.
188  
        /** Unregister all signals.
189  

189  

190  
            @return Error code on failure, empty on success.
190  
            @return Error code on failure, empty on success.
191  
        */
191  
        */
192  
        virtual std::error_code clear() = 0;
192  
        virtual std::error_code clear() = 0;
193  
    };
193  
    };
194  

194  

195  
    /** Destructor.
195  
    /** Destructor.
196  

196  

197  
        Cancels any pending operations and releases signal resources.
197  
        Cancels any pending operations and releases signal resources.
198  
    */
198  
    */
199  
    ~signal_set() override;
199  
    ~signal_set() override;
200  

200  

201  
    /** Construct an empty signal set.
201  
    /** Construct an empty signal set.
202  

202  

203  
        @param ctx The execution context that will own this signal set.
203  
        @param ctx The execution context that will own this signal set.
204  
    */
204  
    */
205  
    explicit signal_set(capy::execution_context& ctx);
205  
    explicit signal_set(capy::execution_context& ctx);
206  

206  

207  
    /** Construct a signal set with initial signals.
207  
    /** Construct a signal set with initial signals.
208  

208  

209  
        @param ctx The execution context that will own this signal set.
209  
        @param ctx The execution context that will own this signal set.
210  
        @param signal First signal number to add.
210  
        @param signal First signal number to add.
211  
        @param signals Additional signal numbers to add.
211  
        @param signals Additional signal numbers to add.
212  

212  

213  
        @throws std::system_error Thrown on failure.
213  
        @throws std::system_error Thrown on failure.
214  
    */
214  
    */
215  
    template<std::convertible_to<int>... Signals>
215  
    template<std::convertible_to<int>... Signals>
216  
    signal_set(capy::execution_context& ctx, int signal, Signals... signals)
216  
    signal_set(capy::execution_context& ctx, int signal, Signals... signals)
217  
        : signal_set(ctx)
217  
        : signal_set(ctx)
218  
    {
218  
    {
219  
        auto check = [](std::error_code ec) {
219  
        auto check = [](std::error_code ec) {
220  
            if (ec)
220  
            if (ec)
221  
                throw std::system_error(ec);
221  
                throw std::system_error(ec);
222  
        };
222  
        };
223  
        check(add(signal));
223  
        check(add(signal));
224  
        (check(add(signals)), ...);
224  
        (check(add(signals)), ...);
225  
    }
225  
    }
226  

226  

227  
    /** Move constructor.
227  
    /** Move constructor.
228  

228  

229  
        Transfers ownership of the signal set resources.
229  
        Transfers ownership of the signal set resources.
230  

230  

231  
        @param other The signal set to move from.
231  
        @param other The signal set to move from.
232  

232  

233  
        @pre No awaitables returned by @p other's methods exist.
233  
        @pre No awaitables returned by @p other's methods exist.
234  
        @pre The execution context associated with @p other must
234  
        @pre The execution context associated with @p other must
235  
            outlive this signal set.
235  
            outlive this signal set.
236  
    */
236  
    */
237  
    signal_set(signal_set&& other) noexcept;
237  
    signal_set(signal_set&& other) noexcept;
238  

238  

239  
    /** Move assignment operator.
239  
    /** Move assignment operator.
240  

240  

241  
        Closes any existing signal set and transfers ownership.
241  
        Closes any existing signal set and transfers ownership.
242  

242  

243  
        @param other The signal set to move from.
243  
        @param other The signal set to move from.
244  

244  

245  
        @pre No awaitables returned by either `*this` or @p other's
245  
        @pre No awaitables returned by either `*this` or @p other's
246  
            methods exist.
246  
            methods exist.
247  
        @pre The execution context associated with @p other must
247  
        @pre The execution context associated with @p other must
248  
            outlive this signal set.
248  
            outlive this signal set.
249  

249  

250  
        @return Reference to this signal set.
250  
        @return Reference to this signal set.
251  
    */
251  
    */
252  
    signal_set& operator=(signal_set&& other) noexcept;
252  
    signal_set& operator=(signal_set&& other) noexcept;
253  

253  

254  
    signal_set(signal_set const&)            = delete;
254  
    signal_set(signal_set const&)            = delete;
255  
    signal_set& operator=(signal_set const&) = delete;
255  
    signal_set& operator=(signal_set const&) = delete;
256  

256  

257  
    /** Add a signal to the signal set.
257  
    /** Add a signal to the signal set.
258  

258  

259  
        This function adds the specified signal to the set with the
259  
        This function adds the specified signal to the set with the
260  
        specified flags. It has no effect if the signal is already
260  
        specified flags. It has no effect if the signal is already
261  
        in the set with the same flags.
261  
        in the set with the same flags.
262  

262  

263  
        If the signal is already registered globally (by another
263  
        If the signal is already registered globally (by another
264  
        signal_set) and the flags differ, an error is returned
264  
        signal_set) and the flags differ, an error is returned
265  
        unless one of them has the `dont_care` flag.
265  
        unless one of them has the `dont_care` flag.
266  

266  

267  
        @param signal_number The signal to be added to the set.
267  
        @param signal_number The signal to be added to the set.
268  
        @param flags The flags to apply when registering the signal.
268  
        @param flags The flags to apply when registering the signal.
269  
            On POSIX systems, these map to sigaction() flags.
269  
            On POSIX systems, these map to sigaction() flags.
270  
            On Windows, flags are accepted but ignored.
270  
            On Windows, flags are accepted but ignored.
271  

271  

272  
        @return Success, or an error if the signal could not be added.
272  
        @return Success, or an error if the signal could not be added.
273  
            Returns `errc::invalid_argument` if the signal is already
273  
            Returns `errc::invalid_argument` if the signal is already
274  
            registered with different flags.
274  
            registered with different flags.
275  
    */
275  
    */
276  
    std::error_code add(int signal_number, flags_t flags);
276  
    std::error_code add(int signal_number, flags_t flags);
277  

277  

278  
    /** Add a signal to the signal set with default flags.
278  
    /** Add a signal to the signal set with default flags.
279  

279  

280  
        This is equivalent to calling `add(signal_number, none)`.
280  
        This is equivalent to calling `add(signal_number, none)`.
281  

281  

282  
        @param signal_number The signal to be added to the set.
282  
        @param signal_number The signal to be added to the set.
283  

283  

284  
        @return Success, or an error if the signal could not be added.
284  
        @return Success, or an error if the signal could not be added.
285  
    */
285  
    */
286  
    std::error_code add(int signal_number)
286  
    std::error_code add(int signal_number)
287  
    {
287  
    {
288  
        return add(signal_number, none);
288  
        return add(signal_number, none);
289  
    }
289  
    }
290  

290  

291  
    /** Remove a signal from the signal set.
291  
    /** Remove a signal from the signal set.
292  

292  

293  
        This function removes the specified signal from the set. It has
293  
        This function removes the specified signal from the set. It has
294  
        no effect if the signal is not in the set.
294  
        no effect if the signal is not in the set.
295  

295  

296  
        @param signal_number The signal to be removed from the set.
296  
        @param signal_number The signal to be removed from the set.
297  

297  

298  
        @return Success, or an error if the signal could not be removed.
298  
        @return Success, or an error if the signal could not be removed.
299  
    */
299  
    */
300  
    std::error_code remove(int signal_number);
300  
    std::error_code remove(int signal_number);
301  

301  

302  
    /** Remove all signals from the signal set.
302  
    /** Remove all signals from the signal set.
303  

303  

304  
        This function removes all signals from the set. It has no effect
304  
        This function removes all signals from the set. It has no effect
305  
        if the set is already empty.
305  
        if the set is already empty.
306  

306  

307  
        @return Success, or an error if resetting any signal handler fails.
307  
        @return Success, or an error if resetting any signal handler fails.
308  
    */
308  
    */
309  
    std::error_code clear();
309  
    std::error_code clear();
310  

310  

311  
protected:
311  
protected:
312  
    explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
312  
    explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
313  

313  

314  
private:
314  
private:
315  
    void do_cancel() override;
315  
    void do_cancel() override;
316  

316  

317  
    implementation& get() const noexcept
317  
    implementation& get() const noexcept
318  
    {
318  
    {
319  
        return *static_cast<implementation*>(h_.get());
319  
        return *static_cast<implementation*>(h_.get());
320  
    }
320  
    }
321  
};
321  
};
322  

322  

323  
} // namespace boost::corosio
323  
} // namespace boost::corosio
324  

324  

325  
#endif
325  
#endif