btcsignals_tests.cpp raw

   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  void IncrementCallback(int& val)
  16  {
  17      val++;
  18  }
  19  void SquareCallback(int& val)
  20  {
  21      val *= val;
  22  }
  23  
  24  bool ReturnTrue()
  25  {
  26      return true;
  27  }
  28  bool ReturnFalse()
  29  {
  30      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  BOOST_AUTO_TEST_CASE(callback_order)
  40  {
  41      btcsignals::signal<void(int&)> sig0;
  42      sig0.connect(IncrementCallback);
  43      sig0.connect(SquareCallback);
  44      int val{3};
  45      sig0(val);
  46      BOOST_CHECK_EQUAL(val, 16);
  47      BOOST_CHECK(!sig0.empty());
  48  }
  49  
  50  BOOST_AUTO_TEST_CASE(disconnects)
  51  {
  52      btcsignals::signal<void(int&)> sig0;
  53      auto conn0 = sig0.connect(IncrementCallback);
  54      auto conn1 = sig0.connect(SquareCallback);
  55      conn1.disconnect();
  56      BOOST_CHECK(!sig0.empty());
  57      int val{3};
  58      sig0(val);
  59      BOOST_CHECK_EQUAL(val, 4);
  60  
  61      BOOST_CHECK(!sig0.empty());
  62      conn0.disconnect();
  63      BOOST_CHECK(sig0.empty());
  64      sig0(val);
  65      BOOST_CHECK_EQUAL(val, 4);
  66  
  67      conn0 = sig0.connect(IncrementCallback);
  68      conn1 = sig0.connect(IncrementCallback);
  69      BOOST_CHECK(!sig0.empty());
  70      sig0(val);
  71      BOOST_CHECK_EQUAL(val, 6);
  72      conn1.disconnect();
  73  
  74      BOOST_CHECK(conn0.connected());
  75      {
  76          btcsignals::scoped_connection scope(conn0);
  77      }
  78      BOOST_CHECK(!conn0.connected());
  79      BOOST_CHECK(sig0.empty());
  80      sig0(val);
  81      BOOST_CHECK_EQUAL(val, 6);
  82  }
  83  
  84  BOOST_AUTO_TEST_CASE(any_of_combiner)
  85  {
  86      btcsignals::signal<bool(), btcsignals::any_of> sig0;
  87      decltype(sig0)::result_type ret;
  88      ret = sig0();
  89      BOOST_CHECK_EQUAL(ret, false);
  90      {
  91          btcsignals::scoped_connection conn0{sig0.connect(ReturnTrue)};
  92          ret = sig0();
  93          BOOST_CHECK_EQUAL(ret, true);
  94      }
  95      ret = sig0();
  96      BOOST_CHECK_EQUAL(ret, false);
  97      {
  98          btcsignals::scoped_connection conn0{sig0.connect(ReturnTrue)};
  99          btcsignals::scoped_connection conn1{sig0.connect(ReturnFalse)};
 100          ret = sig0();
 101          BOOST_CHECK_EQUAL(ret, true);
 102          conn0.disconnect();
 103          ret = sig0();
 104          BOOST_CHECK_EQUAL(ret, false);
 105      }
 106      ret = sig0();
 107      BOOST_CHECK_EQUAL(ret, false);
 108  }
 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  BOOST_AUTO_TEST_CASE(thread_safety)
 121  {
 122      btcsignals::signal<void()> sig0;
 123      std::atomic<uint32_t> val_det{0};
 124      std::atomic<uint32_t> val_non_det{0};
 125      auto conn0 = sig0.connect([&val_det] { val_det++; });
 126  
 127      std::thread incrementor([&conn0, &sig0] {
 128          for (int i = 0; i < 1000; i++) {
 129              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          assert(!sig0.empty());
 135          assert(conn0.connected());
 136      });
 137  
 138      std::thread extra_increment_injector([&conn0, &sig0, &val_non_det] {
 139          static constexpr size_t num_extra_conns{1000};
 140          std::vector<btcsignals::scoped_connection> extra_conns;
 141          extra_conns.reserve(num_extra_conns);
 142          for (size_t i = 0; i < num_extra_conns; i++) {
 143              BOOST_CHECK(!sig0.empty());
 144              BOOST_CHECK(conn0.connected());
 145              btcsignals::scoped_connection extra{sig0.connect([&val_non_det] { val_non_det++; })};
 146              if (i % 2 == 0) {
 147                  extra_conns.emplace_back(std::move(extra));
 148              }
 149              sig0();
 150          }
 151      });
 152      incrementor.join();
 153      extra_increment_injector.join();
 154      conn0.disconnect();
 155      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      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      BOOST_CHECK_GE(val_non_det.load(), 500 * 501 / 2);
 167  }
 168  
 169  /* Test that connection and disconnection works from within signal
 170   * callbacks.
 171   */
 172  BOOST_AUTO_TEST_CASE(recursion_safety)
 173  {
 174      btcsignals::connection conn0, conn1, conn2;
 175      btcsignals::signal<void()> sig0;
 176      bool nonrecursive_callback_ran{false};
 177      bool recursive_callback_ran{false};
 178  
 179      conn0 = sig0.connect([&] {
 180          BOOST_CHECK(!sig0.empty());
 181          nonrecursive_callback_ran = true;
 182      });
 183      BOOST_CHECK(!nonrecursive_callback_ran);
 184      sig0();
 185      BOOST_CHECK(nonrecursive_callback_ran);
 186      BOOST_CHECK(conn0.connected());
 187  
 188      nonrecursive_callback_ran = false;
 189      conn1 = sig0.connect([&] {
 190          nonrecursive_callback_ran = true;
 191          conn1.disconnect();
 192      });
 193      BOOST_CHECK(!nonrecursive_callback_ran);
 194      BOOST_CHECK(conn0.connected());
 195      BOOST_CHECK(conn1.connected());
 196      sig0();
 197      BOOST_CHECK(nonrecursive_callback_ran);
 198      BOOST_CHECK(conn0.connected());
 199      BOOST_CHECK(!conn1.connected());
 200  
 201      nonrecursive_callback_ran = false;
 202      conn1 = sig0.connect([&] {
 203          conn2 = sig0.connect([&] {
 204              BOOST_CHECK(conn0.connected());
 205              recursive_callback_ran = true;
 206              conn0.disconnect();
 207              conn2.disconnect();
 208          });
 209          nonrecursive_callback_ran = true;
 210          conn1.disconnect();
 211      });
 212      BOOST_CHECK(!nonrecursive_callback_ran);
 213      BOOST_CHECK(!recursive_callback_ran);
 214      BOOST_CHECK(conn0.connected());
 215      BOOST_CHECK(conn1.connected());
 216      BOOST_CHECK(!conn2.connected());
 217      sig0();
 218      BOOST_CHECK(nonrecursive_callback_ran);
 219      BOOST_CHECK(!recursive_callback_ran);
 220      BOOST_CHECK(conn0.connected());
 221      BOOST_CHECK(!conn1.connected());
 222      BOOST_CHECK(conn2.connected());
 223      sig0();
 224      BOOST_CHECK(recursive_callback_ran);
 225      BOOST_CHECK(!conn0.connected());
 226      BOOST_CHECK(!conn1.connected());
 227      BOOST_CHECK(!conn2.connected());
 228  }
 229  
 230  /* Test that disconnection from another thread works in real time
 231   */
 232  BOOST_AUTO_TEST_CASE(disconnect_thread_safety)
 233  {
 234      btcsignals::connection conn0, conn1, conn2;
 235      btcsignals::signal<void(int&)> sig0;
 236      std::binary_semaphore done1{0};
 237      std::binary_semaphore done2{0};
 238      int val{0};
 239  
 240      conn0 = sig0.connect([&](int&) {
 241          conn1.disconnect();
 242          done1.release();
 243          done2.acquire();
 244      });
 245      conn1 = sig0.connect(IncrementCallback);
 246      conn2 = sig0.connect(IncrementCallback);
 247      std::thread thr([&] {
 248          done1.acquire();
 249          conn2.disconnect();
 250          done2.release();
 251      });
 252      sig0(val);
 253      thr.join();
 254      BOOST_CHECK_EQUAL(val, 0);
 255  }
 256  
 257  
 258  BOOST_AUTO_TEST_SUITE_END()
 259