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_BTCSIGNALS_H
6#define BITCOIN_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
33/*
34 * optional_last_value is the default and only supported combiner.
35 * As such, its behavior is embedded into the signal functor.
36 *
37 * Because optional<void> is undefined, void must be special-cased.
38 */
39
40template <typename T>
42{
43public:
44 using result_type = std::conditional_t<std::is_void_v<T>, void, std::optional<T>>;
45};
46
47template <typename Signature, typename Combiner = optional_last_value<typename std::function<Signature>::result_type>>
48class signal;
49
50/*
51 * State object representing the liveness of a registered callback.
52 * signal::connect() returns an enabled connection which can be held and
53 * disabled in the future.
54 */
56{
57 template <typename Signature, typename Combiner>
58 friend class signal;
63 {
64 friend class connection;
65 std::atomic_bool m_connected{true};
66
67 void disconnect() { m_connected.store(false); }
68 public:
69 bool connected() const { return m_connected.load(); }
70 };
71
75 std::shared_ptr<liveness> m_state{};
76
80 explicit connection(std::shared_ptr<liveness>&& state) : m_state{std::move(state)}{}
81
82public:
86 constexpr connection() noexcept = default;
87
99 {
100 if (m_state) {
101 m_state->disconnect();
102 }
103 }
104
109 bool connected() const
110 {
111 return m_state && m_state->connected();
112 }
113};
114
115/*
116 * RAII-style connection management
117 */
119{
121
122public:
123 scoped_connection(connection rhs) noexcept : m_conn{std::move(rhs)} {}
124
126 scoped_connection& operator=(scoped_connection&&) noexcept = default;
127
131 scoped_connection& operator=(const scoped_connection&) = delete;
133
135 {
137 }
138
140 {
141 disconnect();
142 }
143};
144
145/*
146 * Functor for calling zero or more connected callbacks
147 */
148template <typename Signature, typename Combiner>
150{
151 using function_type = std::function<Signature>;
152
153 static_assert(std::is_same_v<Combiner, optional_last_value<typename function_type::result_type>>, "only the optional_last_value combiner is supported");
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 optional_last_value is hard-coded
188 * here. Return the value of the last executed callback, or nullopt if none
189 * were executed.
190 *
191 * Callbacks which return void require special handling.
192 *
193 * In order to avoid locking during the callbacks, the list of callbacks is
194 * cached before they are called. This allows a callback to call connect(),
195 * but the newly connected callback will not be run during the current
196 * signal invocation.
197 *
198 * Note that the parameters are accepted as universal references, though
199 * they are not perfectly forwarded as that could cause a use-after-move if
200 * more than one callback is enabled.
201 */
202 template <typename... Args>
203 result_type operator()(Args&&... args) const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
204 {
205 std::vector<std::shared_ptr<connection_holder>> connections;
206 {
207 LOCK(m_mutex);
208 connections = m_connections;
209 }
210 if constexpr (std::is_void_v<result_type>) {
211 for (const auto& connection : connections) {
212 if (connection->connected()) {
213 connection->m_callback(args...);
214 }
215 }
216 } else {
217 result_type ret{std::nullopt};
218 for (const auto& connection : connections) {
219 if (connection->connected()) {
220 ret.emplace(connection->m_callback(args...));
221 }
222 }
223 return ret;
224 }
225 }
226
227 /*
228 * Connect a new callback to the signal. A forwarding callable accepts
229 * anything that can be stored in a std::function.
230 */
231 template <typename Callable>
233 {
234 LOCK(m_mutex);
235
236 // Garbage-collect disconnected connections to prevent unbounded growth
237 std::erase_if(m_connections, [](const auto& holder) { return !holder->connected(); });
238
239 const auto& entry = m_connections.emplace_back(std::make_shared<connection_holder>(std::forward<Callable>(func)));
240 return connection(entry);
241 }
242
243 /*
244 * Returns true if there are no enabled callbacks
245 */
246 [[nodiscard]] bool empty() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
247 {
248 LOCK(m_mutex);
249 return std::ranges::none_of(m_connections, [](const auto& holder) {
250 return holder->connected();
251 });
252 }
253};
254
255} // namespace btcsignals
256
257#endif // BITCOIN_BTCSIGNALS_H
int ret
ArgsManager & args
Definition: bitcoind.cpp:278
std::atomic_bool m_connected
Definition: btcsignals.h:65
void disconnect()
If a callback is associated with this connection, prevent it from being called in the future.
Definition: btcsignals.h:98
std::shared_ptr< liveness > m_state
connections have shared_ptr-like copy and move semantics.
Definition: btcsignals.h:75
connection(std::shared_ptr< liveness > &&state)
Only a signal can create an enabled connection.
Definition: btcsignals.h:80
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:109
std::conditional_t< std::is_void_v< T >, void, std::optional< T > > result_type
Definition: btcsignals.h:44
scoped_connection(scoped_connection &&) noexcept=default
scoped_connection(connection rhs) noexcept
Definition: btcsignals.h:123
std::function< Signature > function_type
Definition: btcsignals.h:151
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:232
Combiner::result_type result_type
Definition: btcsignals.h:172
constexpr signal() noexcept=default
bool empty() const EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Definition: btcsignals.h:246
btcsignals is a simple mechanism for signaling events to multiple subscribers.
Definition: btcsignals.h:31
Definition: common.h:29
connection_holder(Callable &&callback)
Definition: btcsignals.h:160
#define LOCK(cs)
Definition: sync.h:268
#define EXCLUSIVE_LOCKS_REQUIRED(...)
Definition: threadsafety.h:49