Bitcoin Core 31.99.0
P2P Digital Currency
btcsignals.h
Go to the documentation of this file.
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
31namespace btcsignals {
32
35{
36public:
37 using result_type = void;
38};
39
41class any_of
42{
43public:
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
49template <typename Signature, typename Combiner = null_value>
50class 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 */
58{
59 template <typename Signature, typename Combiner>
60 friend class signal;
65 {
66 friend class connection;
67 std::atomic_bool m_connected{true};
68
69 void disconnect() { m_connected.store(false); }
70 public:
71 bool connected() const { return m_connected.load(); }
72 };
73
77 std::shared_ptr<liveness> m_state{};
78
82 explicit connection(std::shared_ptr<liveness>&& state) : m_state{std::move(state)}{}
83
84public:
88 constexpr connection() noexcept = default;
89
101 {
102 if (m_state) {
103 m_state->disconnect();
104 }
105 }
106
111 bool connected() const
112 {
113 return m_state && m_state->connected();
114 }
115};
116
117/*
118 * RAII-style connection management
119 */
121{
123
124public:
125 explicit scoped_connection(connection rhs) noexcept : m_conn{std::move(rhs)} {}
126
128
132 scoped_connection& operator=(scoped_connection&&) = delete;
133 scoped_connection& operator=(const scoped_connection&) = delete;
135
137 {
139 }
140
142 {
143 disconnect();
144 }
145};
146
147/*
148 * Functor for calling zero or more connected callbacks
149 */
150template <typename Signature, typename Combiner>
152{
153 using function_type = std::function<Signature>;
154
155 /*
156 * Helper struct for maintaining a callback and its associated connection liveness
157 */
159 template <typename Callable>
160 connection_holder(Callable&& callback) : m_callback{std::forward<Callable>(callback)}
161 {
162 }
163
165 };
166
167 mutable Mutex m_mutex;
168
169 std::vector<std::shared_ptr<connection_holder>> m_connections GUARDED_BY(m_mutex){};
170
171public:
172 using result_type = Combiner::result_type;
173
174 constexpr signal() noexcept = default;
175 ~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 [[nodiscard]] result_type operator()(Args&&... args) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
202 {
203 std::vector<std::shared_ptr<connection_holder>> connections;
204 {
205 LOCK(m_mutex);
206 connections = m_connections;
207 }
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 for (const auto& connection : connections) {
212 if (connection->connected()) {
213 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 result_type ret{false};
222 for (const auto& connection : connections) {
223 if (connection->connected()) {
224 ret |= connection->m_callback(args...);
225 }
226 }
227 return ret;
228 }
229 }
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>
237 {
238 LOCK(m_mutex);
239
240 // Garbage-collect disconnected connections to prevent unbounded growth
241 std::erase_if(m_connections, [](const auto& holder) { return !holder->connected(); });
242
243 const auto& entry = m_connections.emplace_back(std::make_shared<connection_holder>(std::forward<Callable>(func)));
244 return connection(entry);
245 }
246
247 /*
248 * Returns true if there are no enabled callbacks
249 */
250 [[nodiscard]] bool empty() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
251 {
252 LOCK(m_mutex);
253 return std::ranges::none_of(m_connections, [](const auto& holder) {
254 return holder->connected();
255 });
256 }
257};
258
259} // namespace btcsignals
260
261#endif // BITCOIN_UTIL_BTCSIGNALS_H
int ret
ArgsManager & args
Definition: bitcoind.cpp:280
A combiner, which checks if at least one callback returned true.
Definition: btcsignals.h:42
std::atomic_bool m_connected
Definition: btcsignals.h:67
void disconnect()
If a callback is associated with this connection, prevent it from being called in the future.
Definition: btcsignals.h:100
std::shared_ptr< liveness > m_state
connections have shared_ptr-like copy and move semantics.
Definition: btcsignals.h:77
connection(std::shared_ptr< liveness > &&state)
Only a signal can create an enabled connection.
Definition: btcsignals.h:82
constexpr connection() noexcept=default
The default constructor creates a connection with no associated signal.
bool connected() const
Returns true if this connection was created by a signal and has not been disabled.
Definition: btcsignals.h:111
The default combiner, which only returns void.
Definition: btcsignals.h:35
scoped_connection(scoped_connection &&) noexcept=default
scoped_connection(connection rhs) noexcept
Definition: btcsignals.h:125
std::function< Signature > function_type
Definition: btcsignals.h:153
std::vector< std::shared_ptr< connection_holder > > m_connections GUARDED_BY(m_mutex)
Definition: btcsignals.h:169
connection connect(Callable &&func) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Definition: btcsignals.h:236
Combiner::result_type result_type
Definition: btcsignals.h:172
constexpr signal() noexcept=default
bool empty() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Definition: btcsignals.h:250
btcsignals is a simple mechanism for signaling events to multiple subscribers.
Definition: handler.h:11
connection_holder(Callable &&callback)
Definition: btcsignals.h:160
#define LOCK(cs)
Definition: sync.h:268
#define EXCLUSIVE_LOCKS_REQUIRED(...)
Definition: threadsafety.h:49