Bitcoin Core 32.99.0
P2P Digital Currency
Loading...
Searching...
No Matches
btcsignals_tests.cpp
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
6#include <util/btcsignals.h>
7
8#include <boost/test/unit_test.hpp>
9
10#include <future>
11#include <memory>
12#include <semaphore>
13#include <thread>
14
15namespace {
16
17
18void IncrementCallback(int& val)
19{
20 val++;
21}
22void SquareCallback(int& val)
23{
24 val *= val;
25}
26
27bool ReturnTrue()
28{
29 return true;
30}
31bool ReturnFalse()
32{
33 return false;
34}
35
36} // anonymous namespace
37
39
40/* Callbacks should always be executed in the order in which they were added
41 */
43{
46 sig0.connect(SquareCallback);
47 int val{3};
48 sig0(val);
49 BOOST_CHECK_EQUAL(val, 16);
50 BOOST_CHECK(!sig0.empty());
51}
52
54{
57 auto conn1 = sig0.connect(SquareCallback);
59 BOOST_CHECK(!sig0.empty());
60 int val{3};
61 sig0(val);
62 BOOST_CHECK_EQUAL(val, 4);
63
64 BOOST_CHECK(!sig0.empty());
65 conn0.disconnect();
66 BOOST_CHECK(sig0.empty());
67 sig0(val);
68 BOOST_CHECK_EQUAL(val, 4);
69
70 conn0 = sig0.connect(IncrementCallback);
71 conn1 = sig0.connect(IncrementCallback);
72 BOOST_CHECK(!sig0.empty());
73 sig0(val);
74 BOOST_CHECK_EQUAL(val, 6);
75 conn1.disconnect();
76
77 BOOST_CHECK(conn0.connected());
78 {
80 }
81 BOOST_CHECK(!conn0.connected());
82 BOOST_CHECK(sig0.empty());
83 sig0(val);
84 BOOST_CHECK_EQUAL(val, 6);
85}
86
87/* Characterize when a callback object and the state it owns are destroyed:
88 * eagerly on disconnect(), or with the signal if never disconnected.
89 * Connection handles do not keep the callback alive.
90 */
92{
93 // With no disconnect, callbacks are destroyed with the signal.
94 std::weak_ptr<int> weak0;
95 {
97 auto state0{std::make_shared<int>(0)};
98 weak0 = state0;
99 sig0.connect([state0 = std::move(state0)] {});
100 BOOST_CHECK(!weak0.expired());
101 }
102 BOOST_CHECK(weak0.expired());
103
104 // disconnect() destroys the callback immediately, even while a connection
105 // handle is still held.
107 auto state1{std::make_shared<int>(0)};
108 std::weak_ptr<int> weak1{state1};
109 auto conn1{sig1.connect([state1 = std::move(state1)] {})};
110 BOOST_CHECK(!weak1.expired());
111 conn1.disconnect();
112 BOOST_CHECK(weak1.expired());
113}
114
115/* A callback that disconnects itself during emission remains valid until its
116 * invocation returns, and is destroyed as soon as it does.
117 */
119{
121 auto state0{std::make_shared<int>(0)};
122 std::weak_ptr<int> weak0{state0};
124 conn0 = sig0.connect([&conn0, weak0, state0 = std::move(state0)] {
125 conn0.disconnect();
126 // The callback and the state it owns stay valid while it executes.
127 BOOST_CHECK(!weak0.expired());
128 *state0 += 1;
129 });
130 sig0();
131 BOOST_CHECK(weak0.expired());
132}
133
134/* A callback disconnected on another thread while it is running stays valid
135 * until the call returns, and is destroyed when it does.
136 */
138{
140 auto state0{std::make_shared<int>(0)};
141 std::weak_ptr<int> weak0{state0};
142 std::promise<void> entered;
143 std::promise<void> release;
144 auto conn0{sig0.connect([&entered, release_future = release.get_future().share(), state0 = std::move(state0)] {
145 entered.set_value();
146 release_future.wait();
147 *state0 += 1;
148 })};
149 std::thread thread{[&sig0] { sig0(); }};
150 entered.get_future().wait();
151 conn0.disconnect();
152 BOOST_CHECK(!conn0.connected());
153 BOOST_CHECK(!weak0.expired());
154 release.set_value();
155 thread.join();
156 BOOST_CHECK(weak0.expired());
157}
158
160{
162 decltype(sig0)::result_type ret;
163 ret = sig0();
164 BOOST_CHECK_EQUAL(ret, false);
165 {
167 ret = sig0();
168 BOOST_CHECK_EQUAL(ret, true);
169 }
170 ret = sig0();
171 BOOST_CHECK_EQUAL(ret, false);
172 {
175 ret = sig0();
176 BOOST_CHECK_EQUAL(ret, true);
177 conn0.disconnect();
178 ret = sig0();
179 BOOST_CHECK_EQUAL(ret, false);
180 }
181 ret = sig0();
182 BOOST_CHECK_EQUAL(ret, false);
183}
184
185/* Test the thread-safety of connect/disconnect/empty/connected/callbacks.
186 * Connect sig0 to an incrementor function and loop in a thread.
187 * Meanwhile, in another thread, inject and call new increment callbacks.
188 * Both threads are constantly calling empty/connected.
189 * The end-result must be deterministic for the atomic modified by conn0.
190 * Though, the end-result for the atomic modified by the extra connections is
191 * undefined due to a non-deterministic number of total callbacks executed.
192 * In any case, this should all be completely threadsafe.
193 * Sanitizers should pick up any buggy data race behavior (if present).
194 */
196{
198 std::atomic<uint32_t> val_det{0};
199 std::atomic<uint32_t> val_non_det{0};
200 auto conn0 = sig0.connect([&val_det] { val_det++; });
201
202 std::thread incrementor([&conn0, &sig0] {
203 for (int i = 0; i < 1000; i++) {
204 sig0();
205 }
206 // Because these calls are purposely happening on both threads at the
207 // same time, these must be asserts rather than BOOST_CHECKs to prevent
208 // a race inside of BOOST_CHECK itself (writing to the log).
209 assert(!sig0.empty());
210 assert(conn0.connected());
211 });
212
214 static constexpr size_t num_extra_conns{1000};
215 std::vector<btcsignals::scoped_connection> extra_conns;
217 for (size_t i = 0; i < num_extra_conns; i++) {
218 BOOST_CHECK(!sig0.empty());
219 BOOST_CHECK(conn0.connected());
221 if (i % 2 == 0) {
222 extra_conns.emplace_back(std::move(extra));
223 }
224 sig0();
225 }
226 });
227 incrementor.join();
230 BOOST_CHECK(sig0.empty());
231
232 // sig0 will have been called 2000 times, and only the first connection did
233 // increment val_det, so it must be 2000.
234 BOOST_CHECK_EQUAL(val_det.load(), 2000);
235 // The number of connections that increment val_non_det is growing from 1
236 // to 500, where 500 are disconnected immediately again after the step.
237 // Before the end of each step the connections are called at least once.
238 // However, it is unknown how often the connections have been called
239 // exactly. The 500th Triangular Number gives a lower estimate.
240 // T_n=n(n+1)/2
241 BOOST_CHECK_GE(val_non_det.load(), 500 * 501 / 2);
242}
243
244/* Test that connection and disconnection works from within signal
245 * callbacks.
246 */
248{
251 bool nonrecursive_callback_ran{false};
252 bool recursive_callback_ran{false};
253
254 conn0 = sig0.connect([&] {
255 BOOST_CHECK(!sig0.empty());
257 });
259 sig0();
261 BOOST_CHECK(conn0.connected());
262
264 conn1 = sig0.connect([&] {
266 conn1.disconnect();
267 });
269 BOOST_CHECK(conn0.connected());
270 BOOST_CHECK(conn1.connected());
271 sig0();
273 BOOST_CHECK(conn0.connected());
274 BOOST_CHECK(!conn1.connected());
275
277 conn1 = sig0.connect([&] {
278 conn2 = sig0.connect([&] {
279 BOOST_CHECK(conn0.connected());
281 conn0.disconnect();
282 conn2.disconnect();
283 });
285 conn1.disconnect();
286 });
289 BOOST_CHECK(conn0.connected());
290 BOOST_CHECK(conn1.connected());
291 BOOST_CHECK(!conn2.connected());
292 sig0();
295 BOOST_CHECK(conn0.connected());
296 BOOST_CHECK(!conn1.connected());
297 BOOST_CHECK(conn2.connected());
298 sig0();
300 BOOST_CHECK(!conn0.connected());
301 BOOST_CHECK(!conn1.connected());
302 BOOST_CHECK(!conn2.connected());
303}
304
305/* Test that disconnection from another thread works in real time
306 */
308{
311 std::binary_semaphore done1{0};
312 std::binary_semaphore done2{0};
313 int val{0};
314
315 conn0 = sig0.connect([&](int&) {
316 conn1.disconnect();
317 done1.release();
318 done2.acquire();
319 });
320 conn1 = sig0.connect(IncrementCallback);
321 conn2 = sig0.connect(IncrementCallback);
322 std::thread thr([&] {
323 done1.acquire();
324 conn2.disconnect();
325 done2.release();
326 });
327 sig0(val);
328 thr.join();
329 BOOST_CHECK_EQUAL(val, 0);
330}
331
332
int ret
BOOST_AUTO_TEST_CASE(callback_order)
A combiner, which checks if at least one callback returned true.
Definition btcsignals.h:48
void disconnect()
If a callback is associated with this connection, prevent it from being called in the future.
Definition btcsignals.h:144
connection connect(Callable &&func) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Definition btcsignals.h:288
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
std::thread thread
Thread variable should be after other struct members so the thread does not start until the other mem...
#define BOOST_CHECK_EQUAL(v1, v2)
Definition object.cpp:17
#define BOOST_CHECK(expr)
Definition object.cpp:16
Basic testing setup.
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:82
assert(!tx.IsCoinBase())