Branch data Line data Source code
1 : : // Copyright (c) The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #ifndef BITCOIN_UTIL_BTCSIGNALS_H
6 : : #define BITCOIN_UTIL_BTCSIGNALS_H
7 : :
8 : : #include <sync.h>
9 : :
10 : : #include <algorithm>
11 : : #include <atomic>
12 : : #include <functional>
13 : : #include <memory>
14 : : #include <optional>
15 : : #include <type_traits>
16 : : #include <utility>
17 : : #include <vector>
18 : :
19 : : /**
20 : : * btcsignals is a simple mechanism for signaling events to multiple subscribers.
21 : : * It is api-compatible with a minimal subset of boost::signals2.
22 : : *
23 : : * Rather than using a custom slot type, and the features/complexity that they
24 : : * imply, std::function is used to store the callbacks. Lifetime management of
25 : : * the callbacks is left up to the user.
26 : : *
27 : : * All usage is thread-safe except for interacting with a connection while
28 : : * copying/moving it on another thread.
29 : : */
30 : :
31 : : namespace btcsignals {
32 : :
33 : : /// The default combiner, which only returns void.
34 : : class null_value
35 : : {
36 : : public:
37 : : using result_type = void;
38 : : };
39 : :
40 : : /// A combiner, which checks if at least one callback returned true.
41 : : class any_of
42 : : {
43 : : public:
44 : : // This is the only supported combiner with a non-void return type. As
45 : : // such, its behavior is embedded into the signal functor.
46 : : using result_type = bool;
47 : : };
48 : :
49 : : template <typename Signature, typename Combiner = null_value>
50 : : class signal;
51 : :
52 : : /*
53 : : * State object representing the liveness of a registered callback.
54 : : * signal::connect() returns an enabled connection which can be held and
55 : : * disabled in the future.
56 : : */
57 [ # # # # : 27292 : class connection
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ][ - + -
+ - + - +
- + - + ]
[ + - + -
+ - # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ][ # # ]
[ # # # #
# # # # #
# # # # #
# # # # #
# # # #
# ][ - + +
- - + + -
- + + - +
- + - + -
- - - - -
- - + + -
- + + - -
+ + - + -
+ - + - -
- - - - -
- + + - -
- - + + -
- + + - +
- + - + -
- - - - +
- + - + -
+ - ]
58 : : {
59 : : template <typename Signature, typename Combiner>
60 : : friend class signal;
61 : : /**
62 : : * Track liveness. Also serves as a tag for the constructor used by signal.
63 : : */
64 : 22276 : class liveness
65 : : {
66 : : friend class connection;
67 : : std::atomic_bool m_connected{true};
68 : :
69 : 1387 : void disconnect() { m_connected.store(false); }
70 : : public:
71 [ # # # # : 261758 : bool connected() const { return m_connected.load(); }
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # ][ - -
- + - + -
- - - -
- ][ + - +
+ - - - -
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ][ #
# # # # #
# # # # #
# # # # #
# # # # ]
[ - - + +
+ - + - -
- + + - -
+ + + + -
- - - - -
- - - - -
- - - - -
- + + + ]
72 : : };
73 : :
74 : : /**
75 : : * connections have shared_ptr-like copy and move semantics.
76 : : */
77 : : std::shared_ptr<liveness> m_state{};
78 : :
79 : : /**
80 : : * Only a signal can create an enabled connection.
81 : : */
82 : 22276 : explicit connection(std::shared_ptr<liveness>&& state) : m_state{std::move(state)}{}
83 : :
84 : : public:
85 : : /**
86 : : * The default constructor creates a connection with no associated signal
87 : : */
88 : : constexpr connection() noexcept = default;
89 : :
90 : : /**
91 : : * If a callback is associated with this connection, prevent it from being
92 : : * called in the future.
93 : : *
94 : : * If a connection is disabled as part of a signal's callback function, it
95 : : * will _not_ be executed in the current signal invocation.
96 : : *
97 : : * Note that disconnected callbacks are not removed from their owning
98 : : * signals here. They are garbage collected in signal::connect().
99 : : */
100 : 1887 : void disconnect()
101 : : {
102 [ + + ]: 1887 : if (m_state) {
103 : 1387 : m_state->disconnect();
104 : : }
105 : 1887 : }
106 : :
107 : : /**
108 : : * Returns true if this connection was created by a signal and has not been
109 : : * disabled.
110 : : */
111 : 1018 : bool connected() const
112 : : {
113 [ + + + + ]: 1018 : return m_state && m_state->connected();
114 : : }
115 : : };
116 : :
117 : : /*
118 : : * RAII-style connection management
119 : : */
120 : : class scoped_connection
121 : : {
122 : : connection m_conn;
123 : :
124 : : public:
125 [ + + + - : 1003 : explicit scoped_connection(connection rhs) noexcept : m_conn{std::move(rhs)} {}
+ - + - ]
126 : :
127 : 500 : scoped_connection(scoped_connection&&) noexcept = default;
128 : :
129 : : /**
130 : : * For simplicity, disable copy construction and copy/move assignment.
131 : : */
132 : : scoped_connection& operator=(scoped_connection&&) = delete;
133 : : scoped_connection& operator=(const scoped_connection&) = delete;
134 : : scoped_connection(const scoped_connection&) = delete;
135 : :
136 : 1505 : void disconnect()
137 : : {
138 : 1 : m_conn.disconnect();
139 : : }
140 : :
141 : 1504 : ~scoped_connection()
142 : : {
143 : 1504 : disconnect();
144 [ + + ]: 1504 : }
145 : : };
146 : :
147 : : /*
148 : : * Functor for calling zero or more connected callbacks
149 : : */
150 : : template <typename Signature, typename Combiner>
151 : : class signal
152 : : {
153 : : using function_type = std::function<Signature>;
154 : :
155 : : /*
156 : : * Helper struct for maintaining a callback and its associated connection liveness
157 : : */
158 : 22276 : struct connection_holder : connection::liveness {
159 : : template <typename Callable>
160 [ # # # # : 22276 : connection_holder(Callable&& callback) : m_callback{std::forward<Callable>(callback)}
# # # # #
# # # # #
# # # # ]
[ + - # #
# # # # #
# # # # #
# # # # ]
[ # # # #
# # # # #
# ][ + - +
- + - ]
161 : : {
162 : : }
163 : :
164 : : const function_type m_callback;
165 : : };
166 : :
167 : : mutable Mutex m_mutex;
168 : :
169 : : std::vector<std::shared_ptr<connection_holder>> m_connections GUARDED_BY(m_mutex){};
170 : :
171 : : public:
172 : : using result_type = Combiner::result_type;
173 : :
174 : 8397 : constexpr signal() noexcept = default;
175 [ + - + - ]: 8437 : ~signal() = default;
176 : :
177 : : /*
178 : : * For simplicity, disable all moving/copying/assigning.
179 : : */
180 : : signal(const signal&) = delete;
181 : : signal(signal&&) = delete;
182 : : signal& operator=(const signal&) = delete;
183 : : signal& operator=(signal&&) = delete;
184 : :
185 : : /*
186 : : * Execute all enabled callbacks for the signal. Rather than allowing for
187 : : * custom combiners, the behavior of any_of is hard-coded here.
188 : : *
189 : : * Callbacks which return void require special handling.
190 : : *
191 : : * In order to avoid locking during the callbacks, the list of callbacks is
192 : : * cached before they are called. This allows a callback to call connect(),
193 : : * but the newly connected callback will not be run during the current
194 : : * signal invocation.
195 : : *
196 : : * Note that the parameters are accepted as universal references, though
197 : : * they are not perfectly forwarded as that could cause a use-after-move if
198 : : * more than one callback is enabled.
199 : : */
200 : : template <typename... Args>
201 : 457843 : [[nodiscard]] result_type operator()(Args&&... args) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
202 : : {
203 : 457843 : std::vector<std::shared_ptr<connection_holder>> connections;
204 : : {
205 [ + - ]: 457843 : LOCK(m_mutex);
206 [ + - ]: 457843 : connections = m_connections;
207 : 0 : }
208 : : if constexpr (std::is_void_v<result_type>) {
209 : : static_assert(std::is_same_v<result_type, typename function_type::result_type>,
210 : : "Callback result type must be equal to the combiner result type (void).");
211 [ + + + + ]: 794397 : for (const auto& connection : connections) {
212 [ + + ]: 336571 : if (connection->connected()) {
213 [ + - + - ]: 349670 : connection->m_callback(args...);
[ + - ]
214 : : }
215 : : }
216 : : } else {
217 : : static_assert(std::is_same_v<Combiner, any_of>,
218 : : "only the any_of combiner is supported and hard-coded into this functor.");
219 : : static_assert(std::is_same_v<result_type, typename function_type::result_type>,
220 : : "Callback result type must be equal to the combiner result type (bool).");
221 : 17 : result_type ret{false};
222 [ + + + + ]: 36 : for (const auto& connection : connections) {
223 [ + + ]: 19 : if (connection->connected()) {
224 [ + - ]: 15 : ret |= connection->m_callback(args...);
225 : : }
226 : : }
227 : 17 : return ret;
228 : : }
229 : 457843 : }
230 : :
231 : : /*
232 : : * Connect a new callback to the signal. A forwarding callable accepts
233 : : * anything that can be stored in a std::function.
234 : : */
235 : : template <typename Callable>
236 : 22276 : connection connect(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
237 : : {
238 : 22276 : LOCK(m_mutex);
239 : :
240 : : // Garbage-collect disconnected connections to prevent unbounded growth
241 [ - - + + : 282658 : std::erase_if(m_connections, [](const auto& holder) { return !holder->connected(); });
+ - + - -
- + + - -
+ + + + -
- - - - -
- - - - -
- - - - -
- + ][ - -
- + - + -
- - - -
- ][ + - +
+ - - - -
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # ][ #
# # # # #
# # # # #
# # # # #
# # # # ]
242 : :
243 [ + - + - : 22276 : const auto& entry = m_connections.emplace_back(std::make_shared<connection_holder>(std::forward<Callable>(func)));
- + + - ]
244 [ + - ]: 22276 : return connection(entry);
245 : 22276 : }
246 : :
247 : : /*
248 : : * Returns true if there are no enabled callbacks
249 : : */
250 : 2082 : [[nodiscard]] bool empty() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
251 : : {
252 : 2082 : LOCK(m_mutex);
253 [ + + + + ]: 3597 : return std::ranges::none_of(m_connections, [](const auto& holder) {
254 [ # # ]: 1515 : return holder->connected();
[ + + + + ]
255 : 2082 : });
256 : 2082 : }
257 : : };
258 : :
259 : : } // namespace btcsignals
260 : :
261 : : #endif // BITCOIN_UTIL_BTCSIGNALS_H
|