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  
//
4  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// 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)
6  
//
7  
//
7  
// Official repository: https://github.com/cppalliance/corosio
8  
// Official repository: https://github.com/cppalliance/corosio
8  
//
9  
//
9  

10  

10  
#ifndef BOOST_COROSIO_SIGNAL_SET_HPP
11  
#ifndef BOOST_COROSIO_SIGNAL_SET_HPP
11  
#define BOOST_COROSIO_SIGNAL_SET_HPP
12  
#define BOOST_COROSIO_SIGNAL_SET_HPP
12  

13  

13  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/config.hpp>
14 -
#include <boost/corosio/io_object.hpp>
15 +
#include <boost/corosio/io/io_signal_set.hpp>
15 -
#include <boost/capy/io_result.hpp>
 
16 -
#include <boost/capy/error.hpp>
 
17 -
#include <boost/capy/ex/executor_ref.hpp>
 
18 -
#include <boost/capy/ex/io_env.hpp>
 
19  
#include <boost/capy/ex/execution_context.hpp>
16  
#include <boost/capy/ex/execution_context.hpp>
20 -
#include <system_error>
 
21  
#include <boost/capy/concept/executor.hpp>
17  
#include <boost/capy/concept/executor.hpp>
22  

18  

23 -
#include <coroutine>
 
24 -
#include <stop_token>
 
25  
#include <concepts>
19  
#include <concepts>
26  
#include <system_error>
20  
#include <system_error>
27  

21  

28  
/*
22  
/*
29  
    Signal Set Public API
23  
    Signal Set Public API
30  
    =====================
24  
    =====================
31  

25  

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

30  

37  
    Key design decisions:
31  
    Key design decisions:
38  

32  

39  
    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
40  
       (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.
41  
       The POSIX implementation maps these to actual SA_* constants internally.
35  
       The POSIX implementation maps these to actual SA_* constants internally.
42  

36  

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

41  

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

45  

52  
    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
53  
       of adding signals without flags (delegates to add(int, none)).
47  
       of adding signals without flags (delegates to add(int, none)).
54  
*/
48  
*/
55  

49  

