mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-17 01:58:57 +02:00
Previously, the signal was using btcsignals::optional_last_value<bool>.
However, this only worked by accident:
The return value was influenced by the order in which the connections
were done. The noui callbacks would always overwrite the return value
with false. This makes the code overall brittle, and confusing.
For example, the following patch that changes the order of connections
would break the only and single place where the return value actually
matters:
```diff
diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp
index 0b89c605b9..976549470e 100644
--- a/src/qt/bitcoin.cpp
+++ b/src/qt/bitcoin.cpp
@@ -488,3 +488,2 @@ int GuiMain(int argc, char* argv[])
btcsignals::scoped_connection handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBox);
- btcsignals::scoped_connection handler_question = ::uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestion);
btcsignals::scoped_connection handler_init_message = ::uiInterface.InitMessage_connect(noui_InitMessage);
@@ -663,2 +662,3 @@ int GuiMain(int argc, char* argv[])
app.createWindow(networkStyle.data());
+ btcsignals::scoped_connection handler_question = ::uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestion);
// Perform base initialization before spinning up initialization/shutdown thread
```
This can be tested by applying the patch and then calling:
(May have to be started twice to trigger the question)
```
bitcoin-qt -regtest -datadir=/tmp -mocktime=123456789
```
Before the changes in this commit (on current master), pressing `OK`
would not have any effect and would abort the program.
After the changes in this commit, pressing `OK` will correctly trigger a
-reindex and leave the program running.
251 lines
6.9 KiB
C++
251 lines
6.9 KiB
C++
// Copyright (c) The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <btcsignals.h>
|
|
#include <test/util/setup_common.h>
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
#include <semaphore>
|
|
|
|
namespace {
|
|
|
|
|
|
void IncrementCallback(int& val)
|
|
{
|
|
val++;
|
|
}
|
|
void SquareCallback(int& val)
|
|
{
|
|
val *= val;
|
|
}
|
|
|
|
bool ReturnTrue()
|
|
{
|
|
return true;
|
|
}
|
|
bool ReturnFalse()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
BOOST_FIXTURE_TEST_SUITE(btcsignals_tests, BasicTestingSetup)
|
|
|
|
/* Callbacks should always be executed in the order in which they were added
|
|
*/
|
|
BOOST_AUTO_TEST_CASE(callback_order)
|
|
{
|
|
btcsignals::signal<void(int&)> sig0;
|
|
sig0.connect(IncrementCallback);
|
|
sig0.connect(SquareCallback);
|
|
int val{3};
|
|
sig0(val);
|
|
BOOST_CHECK_EQUAL(val, 16);
|
|
BOOST_CHECK(!sig0.empty());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(disconnects)
|
|
{
|
|
btcsignals::signal<void(int&)> sig0;
|
|
auto conn0 = sig0.connect(IncrementCallback);
|
|
auto conn1 = sig0.connect(SquareCallback);
|
|
conn1.disconnect();
|
|
BOOST_CHECK(!sig0.empty());
|
|
int val{3};
|
|
sig0(val);
|
|
BOOST_CHECK_EQUAL(val, 4);
|
|
|
|
BOOST_CHECK(!sig0.empty());
|
|
conn0.disconnect();
|
|
BOOST_CHECK(sig0.empty());
|
|
sig0(val);
|
|
BOOST_CHECK_EQUAL(val, 4);
|
|
|
|
conn0 = sig0.connect(IncrementCallback);
|
|
conn1 = sig0.connect(IncrementCallback);
|
|
BOOST_CHECK(!sig0.empty());
|
|
sig0(val);
|
|
BOOST_CHECK_EQUAL(val, 6);
|
|
conn1.disconnect();
|
|
|
|
BOOST_CHECK(conn0.connected());
|
|
{
|
|
btcsignals::scoped_connection scope(conn0);
|
|
}
|
|
BOOST_CHECK(!conn0.connected());
|
|
BOOST_CHECK(sig0.empty());
|
|
sig0(val);
|
|
BOOST_CHECK_EQUAL(val, 6);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(any_of_combiner)
|
|
{
|
|
btcsignals::signal<bool(), btcsignals::any_of> sig0;
|
|
decltype(sig0)::result_type ret;
|
|
ret = sig0();
|
|
BOOST_CHECK_EQUAL(ret, false);
|
|
{
|
|
btcsignals::scoped_connection conn0{sig0.connect(ReturnTrue)};
|
|
ret = sig0();
|
|
BOOST_CHECK_EQUAL(ret, true);
|
|
}
|
|
ret = sig0();
|
|
BOOST_CHECK_EQUAL(ret, false);
|
|
{
|
|
btcsignals::scoped_connection conn0{sig0.connect(ReturnTrue)};
|
|
btcsignals::scoped_connection conn1{sig0.connect(ReturnFalse)};
|
|
ret = sig0();
|
|
BOOST_CHECK_EQUAL(ret, true);
|
|
conn0.disconnect();
|
|
ret = sig0();
|
|
BOOST_CHECK_EQUAL(ret, false);
|
|
}
|
|
ret = sig0();
|
|
BOOST_CHECK_EQUAL(ret, false);
|
|
}
|
|
|
|
/* Test the thread-safety of connect/disconnect/empty/connected/callbacks.
|
|
* Connect sig0 to an incrementor function and loop in a thread.
|
|
* Meanwhile, in another thread, inject and call new increment callbacks.
|
|
* Both threads are constantly calling empty/connected.
|
|
* Though the end-result is undefined due to a non-deterministic number of
|
|
* total callbacks executed, this should all be completely threadsafe.
|
|
* Sanitizers should pick up any buggy data race behavior (if present).
|
|
*/
|
|
BOOST_AUTO_TEST_CASE(thread_safety)
|
|
{
|
|
btcsignals::signal<void()> sig0;
|
|
std::atomic<uint32_t> val{0};
|
|
auto conn0 = sig0.connect([&val] {
|
|
val++;
|
|
});
|
|
|
|
std::thread incrementor([&conn0, &sig0] {
|
|
for (int i = 0; i < 1000; i++) {
|
|
sig0();
|
|
}
|
|
// Because these calls are purposely happening on both threads at the
|
|
// same time, these must be asserts rather than BOOST_CHECKs to prevent
|
|
// a race inside of BOOST_CHECK itself (writing to the log).
|
|
assert(!sig0.empty());
|
|
assert(conn0.connected());
|
|
});
|
|
|
|
std::thread extra_increment_injector([&conn0, &sig0, &val] {
|
|
static constexpr size_t num_extra_conns{1000};
|
|
std::vector<btcsignals::scoped_connection> extra_conns;
|
|
extra_conns.reserve(num_extra_conns);
|
|
for (size_t i = 0; i < num_extra_conns; i++) {
|
|
BOOST_CHECK(!sig0.empty());
|
|
BOOST_CHECK(conn0.connected());
|
|
extra_conns.emplace_back(sig0.connect([&val] {
|
|
val++;
|
|
}));
|
|
sig0();
|
|
}
|
|
});
|
|
incrementor.join();
|
|
extra_increment_injector.join();
|
|
conn0.disconnect();
|
|
BOOST_CHECK(sig0.empty());
|
|
|
|
// sig will have been called 2000 times, and at least 1000 of those will
|
|
// have been executing multiple incrementing callbacks. So while val is
|
|
// probably MUCH bigger, it's guaranteed to be at least 3000.
|
|
BOOST_CHECK_GE(val.load(), 3000);
|
|
}
|
|
|
|
/* Test that connection and disconnection works from within signal
|
|
* callbacks.
|
|
*/
|
|
BOOST_AUTO_TEST_CASE(recursion_safety)
|
|
{
|
|
btcsignals::connection conn0, conn1, conn2;
|
|
btcsignals::signal<void()> sig0;
|
|
bool nonrecursive_callback_ran{false};
|
|
bool recursive_callback_ran{false};
|
|
|
|
conn0 = sig0.connect([&] {
|
|
BOOST_CHECK(!sig0.empty());
|
|
nonrecursive_callback_ran = true;
|
|
});
|
|
BOOST_CHECK(!nonrecursive_callback_ran);
|
|
sig0();
|
|
BOOST_CHECK(nonrecursive_callback_ran);
|
|
BOOST_CHECK(conn0.connected());
|
|
|
|
nonrecursive_callback_ran = false;
|
|
conn1 = sig0.connect([&] {
|
|
nonrecursive_callback_ran = true;
|
|
conn1.disconnect();
|
|
});
|
|
BOOST_CHECK(!nonrecursive_callback_ran);
|
|
BOOST_CHECK(conn0.connected());
|
|
BOOST_CHECK(conn1.connected());
|
|
sig0();
|
|
BOOST_CHECK(nonrecursive_callback_ran);
|
|
BOOST_CHECK(conn0.connected());
|
|
BOOST_CHECK(!conn1.connected());
|
|
|
|
nonrecursive_callback_ran = false;
|
|
conn1 = sig0.connect([&] {
|
|
conn2 = sig0.connect([&] {
|
|
BOOST_CHECK(conn0.connected());
|
|
recursive_callback_ran = true;
|
|
conn0.disconnect();
|
|
conn2.disconnect();
|
|
});
|
|
nonrecursive_callback_ran = true;
|
|
conn1.disconnect();
|
|
});
|
|
BOOST_CHECK(!nonrecursive_callback_ran);
|
|
BOOST_CHECK(!recursive_callback_ran);
|
|
BOOST_CHECK(conn0.connected());
|
|
BOOST_CHECK(conn1.connected());
|
|
BOOST_CHECK(!conn2.connected());
|
|
sig0();
|
|
BOOST_CHECK(nonrecursive_callback_ran);
|
|
BOOST_CHECK(!recursive_callback_ran);
|
|
BOOST_CHECK(conn0.connected());
|
|
BOOST_CHECK(!conn1.connected());
|
|
BOOST_CHECK(conn2.connected());
|
|
sig0();
|
|
BOOST_CHECK(recursive_callback_ran);
|
|
BOOST_CHECK(!conn0.connected());
|
|
BOOST_CHECK(!conn1.connected());
|
|
BOOST_CHECK(!conn2.connected());
|
|
}
|
|
|
|
/* Test that disconnection from another thread works in real time
|
|
*/
|
|
BOOST_AUTO_TEST_CASE(disconnect_thread_safety)
|
|
{
|
|
btcsignals::connection conn0, conn1, conn2;
|
|
btcsignals::signal<void(int&)> sig0;
|
|
std::binary_semaphore done1{0};
|
|
std::binary_semaphore done2{0};
|
|
int val{0};
|
|
|
|
conn0 = sig0.connect([&](int&) {
|
|
conn1.disconnect();
|
|
done1.release();
|
|
done2.acquire();
|
|
});
|
|
conn1 = sig0.connect(IncrementCallback);
|
|
conn2 = sig0.connect(IncrementCallback);
|
|
std::thread thr([&] {
|
|
done1.acquire();
|
|
conn2.disconnect();
|
|
done2.release();
|
|
});
|
|
sig0(val);
|
|
thr.join();
|
|
BOOST_CHECK_EQUAL(val, 0);
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|