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

9  

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

12  

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

22  

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

27  

28  
/*
28  
/*
29  
    Signal Set Public API
29  
    Signal Set Public API
30  
    =====================
30  
    =====================
31  

31  

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

36  

37  
    Key design decisions:
37  
    Key design decisions:
38  

38  

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

42  

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

47  

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

51  

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

55  

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

57  

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

59  

60  
    This class provides the ability to perform an asynchronous wait
60  
    This class provides the ability to perform an asynchronous wait
61  
    for one or more signals to occur. The signal set registers for
61  
    for one or more signals to occur. The signal set registers for
62  
    signals using sigaction() on POSIX systems or the C runtime
62  
    signals using sigaction() on POSIX systems or the C runtime
63  
    signal() function on Windows.
63  
    signal() function on Windows.
64  

64  

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

69  

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

74  

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

78  

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

97  

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

100  

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

112  

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

115  

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

119  

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

123  

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

127  

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

131  

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

136  

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

143  

144  
    /// Mask two flag values.
144  
    /// Mask two flag values.
145  
    friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
145  
    friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
146  
    {
146  
    {
147  
        return static_cast<flags_t>(
147  
        return static_cast<flags_t>(
148  
            static_cast<unsigned>(a) & static_cast<unsigned>(b));
148  
            static_cast<unsigned>(a) & static_cast<unsigned>(b));
149  
    }
149  
    }
150  

150  

151  
    /// Compound assignment OR.
151  
    /// Compound assignment OR.
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  
    /// Compound assignment AND.
157  
    /// Compound assignment AND.
158  
    friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
158  
    friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
159  
    {
159  
    {
160  
        return a = a & b;
160  
        return a = a & b;
161  
    }
161  
    }
162  

162  

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

168  

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

176  

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

178  

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

183  

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

190  

191  
        template<typename Ex>
191  
        template<typename Ex>
192  
        auto await_suspend(
192  
        auto await_suspend(
193  
            std::coroutine_handle<> h,
193  
            std::coroutine_handle<> h,
194  
            Ex const& ex,
194  
            Ex const& ex,
195  
            std::stop_token token) -> std::coroutine_handle<>
195  
            std::stop_token token) -> std::coroutine_handle<>
196  
        {
196  
        {
197  
            token_ = std::move(token);
197  
            token_ = std::move(token);
198  
            s_.get().wait(h, ex, token_, &ec_, &signal_number_);
198  
            s_.get().wait(h, ex, token_, &ec_, &signal_number_);
199  
            return std::noop_coroutine();
199  
            return std::noop_coroutine();
200  
        }
200  
        }
201  
    };
201  
    };
202  

202  

203  
public:
203  
public:
204  
    struct signal_set_impl : io_object_impl
204  
    struct signal_set_impl : io_object_impl
205  
    {
205  
    {
206  
        virtual void wait(
206  
        virtual void wait(
207  
            std::coroutine_handle<>,
207  
            std::coroutine_handle<>,
208  
            capy::executor_ref,
208  
            capy::executor_ref,
209  
            std::stop_token,
209  
            std::stop_token,
210  
            std::error_code*,
210  
            std::error_code*,
211  
            int*) = 0;
211  
            int*) = 0;
212  

212  

213  
        virtual std::error_code add(int signal_number, flags_t flags) = 0;
213  
        virtual std::error_code add(int signal_number, flags_t flags) = 0;
214  
        virtual std::error_code remove(int signal_number) = 0;
214  
        virtual std::error_code remove(int signal_number) = 0;
215  
        virtual std::error_code clear() = 0;
215  
        virtual std::error_code clear() = 0;
216  
        virtual void cancel() = 0;
216  
        virtual void cancel() = 0;
217  
    };
217  
    };
218  

218  

219  
    /** Destructor.
219  
    /** Destructor.
220  

220  

221  
        Cancels any pending operations and releases signal resources.
221  
        Cancels any pending operations and releases signal resources.
222  
    */
222  
    */
223  
    ~signal_set();
223  
    ~signal_set();
224  

224  

225  
    /** Construct an empty signal set.
225  
    /** Construct an empty signal set.
226  

226  

227  
        @param ctx The execution context that will own this signal set.
227  
        @param ctx The execution context that will own this signal set.
228  
    */
228  
    */
229  
    explicit signal_set(capy::execution_context& ctx);
229  
    explicit signal_set(capy::execution_context& ctx);
230  

230  

231  
    /** Construct a signal set with initial signals.
231  
    /** Construct a signal set with initial signals.
232  

232  

233  
        @param ctx The execution context that will own this signal set.
233  
        @param ctx The execution context that will own this signal set.
234  
        @param signal First signal number to add.
234  
        @param signal First signal number to add.
235  
        @param signals Additional signal numbers to add.
235  
        @param signals Additional signal numbers to add.
236  

236  

237  
        @throws std::system_error Thrown on failure.
237  
        @throws std::system_error Thrown on failure.
238  
    */
