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 : : #include <test/util/setup_common.h>
6 : : #include <util/btcsignals.h>
7 : :
8 : : #include <boost/test/unit_test.hpp>
9 : :
10 : : #include <semaphore>
11 : :
12 : : namespace {
13 : :
14 : :
15 : 4 : void IncrementCallback(int& val)
16 : : {
17 : 4 : val++;
18 : 4 : }
19 : 1 : void SquareCallback(int& val)
20 : : {
21 : 1 : val *= val;
22 : 1 : }
23 : :
24 : 2 : bool ReturnTrue()
25 : : {
26 : 2 : return true;
27 : : }
28 : 2 : bool ReturnFalse()
29 : : {
30 : 2 : return false;
31 : : }
32 : :
33 : : } // anonymous namespace
34 : :
35 : : BOOST_FIXTURE_TEST_SUITE(btcsignals_tests, BasicTestingSetup)
36 : :
37 : : /* Callbacks should always be executed in the order in which they were added
38 : : */
39 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(callback_order)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
40 : : {
41 : 1 : btcsignals::signal<void(int&)> sig0;
42 [ + - ]: 1 : sig0.connect(IncrementCallback);
43 [ + - ]: 1 : sig0.connect(SquareCallback);
44 : 1 : int val{3};
45 [ + - ]: 1 : sig0(val);
46 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 16);
47 [ + - + - : 2 : BOOST_CHECK(!sig0.empty());
+ - ]
48 : 1 : }
49 : :
50 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(disconnects)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
51 : : {
52 : 1 : btcsignals::signal<void(int&)> sig0;
53 [ + - ]: 1 : auto conn0 = sig0.connect(IncrementCallback);
54 [ + - ]: 1 : auto conn1 = sig0.connect(SquareCallback);
55 : 1 : conn1.disconnect();
56 [ + - + - : 2 : BOOST_CHECK(!sig0.empty());
+ - + - ]
57 : 1 : int val{3};
58 [ + - ]: 1 : sig0(val);
59 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 4);
60 : :
61 [ + - + - : 2 : BOOST_CHECK(!sig0.empty());
+ - ]
62 : 1 : conn0.disconnect();
63 [ + - + - : 2 : BOOST_CHECK(sig0.empty());
+ - + - ]
64 [ + - ]: 1 : sig0(val);
65 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 4);
66 : :
67 [ + - - + ]: 2 : conn0 = sig0.connect(IncrementCallback);
68 [ + - - + ]: 2 : conn1 = sig0.connect(IncrementCallback);
69 [ + - + - : 2 : BOOST_CHECK(!sig0.empty());
+ - + - ]
70 [ + - ]: 1 : sig0(val);
71 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 6);
72 : 1 : conn1.disconnect();
73 : :
74 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
75 : 1 : {
76 [ + - ]: 2 : btcsignals::scoped_connection scope(conn0);
77 : 1 : }
78 [ + - + - : 2 : BOOST_CHECK(!conn0.connected());
+ - ]
79 [ + - + - : 2 : BOOST_CHECK(sig0.empty());
+ - + - ]
80 [ + - ]: 1 : sig0(val);
81 [ + - + - : 1 : BOOST_CHECK_EQUAL(val, 6);
+ - ]
82 [ + - ]: 2 : }
83 : :
84 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(any_of_combiner)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
85 : : {
86 : 1 : btcsignals::signal<bool(), btcsignals::any_of> sig0;
87 : 1 : decltype(sig0)::result_type ret;
88 [ + - ]: 1 : ret = sig0();
89 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(ret, false);
90 : 1 : {
91 [ + - + - ]: 1 : btcsignals::scoped_connection conn0{sig0.connect(ReturnTrue)};
92 [ + - ]: 1 : ret = sig0();
93 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(ret, true);
94 : 1 : }
95 [ + - ]: 1 : ret = sig0();
96 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(ret, false);
97 : 1 : {
98 [ + - + - ]: 1 : btcsignals::scoped_connection conn0{sig0.connect(ReturnTrue)};
99 [ + - + - ]: 1 : btcsignals::scoped_connection conn1{sig0.connect(ReturnFalse)};
100 [ + - ]: 1 : ret = sig0();
101 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(ret, true);
102 : 1 : conn0.disconnect();
103 [ + - ]: 1 : ret = sig0();
104 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(ret, false);
105 : 1 : }
106 [ + - ]: 1 : ret = sig0();
107 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(ret, false);
108 : 1 : }
109 : :
110 : : /* Test the thread-safety of connect/disconnect/empty/connected/callbacks.
111 : : * Connect sig0 to an incrementor function and loop in a thread.
112 : : * Meanwhile, in another thread, inject and call new increment callbacks.
113 : : * Both threads are constantly calling empty/connected.
114 : : * The end-result must be deterministic for the atomic modified by conn0.
115 : : * Though, the end-result for the atomic modified by the extra connections is
116 : : * undefined due to a non-deterministic number of total callbacks executed.
117 : : * In any case, this should all be completely threadsafe.
118 : : * Sanitizers should pick up any buggy data race behavior (if present).
119 : : */
120 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(thread_safety)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
121 : : {
122 : 1 : btcsignals::signal<void()> sig0;
123 : 1 : std::atomic<uint32_t> val_det{0};
124 : 1 : std::atomic<uint32_t> val_non_det{0};
125 [ + - ]: 2001 : auto conn0 = sig0.connect([&val_det] { val_det++; });
126 : :
127 : 2 : std::thread incrementor([&conn0, &sig0] {
128 [ + + ]: 1001 : for (int i = 0; i < 1000; i++) {
129 : 1000 : sig0();
130 : : }
131 : : // Because these calls are purposely happening on both threads at the
132 : : // same time, these must be asserts rather than BOOST_CHECKs to prevent
133 : : // a race inside of BOOST_CHECK itself (writing to the log).
134 [ - + ]: 1 : assert(!sig0.empty());
135 [ - + ]: 1 : assert(conn0.connected());
136 [ + - ]: 2 : });
137 : :
138 : 2 : std::thread extra_increment_injector([&conn0, &sig0, &val_non_det] {
139 : 1 : static constexpr size_t num_extra_conns{1000};
140 : 1 : std::vector<btcsignals::scoped_connection> extra_conns;
141 [ + - ]: 1 : extra_conns.reserve(num_extra_conns);
142 [ + + ]: 1001 : for (size_t i = 0; i < num_extra_conns; i++) {
143 [ + - + - : 2000 : BOOST_CHECK(!sig0.empty());
+ - + - ]
144 [ + - + - : 2000 : BOOST_CHECK(conn0.connected());
+ - ]
145 [ + - + + ]: 1000 : btcsignals::scoped_connection extra{sig0.connect([&val_non_det] { val_non_det++; })};
146 [ + + ]: 1000 : if (i % 2 == 0) {
147 [ + - ]: 500 : extra_conns.emplace_back(std::move(extra));
148 : : }
149 [ + - ]: 1000 : sig0();
150 : 1000 : }
151 [ + - ]: 2 : });
152 [ + - ]: 1 : incrementor.join();
153 [ + - ]: 1 : extra_increment_injector.join();
154 : 1 : conn0.disconnect();
155 [ + - + - : 2 : BOOST_CHECK(sig0.empty());
+ - + - ]
156 : :
157 : : // sig0 will have been called 2000 times, and only the first connection did
158 : : // increment val_det, so it must be 2000.
159 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val_det.load(), 2000);
160 : : // The number of connections that increment val_non_det is growing from 1
161 : : // to 500, where 500 are disconnected immediately again after the step.
162 : : // Before the end of each step the connections are called at least once.
163 : : // However, it is unknown how often the connections have been called
164 : : // exactly. The 500th Triangular Number gives a lower estimate.
165 : : // T_n=n(n+1)/2
166 [ + - + - ]: 1 : BOOST_CHECK_GE(val_non_det.load(), 500 * 501 / 2);
167 [ + - ]: 2 : }
168 : :
169 : : /* Test that connection and disconnection works from within signal
170 : : * callbacks.
171 : : */
172 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(recursion_safety)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
173 : : {
174 : 1 : btcsignals::connection conn0, conn1, conn2;
175 : 1 : btcsignals::signal<void()> sig0;
176 : 1 : bool nonrecursive_callback_ran{false};
177 : 1 : bool recursive_callback_ran{false};
178 : :
179 [ + - ]: 5 : conn0 = sig0.connect([&] {
180 [ + - + - ]: 8 : BOOST_CHECK(!sig0.empty());
181 : 4 : nonrecursive_callback_ran = true;
182 [ - + ]: 5 : });
183 [ + - + - : 2 : BOOST_CHECK(!nonrecursive_callback_ran);
+ - ]
184 [ + - ]: 1 : sig0();
185 [ + - + - : 2 : BOOST_CHECK(nonrecursive_callback_ran);
+ - ]
186 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
187 : :
188 : 1 : nonrecursive_callback_ran = false;
189 [ + - ]: 2 : conn1 = sig0.connect([&] {
190 : 1 : nonrecursive_callback_ran = true;
191 : 1 : conn1.disconnect();
192 [ - + ]: 1 : });
193 [ + - + - : 2 : BOOST_CHECK(!nonrecursive_callback_ran);
+ - ]
194 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
195 [ + - + - : 2 : BOOST_CHECK(conn1.connected());
+ - ]
196 [ + - ]: 1 : sig0();
197 [ + - + - : 2 : BOOST_CHECK(nonrecursive_callback_ran);
+ - ]
198 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
199 [ + - + - : 2 : BOOST_CHECK(!conn1.connected());
+ - ]
200 : :
201 : 1 : nonrecursive_callback_ran = false;
202 [ + - ]: 2 : conn1 = sig0.connect([&] {
203 : 1 : conn2 = sig0.connect([&] {
204 [ + - ]: 2 : BOOST_CHECK(conn0.connected());
205 : 1 : recursive_callback_ran = true;
206 : 1 : conn0.disconnect();
207 : 1 : conn2.disconnect();
208 [ - + ]: 2 : });
209 : 1 : nonrecursive_callback_ran = true;
210 : 1 : conn1.disconnect();
211 [ - + ]: 2 : });
212 [ + - + - : 2 : BOOST_CHECK(!nonrecursive_callback_ran);
+ - ]
213 [ + - + - : 2 : BOOST_CHECK(!recursive_callback_ran);
+ - ]
214 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
215 [ + - + - : 2 : BOOST_CHECK(conn1.connected());
+ - ]
216 [ + - + - : 2 : BOOST_CHECK(!conn2.connected());
+ - ]
217 [ + - ]: 1 : sig0();
218 [ + - + - : 2 : BOOST_CHECK(nonrecursive_callback_ran);
+ - ]
219 [ + - + - : 2 : BOOST_CHECK(!recursive_callback_ran);
+ - ]
220 [ + - + - : 2 : BOOST_CHECK(conn0.connected());
+ - ]
221 [ + - + - : 2 : BOOST_CHECK(!conn1.connected());
+ - ]
222 [ + - + - : 2 : BOOST_CHECK(conn2.connected());
+ - ]
223 [ + - ]: 1 : sig0();
224 [ + - + - : 2 : BOOST_CHECK(recursive_callback_ran);
+ - ]
225 [ + - + - : 2 : BOOST_CHECK(!conn0.connected());
+ - ]
226 [ + - + - : 2 : BOOST_CHECK(!conn1.connected());
+ - ]
227 [ + - + - ]: 2 : BOOST_CHECK(!conn2.connected());
228 [ + - + - : 4 : }
+ - ]
229 : :
230 : : /* Test that disconnection from another thread works in real time
231 : : */
232 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(disconnect_thread_safety)
+ - + - -
+ + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- + - + -
+ - + - +
- + - - +
+ - + - +
- + - + -
+ - - + +
- ]
233 : : {
234 : 1 : btcsignals::connection conn0, conn1, conn2;
235 : 1 : btcsignals::signal<void(int&)> sig0;
236 [ + - ]: 1 : std::binary_semaphore done1{0};
237 : 1 : std::binary_semaphore done2{0};
238 : 1 : int val{0};
239 : :
240 [ + - ]: 2 : conn0 = sig0.connect([&](int&) {
241 : 1 : conn1.disconnect();
242 : 1 : done1.release();
243 : 1 : done2.acquire();
244 [ - + ]: 2 : });
245 [ + - - + ]: 2 : conn1 = sig0.connect(IncrementCallback);
246 [ + - - + ]: 2 : conn2 = sig0.connect(IncrementCallback);
247 : 2 : std::thread thr([&] {
248 : 1 : done1.acquire();
249 : 1 : conn2.disconnect();
250 : 1 : done2.release();
251 [ + - ]: 2 : });
252 [ + - ]: 1 : sig0(val);
253 [ + - ]: 1 : thr.join();
254 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(val, 0);
255 [ + - + - : 4 : }
+ - ]
256 : :
257 : :
258 : : BOOST_AUTO_TEST_SUITE_END()
|