1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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/ex/io_env.hpp>
20  
#include <boost/capy/ex/io_env.hpp>
21  
#include <boost/capy/concept/executor.hpp>
21  
#include <boost/capy/concept/executor.hpp>
22  
#include <system_error>
22  
#include <system_error>
23  

23  

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

28  

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

32  

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

37  

38  
    Key design decisions:
38  
    Key design decisions:
39  

39  

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

43  

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

48  

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

52  

53  
    4. The inline add(int) overload avoids a virtual call for the common case
53  
    4. The inline add(int) overload avoids a virtual call for the common case
54  
       of adding signals without flags (delegates to add(int, none)).
54  
       of adding signals without flags (delegates to add(int, none)).
55  
*/
55  
*/
56  

56  

57  
namespace boost::corosio {
57  
namespace boost::corosio {
58  

58  

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

60  

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

65  

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

70  

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

75  

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

79  

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

98  

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

101  

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

113  

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

116  

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

120  

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

124  

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

128  

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

132  

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

137  

138  
    /// Combine two flag values.
138  
    /// Combine 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  
    /// Mask two flag values.
145  
    /// Mask two flag values.
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 static_cast<flags_t>(
148  
        return static_cast<flags_t>(
149  
            static_cast<unsigned>(a) & static_cast<unsigned>(b));
149  
            static_cast<unsigned>(a) & static_cast<unsigned>(b));
150  
    }
150  
    }
151  

151  

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

157  

158  
    /// Compound assignment AND.
158  
    /// Compound assignment AND.
159  
    friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
159  
    friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
160  
    {
160  
    {
161  
        return a = a & b;
161  
        return a = a & b;
162  
    }
162  
    }
163  

163  

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

169  

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

177  

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

179  

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

184  

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

191  

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

200  

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

210  

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

216  

217  
    /** Destructor.
217  
    /** Destructor.
218  

218  

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

222  

223  
    /** Construct an empty signal set.
223  
    /** Construct an empty signal set.
224  

224  

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

228  

229  
    /** Construct a signal set with initial signals.
229  
    /** Construct a signal set with initial signals.
230  

230  

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

234  

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

251  

252  
    /** Move constructor.
252  
    /** Move constructor.
253  

253  

254  
        Transfers ownership of the signal set resources.
254  
        Transfers ownership of the signal set resources.
255  

255  

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

259  

260  
    /** Move assignment operator.
260  
    /** Move assignment operator.
261  

261  

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

264  

265  
        @param other The signal set to move from.
265  
        @param other The signal set to move from.
266  

266  

267  
        @return Reference to this signal set.
267  
        @return Reference to this signal set.
268  

268  

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

273  

274  
    signal_set(signal_set const&) = delete;
274  
    signal_set(signal_set const&) = delete;
275  
    signal_set& operator=(signal_set const&) = delete;
275  
    signal_set& operator=(signal_set const&) = delete;
276  

276  

277  
    /** Add a signal to the signal set.
277  
    /** Add a signal to the signal set.
278  

278  

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

282  

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

286  

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

291  

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

297  

298  
    /** Add a signal to the signal set with default flags.
298  
    /** Add a signal to the signal set with default flags.
299  

299  

300  
        This is equivalent to calling `add(signal_number, none)`.
300  
        This is equivalent to calling `add(signal_number, none)`.
301  

301  

302  
        @param signal_number The signal to be added to the set.
302  
        @param signal_number The signal to be added to the set.
303  

303  

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

310  

311  
    /** Remove a signal from the signal set.
311  
    /** Remove a signal from the signal set.
312  

312  

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

315  

316  
        @param signal_number The signal to be removed from the set.
316  
        @param signal_number The signal to be removed from the set.
317  

317  

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

321  

322  
    /** Remove all signals from the signal set.
322  
    /** Remove all signals from the signal set.
323  

323  

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

326  

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

330  

331  
    /** Cancel all operations associated with the signal set.
331  
    /** Cancel all operations associated with the signal set.
332  

332  

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

337  

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

341  

342  
    /** Wait for a signal to be delivered.
342  
    /** Wait for a signal to be delivered.
343  

343  

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

348  

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

366  

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

376  

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

383  

384  
} // namespace boost::corosio
384  
} // namespace boost::corosio
385  

385  

386  
#endif
386  
#endif