238  
    */
239  
    template<std::convertible_to<int>... Signals>
239  
    template<std::convertible_to<int>... Signals>
240  
    signal_set(
240  
    signal_set(
241  
        capy::execution_context& ctx,
241  
        capy::execution_context& ctx,
242  
        int signal,
242  
        int signal,
243  
        Signals... signals)
243  
        Signals... signals)
244  
        : signal_set(ctx)
244  
        : signal_set(ctx)
245  
    {
245  
    {
246  
        auto check = [](std::error_code ec) {
246  
        auto check = [](std::error_code ec) {
247  
            if( ec )
247  
            if( ec )
248  
                throw std::system_error(ec);
248  
                throw std::system_error(ec);
249  
        };
249  
        };
250  
        check(add(signal));
250  
        check(add(signal));
251  
        (check(add(signals)), ...);
251  
        (check(add(signals)), ...);
252  
    }
252  
    }
253  

253  

254  
    /** Move constructor.
254  
    /** Move constructor.
255  

255  

256  
        Transfers ownership of the signal set resources.
256  
        Transfers ownership of the signal set resources.
257  

257  

258  
        @param other The signal set to move from.
258  
        @param other The signal set to move from.
259  
    */
259  
    */
260  
    signal_set(signal_set&& other) noexcept;
260  
    signal_set(signal_set&& other) noexcept;
261  

261  

262  
    /** Move assignment operator.
262  
    /** Move assignment operator.
263  

263  

264  
        Closes any existing signal set and transfers ownership.
264  
        Closes any existing signal set and transfers ownership.
265  
        The source and destination must share the same execution context.
265  
        The source and destination must share the same execution context.
266  

266  

267  
        @param other The signal set to move from.
267  
        @param other The signal set to move from.
268  

268  

269  
        @return Reference to this signal set.
269  
        @return Reference to this signal set.
270  

270  

271  
        @throws std::logic_error if the signal sets have different
271  
        @throws std::logic_error if the signal sets have different
272  
            execution contexts.
272  
            execution contexts.
273  
    */
273  
    */
274  
    signal_set& operator=(signal_set&& other);
274  
    signal_set& operator=(signal_set&& other);
275  

275  

276  
    signal_set(signal_set const&) = delete;
276  
    signal_set(signal_set const&) = delete;
277  
    signal_set& operator=(signal_set const&) = delete;
277  
    signal_set& operator=(signal_set const&) = delete;
278  

278  

279  
    /** Add a signal to the signal set.
279  
    /** Add a signal to the signal set.
280  

280  

281  
        This function adds the specified signal to the set with the
281  
        This function adds the specified signal to the set with the
282  
        specified flags. It has no effect if the signal is already
282  
        specified flags. It has no effect if the signal is already
283  
        in the set with the same flags.
283  
        in the set with the same flags.
284  

284  

285  
        If the signal is already registered globally (by another
285  
        If the signal is already registered globally (by another
286  
        signal_set) and the flags differ, an error is returned
286  
        signal_set) and the flags differ, an error is returned
287  
        unless one of them has the `dont_care` flag.
287  
        unless one of them has the `dont_care` flag.
288  

288  

289  
        @param signal_number The signal to be added to the set.
289  
        @param signal_number The signal to be added to the set.
290  
        @param flags The flags to apply when registering the signal.
290  
        @param flags The flags to apply when registering the signal.
291  
            On POSIX systems, these map to sigaction() flags.
291  
            On POSIX systems, these map to sigaction() flags.
292  
            On Windows, flags are accepted but ignored.
292  
            On Windows, flags are accepted but ignored.
293  

293  

294  
        @return Success, or an error if the signal could not be added.
294  
        @return Success, or an error if the signal could not be added.
295  
            Returns `errc::invalid_argument` if the signal is already
295  
            Returns `errc::invalid_argument` if the signal is already
296  
            registered with different flags.
296  
            registered with different flags.
297  
    */
297  
    */
298  
    std::error_code add(int signal_number, flags_t flags);
298  
    std::error_code add(int signal_number, flags_t flags);
299  

299  

300  
    /** Add a signal to the signal set with default flags.
300  
    /** Add a signal to the signal set with default flags.
301  

301  

302  
        This is equivalent to calling `add(signal_number, none)`.
302  
        This is equivalent to calling `add(signal_number, none)`.
303  

303  

304  
        @param signal_number The signal to be added to the set.
304  
        @param signal_number The signal to be added to the set.
305  

305  

306  
        @return Success, or an error if the signal could not be added.
306  
        @return Success, or an error if the signal could not be added.
307  
    */
307  
    */
308  
    std::error_code add(int signal_number)
308  
    std::error_code add(int signal_number)
309  
    {
309  
    {
310  
        return add(signal_number, none);
310  
        return add(signal_number, none);
311  
    }
311  
    }
312  

