TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Steve Gerbino
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
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)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_HPP
12 : #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_HPP
13 :
14 : #include <boost/corosio/detail/platform.hpp>
15 :
16 : #if BOOST_COROSIO_POSIX
17 :
18 : #include <boost/corosio/detail/config.hpp>
19 : #include <boost/corosio/signal_set.hpp>
20 : #include <boost/corosio/detail/intrusive.hpp>
21 : #include <boost/corosio/detail/object_ref.hpp>
22 : #include <boost/corosio/detail/scheduler_op.hpp>
23 : #include <boost/capy/continuation.hpp>
24 : #include <boost/capy/ex/executor_ref.hpp>
25 :
26 : #include <coroutine>
27 : #include <cstddef>
28 : #include <optional>
29 : #include <stop_token>
30 : #include <system_error>
31 :
32 : namespace boost::corosio {
33 :
34 : namespace detail {
35 :
36 : // Forward declarations
37 : class posix_signal_service;
38 :
39 : // Maximum signal number supported (NSIG is typically 64 on Linux)
40 : enum
41 : {
42 : max_signal_number = 64
43 : };
44 :
45 : // signal_op - pending wait operation
46 :
47 : struct signal_op : scheduler_op
48 : {
49 : /// The awaitable's continuation; see coro_op::cont.
50 : capy::continuation* cont = nullptr;
51 : capy::executor_ref d;
52 : /// Holds the owning set while this op sits in the scheduler queue.
53 : detail::object_ref keep;
54 : std::error_code* ec_out = nullptr;
55 : int* signal_out = nullptr;
56 : int signal_number = 0;
57 : posix_signal_service* svc = nullptr; // For work_finished callback
58 :
59 : void operator()() noexcept override;
60 : void destroy() override;
61 : };
62 :
63 : // signal_registration - per-signal registration tracking
64 :
65 : struct signal_registration
66 : {
67 : int signal_number = 0;
68 : signal_set::flags_t flags = signal_set::none;
69 : signal_set::implementation* owner = nullptr;
70 : std::size_t undelivered = 0;
71 : signal_registration* next_in_table = nullptr;
72 : signal_registration* prev_in_table = nullptr;
73 : signal_registration* next_in_set = nullptr;
74 : };
75 :
76 : // posix_signal - per-signal_set implementation
77 :
78 : class posix_signal final
79 : : public signal_set::implementation
80 : , public intrusive_list<posix_signal>::node
81 : {
82 : friend class posix_signal_service;
83 :
84 : posix_signal_service& svc_;
85 : signal_registration* signals_ = nullptr;
86 : signal_op pending_op_;
87 : bool waiting_ = false;
88 : bool cancelled_ = false;
89 :
90 : /** Routes a stop request into the service's per-operation cancel.
91 :
92 : Deliberately NOT `cancel_wait`: that sets the sticky `cancelled_`
93 : latch, which belongs to `cancel()` and scopes to the whole set. A
94 : stop token scopes to one wait, so a late fire must be a no-op
95 : rather than poisoning the next wait.
96 : */
97 : struct token_canceller
98 : {
99 : posix_signal* self;
100 : void operator()() const noexcept;
101 : };
102 :
103 : /** Armed for the duration of one wait; see wait().
104 :
105 : Never reset while `posix_signal_service::mutex_` is held:
106 : `~stop_callback` blocks until a concurrently running callback
107 : returns, and that callback takes the same mutex.
108 : */
109 : std::optional<std::stop_callback<token_canceller>> stop_cb_;
110 :
111 : /** Set when a stop request arrives for the current wait.
112 :
113 : Closes a lost-wakeup race. The callback is armed before
114 : `start_wait` takes the lock, so a request landing in that window
115 : finds `waiting_ == false` and would otherwise return having done
116 : nothing, leaving `start_wait` to park the wait forever. Every
117 : other op survives this because `coro_op::on_cancel()` defaults to
118 : `request_cancel()`, which sets a persistent flag the completion
119 : decode reads later; this is that flag for the signal path.
120 :
121 : Distinct from `cancelled_` on purpose: `cancelled_` is the sticky
122 : per-set latch `cancel()` owns, this is per-operation.
123 : */
124 : bool token_cancelled_ = false;
125 :
126 : public:
127 : explicit posix_signal(posix_signal_service& svc) noexcept;
128 :
129 : /// Recycle into the owning service's pool. Defined out-of-line
130 : /// after posix_signal_service for its complete type.
131 : void retire() noexcept override;
132 :
133 : /** Reset for recycling.
134 :
135 : `destroy()` already calls `clear()` (empties the registration
136 : list) and `disarm_stop()` before the refcount reaches zero, so
137 : those are asserted rather than re-cleared. `destroy()` also
138 : calls `cancel()`, which deliberately latches `cancelled_` to
139 : abort any wait still unwinding — that latch is this object's
140 : to clear for its next, unrelated use, not something reset()
141 : can skip by asserting it already false.
142 :
143 : @pre refs_ == 0, registration list empty, no wait in flight.
144 : */
145 HIT 72 : void reuse() noexcept
146 : {
147 72 : BOOST_COROSIO_ASSERT(signals_ == nullptr);
148 72 : BOOST_COROSIO_ASSERT(!waiting_);
149 72 : BOOST_COROSIO_ASSERT(!stop_cb_);
150 72 : cancelled_ = false;
151 72 : token_cancelled_ = false;
152 72 : }
153 :
154 : std::coroutine_handle<> wait(
155 : capy::continuation&,
156 : capy::executor_ref,
157 : std::stop_token,
158 : std::error_code*,
159 : int*) override;
160 :
161 : std::error_code add(int signal_number, signal_set::flags_t flags) override;
162 : std::error_code remove(int signal_number) override;
163 : std::error_code clear() override;
164 : void cancel() noexcept override;
165 :
166 : /// Disarm the wait's stop callback. Called before teardown.
167 3476 : void disarm_stop() noexcept
168 : {
169 3476 : stop_cb_.reset();
170 3476 : }
171 : };
172 :
173 : } // namespace detail
174 :
175 : } // namespace boost::corosio
176 :
177 : #endif // BOOST_COROSIO_POSIX
178 :
179 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_HPP
|