56  
namespace boost::corosio {
50  
namespace boost::corosio {
57  

51  

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

53  

60  
    This class provides the ability to perform an asynchronous wait
54  
    This class provides the ability to perform an asynchronous wait
61  
    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
62  
    signals using sigaction() on POSIX systems or the C runtime
56  
    signals using sigaction() on POSIX systems or the C runtime
63  
    signal() function on Windows.
57  
    signal() function on Windows.
64  

58  

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

63  

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

68  

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

72  

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

91  

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

94  

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

106  

113  
        /// No special flags.
107  
        /// No special flags.
114  
        none = 0,
108  
        none = 0,
115  

109  

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

113  

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

117  

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

121  

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

125  

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

130  

137  
    /// Combine two flag values.
131  
    /// Combine two flag values.
138  
    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
139  
    {
133  
    {
140  
        return static_cast<flags_t>(
134  
        return static_cast<flags_t>(
141  
            static_cast<unsigned>(a) | static_cast<unsigned>(b));
135  
            static_cast<unsigned>(a) | static_cast<unsigned>(b));
142  
    }
136  
    }
143  

137  

144  
    /// Mask two flag values.
138  
    /// Mask two flag values.
145  
    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
146  
    {
140  
    {
147  
        return static_cast<flags_t>(
141  
        return static_cast<flags_t>(
148  
            static_cast<unsigned>(a) & static_cast<unsigned>(b));
142  
            static_cast<unsigned>(a) & static_cast<unsigned>(b));
149  
    }
143  
    }
150  

144  

151  
    /// Compound assignment OR.
145  
    /// Compound assignment OR.
152  
    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
153  
    {
147  
    {
154  
        return a = a | b;
148  
        return a = a | b;
155  
    }
149  
    }
156  

150  

157  
    /// Compound assignment AND.
151  
    /// Compound assignment AND.
158  
    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
159  
    {
153  
    {
160  
        return a = a & b;
154  
        return a = a & b;
161  
    }
155  
    }
162  

156  

163  
    /// Bitwise NOT (complement).
157  
    /// Bitwise NOT (complement).
164  
    friend constexpr flags_t operator~(flags_t a) noexcept
158  
    friend constexpr flags_t operator~(flags_t a) noexcept
165  
    {
159  
    {
166  
        return static_cast<flags_t>(~static_cast<unsigned>(a));
160  
        return static_cast<flags_t>(~static_cast<unsigned>(a));
167  
    }
161  
    }
168  

162  

169 -
private:
163 +
    struct implementation : io_signal_set::implementation
170 -
    struct wait_awaitable
 
171 -
    {
 
172 -
        signal_set& s_;
 
173 -
        std::stop_token token_;
 
174 -
        mutable std::error_code ec_;
 
175 -
        mutable int signal_number_ = 0;
 
176 -

 
177 -
        explicit wait_awaitable(signal_set& s) noexcept : s_(s) {}
 
178 -

 
179 -
        bool await_ready() const noexcept
 
180 -
        {
 
181 -
            return token_.stop_requested();
 
182 -
        }
 
183 -

 
184 -
        capy::io_result<int> await_resume() const noexcept
 
185 -
        {
 
186 -
            if (token_.stop_requested())
 
187 -
                return {capy::error::canceled};
 
188 -
            return {ec_, signal_number_};
 
189 -
        }
 
190 -

 
191 -
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
 
192 -
            -> std::coroutine_handle<>
 
193 -
        {
 
194 -
            token_ = env->stop_token;
 
195 -
            return s_.get().wait(
 
196 -
                h, env->executor, token_, &ec_, &signal_number_);
 
197 -
        }
 
198 -
    };
 
199 -

 
200 -
public:
 
201 -
    struct implementation : io_object::implementation
 
202 -
        virtual std::coroutine_handle<> wait(
 
203 -
            std::coroutine_handle<>,
 
204 -
            capy::executor_ref,
 
205 -
            std::stop_token,
 
206 -
            std::error_code*,
 
207 -
            int*) = 0;
 
208 -

 
209  
    {
164  
    {
210  
        virtual std::error_code add(int signal_number, flags_t flags) = 0;
165  
        virtual std::error_code add(int signal_number, flags_t flags) = 0;
211 -
        virtual std::error_code remove(int signal_number) = 0;
166 +
        virtual std::error_code remove(int signal_number)             = 0;
212 -
        virtual std::error_code clear() = 0;
167 +
        virtual std::error_code clear()                               = 0;
213 -
        virtual void cancel() = 0;
 
214  
    };
168  
    };
215  

169  

216  
    /** Destructor.
170  
    /** Destructor.
217  

171  

218  
        Cancels any pending operations and releases signal resources.
172  
        Cancels any pending operations and releases signal resources.
219  
    */
173  
    */
220  
    ~signal_set() override;
174  
    ~signal_set() override;
221  

175  

222  
    /** Construct an empty signal set.
176  
    /** Construct an empty signal set.
223  

177  

224  
        @param ctx The execution context that will own this signal set.
178  
        @param ctx The execution context that will own this signal set.
225  
    */
179  
    */
226  
    explicit signal_set(capy::execution_context& ctx);
180  
    explicit signal_set(capy::execution_context& ctx);
227  

181  

228  
    /** Construct a signal set with initial signals.
182  
    /** Construct a signal set with initial signals.
229  

183  

230  
        @param ctx The execution context that will own this signal set.
184  
        @param ctx The execution context that will own this signal set.
231  
        @param signal First signal number to add.
185  
        @param signal First signal number to add.
232  
        @param signals Additional signal numbers to add.
186  
        @param signals Additional signal numbers to add.
233  

187  

234  
        @throws std::system_error Thrown on failure.
188  
        @throws std::system_error Thrown on failure.
235  
    */
189  
    */
236  
    template<std::convertible_to<int>... Signals>
190  
    template<std::convertible_to<int>... Signals>
237  
    signal_set(capy::execution_context& ctx, int signal, Signals... signals)
191  
    signal_set(capy::execution_context& ctx, int signal, Signals... signals)
238  
        : signal_set(ctx)
192  
        : signal_set(ctx)
239  
    {
193  
    {
240  
        auto check = [](std::error_code ec) {
194  
        auto check = [](std::error_code ec) {
241  
            if (ec)
195  
            if (ec)
242  
                throw std::system_error(ec);
196  
                throw std::system_error(ec);
243  
        };
197  
        };
244  
        check(add(signal));
198  
        check(add(signal));
245  
        (check(add(signals)), ...);
199  
        (check(add(signals)), ...);
246  
    }
200  
    }
247  

201  

248  
    /** Move constructor.
202  
    /** Move constructor.
249  

203  

250  
        Transfers ownership of the signal set resources.
204  
        Transfers ownership of the signal set resources.
251  

205  

252  
        @param other The signal set to move from.
206  
        @param other The signal set to move from.
253  
    */
207  
    */
254  
    signal_set(signal_set&& other) noexcept;
208  
    signal_set(signal_set&& other) noexcept;
255  

209  

256  
    /** Move assignment operator.
210  
    /** Move assignment operator.
257  

211  

258  
        Closes any existing signal set and transfers ownership.
212  
        Closes any existing signal set and transfers ownership.
259  
        @param other The signal set to move from.
213  
        @param other The signal set to move from.
260  

214  

261  
        @return Reference to this signal set.
215  
        @return Reference to this signal set.
262  
    */
216  
    */
263  
    signal_set& operator=(signal_set&& other) noexcept;
217  
    signal_set& operator=(signal_set&& other) noexcept;
264  

218  

265 -
    signal_set(signal_set const&) = delete;
219 +
    signal_set(signal_set const&)            = delete;
266  
    signal_set& operator=(signal_set const&) = delete;
220  
    signal_set& operator=(signal_set const&) = delete;
267  

221  

268  
    /** Add a signal to the signal set.
222  
    /** Add a signal to the signal set.
269  

223  

270  
        This function adds the specified signal to the set with the
224  
        This function adds the specified signal to the set with the
271  
        specified flags. It has no effect if the signal is already
225  
        specified flags. It has no effect if the signal is already
272  
        in the set with the same flags.
226  
        in the set with the same flags.
273  

227  

274  
        If the signal is already registered globally (by another
228  
        If the signal is already registered globally (by another
275  
        signal_set) and the flags differ, an error is returned
229  
        signal_set) and the flags differ, an error is returned
276  
        unless one of them has the `dont_care` flag.
230  
        unless one of them has the `dont_care` flag.
277  

231  

278  
        @param signal_number The signal to be added to the set.
232  
        @param signal_number The signal to be added to the set.
279  
        @param flags The flags to apply when registering the signal.
233  
        @param flags The flags to apply when registering the signal.
280  
            On POSIX systems, these map to sigaction() flags.
234  
            On POSIX systems, these map to sigaction() flags.
281  
            On Windows, flags are accepted but ignored.
235  
            On Windows, flags are accepted but ignored.
282  

236  

283  
        @return Success, or an error if the signal could not be added.
237  
        @return Success, or an error if the signal could not be added.
284  
            Returns `errc::invalid_argument` if the signal is already
238  
            Returns `errc::invalid_argument` if the signal is already
285  
            registered with different flags.
239  
            registered with different flags.
286  
    */
240  
    */
287  
    std::error_code add(int signal_number, flags_t flags);
241  
    std::error_code add(int signal_number, flags_t flags);
288  

242  

289  
    /** Add a signal to the signal set with default flags.
243  
    /** Add a signal to the signal set with default flags.
290  

244  

291  
        This is equivalent to calling `add(signal_number, none)`.
245  
        This is equivalent to calling `add(signal_number, none)`.
292  

246  

293  
        @param signal_number The signal to be added to the set.
247  
        @param signal_number The signal to be added to the set.
294  

248  

295  
        @return Success, or an error if the signal could not be added.
249  
        @return Success, or an error if the signal could not be added.
296  
    */
250  
    */
297  
    std::error_code add(int signal_number)
251  
    std::error_code add(int signal_number)
298  
    {
252  
    {
299  
        return add(signal_number, none);
253  
        return add(signal_number, none);
300  
    }
254  
    }
301  

255  

302  
    /** Remove a signal from the signal set.
256  
    /** Remove a signal from the signal set.
303  

257  

304  
        This function removes the specified signal from the set. It has
258  
        This function removes the specified signal from the set. It has
305  
        no effect if the signal is not in the set.
259  
        no effect if the signal is not in the set.
306  

260  

307  
        @param signal_number The signal to be removed from the set.
261  
        @param signal_number The signal to be removed from the set.
308  

262  

309  
        @return Success, or an error if the signal could not be removed.
263  
        @return Success, or an error if the signal could not be removed.
310  
    */
264  
    */
311  
    std::error_code remove(int signal_number);
265  
    std::error_code remove(int signal_number);
312  

266  

313  
    /** Remove all signals from the signal set.
267  
    /** Remove all signals from the signal set.
314  

268  

315  
        This function removes all signals from the set. It has no effect
269  
        This function removes all signals from the set. It has no effect
316  
        if the set is already empty.
270  
        if the set is already empty.
317  

271  

318  
        @return Success, or an error if resetting any signal handler fails.
272  
        @return Success, or an error if resetting any signal handler fails.
319  
    */
273  
    */
320  
    std::error_code clear();
274  
    std::error_code clear();
321  

275  

322 -
    /** Cancel all operations associated with the signal set.
276 +
protected:
323 -

277 +
    explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
324 -
        This function forces the completion of any pending asynchronous
 
325 -
        wait operations against the signal set. The handler for each
 
326 -
        cancelled operation will be invoked with an error code that
 
327 -
        compares equal to `capy::cond::canceled`.
 
328 -

 
329 -
        Cancellation does not alter the set of registered signals.
 
330 -
    */
 
331 -
    void cancel();
 
332 -

 
333 -
    /** Wait for a signal to be delivered.
 
334 -

 
335 -
        The operation supports cancellation via `std::stop_token` through
 
336 -
        the affine awaitable protocol. If the associated stop token is
 
337 -
        triggered, the operation completes immediately with an error
 
338 -
        that compares equal to `capy::cond::canceled`.
 
339 -

 
340 -
        @par Example
 
341 -
        @code
 
342 -
        signal_set signals(ctx, SIGINT);
 
343 -
        auto [ec, signum] = co_await signals.wait();
 
344 -
        if (ec == capy::cond::canceled)
 
345 -
        {
 
346 -
            // Cancelled via stop_token or cancel()
 
347 -
            co_return;
 
348 -
        }
 
349 -
        if (ec)
 
350 -
        {
 
351 -
            // Handle other errors
 
352 -
            co_return;
 
353 -
        }
 
354 -
        // Process signal
 
355 -
        std::cout << "Received signal " << signum << std::endl;
 
356 -
        @endcode
 
357 -

 
358 -
        @return An awaitable that completes with `io_result<int>`.
 
359 -
            Returns the signal number when a signal is delivered,
 
360 -
            or an error code on failure. Compare against error conditions
 
361 -
            (e.g., `ec == capy::cond::canceled`) rather than error codes.
 
362 -
    */
 
363 -
    auto wait()
 
364 -
    {
 
365 -
        return wait_awaitable(*this);
 
366 -
    }
 
367  

278  

368  
private:
279  
private:
 
280 +
    void do_cancel() override;
 
281 +

369  
    implementation& get() const noexcept
282  
    implementation& get() const noexcept
370  
    {
283  
    {
371  
        return *static_cast<implementation*>(h_.get());
284  
        return *static_cast<implementation*>(h_.get());
372  
    }
285  
    }
373  
};
286  
};
374  

287  

375  
} // namespace boost::corosio
288  
} // namespace boost::corosio
376  

289  

377  
#endif
290  
#endif