312  

313  
    /** Remove a signal from the signal set.
313  
    /** Remove a signal from the signal set.
314  

314  

315  
        This function removes the specified signal from the set. It has
315  
        This function removes the specified signal from the set. It has
316  
        no effect if the signal is not in the set.
316  
        no effect if the signal is not in the set.
317  

317  

318  
        @param signal_number The signal to be removed from the set.
318  
        @param signal_number The signal to be removed from the set.
319  

319  

320  
        @return Success, or an error if the signal could not be removed.
320  
        @return Success, or an error if the signal could not be removed.
321  
    */
321  
    */
322  
    std::error_code remove(int signal_number);
322  
    std::error_code remove(int signal_number);
323  

323  

324  
    /** Remove all signals from the signal set.
324  
    /** Remove all signals from the signal set.
325  

325  

326  
        This function removes all signals from the set. It has no effect
326  
        This function removes all signals from the set. It has no effect
327  
        if the set is already empty.
327  
        if the set is already empty.
328  

328  

329  
        @return Success, or an error if resetting any signal handler fails.
329  
        @return Success, or an error if resetting any signal handler fails.
330  
    */
330  
    */
331  
    std::error_code clear();
331  
    std::error_code clear();
332  

332  

333  
    /** Cancel all operations associated with the signal set.
333  
    /** Cancel all operations associated with the signal set.
334  

334  

335  
        This function forces the completion of any pending asynchronous
335  
        This function forces the completion of any pending asynchronous
336  
        wait operations against the signal set. The handler for each
336  
        wait operations against the signal set. The handler for each
337  
        cancelled operation will be invoked with an error code that
337  
        cancelled operation will be invoked with an error code that
338  
        compares equal to `capy::cond::canceled`.
338  
        compares equal to `capy::cond::canceled`.
339  

339  

340  
        Cancellation does not alter the set of registered signals.
340  
        Cancellation does not alter the set of registered signals.
341  
    */
341  
    */
342  
    void cancel();
342  
    void cancel();
343  

343  

344  
    /** Wait for a signal to be delivered.
344  
    /** Wait for a signal to be delivered.
345  

345  

346  
        The operation supports cancellation via `std::stop_token` through
346  
        The operation supports cancellation via `std::stop_token` through
347  
        the affine awaitable protocol. If the associated stop token is
347  
        the affine awaitable protocol. If the associated stop token is
348  
        triggered, the operation completes immediately with an error
348  
        triggered, the operation completes immediately with an error
349  
        that compares equal to `capy::cond::canceled`.
349  
        that compares equal to `capy::cond::canceled`.
350  

350  

351  
        @par Example
351  
        @par Example
352  
        @code
352  
        @code
353  
        signal_set signals(ctx, SIGINT);
353  
        signal_set signals(ctx, SIGINT);
354  
        auto [ec, signum] = co_await signals.wait();
354  
        auto [ec, signum] = co_await signals.wait();
355  
        if (ec == capy::cond::canceled)
355  
        if (ec == capy::cond::canceled)
356  
        {
356  
        {
357  
            // Cancelled via stop_token or cancel()
357  
            // Cancelled via stop_token or cancel()
358  
            co_return;
358  
            co_return;
359  
        }
359  
        }
360  
        if (ec)
360  
        if (ec)
361  
        {
361  
        {
362  
            // Handle other errors
362  
            // Handle other errors
363  
            co_return;
363  
            co_return;
364  
        }
364  
        }
365  
        // Process signal
365  
        // Process signal
366  
        std::cout << "Received signal " << signum << std::endl;
366  
        std::cout << "Received signal " << signum << std::endl;
367  
        @endcode
367  
        @endcode
368  

368  

369  
        @return An awaitable that completes with `io_result<int>`.
369  
        @return An awaitable that completes with `io_result<int>`.
370  
            Returns the signal number when a signal is delivered,
370  
            Returns the signal number when a signal is delivered,
371  
            or an error code on failure. Compare against error conditions
371  
            or an error code on failure. Compare against error conditions
372  
            (e.g., `ec == capy::cond::canceled`) rather than error codes.
372  
            (e.g., `ec == capy::cond::canceled`) rather than error codes.
373  
    */
373  
    */
374  
    auto wait()
374  
    auto wait()
375  
    {
375  
    {
376  
        return wait_awaitable(*this);
376  
        return wait_awaitable(*this);
377  
    }
377  
    }
378  

378  

379  
private:
379  
private:
380  
    signal_set_impl& get() const noexcept
380  
    signal_set_impl& get() const noexcept
381  
    {
381  
    {
382  
        return *static_cast<signal_set_impl*>(impl_);
382  
        return *static_cast<signal_set_impl*>(impl_);
383  
    }
383  
    }
384  
};
384  
};
385  

385  

386  
} // namespace boost::corosio
386  
} // namespace boost::corosio
387  

387  

388  
#endif
388  
#endif