validationinterface_tests.cpp raw

   1  // Copyright (c) 2020 The Limenka 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 <boost/test/unit_test.hpp>
   6  #include <consensus/validation.h>
   7  #include <primitives/block.h>
   8  #include <scheduler.h>
   9  #include <test/util/setup_common.h>
  10  #include <util/check.h>
  11  #include <kernel/chain.h>
  12  #include <validationinterface.h>
  13  
  14  #include <atomic>
  15  
  16  BOOST_FIXTURE_TEST_SUITE(validationinterface_tests, ChainTestingSetup)
  17  
  18  struct TestSubscriberNoop final : public CValidationInterface {
  19      void BlockChecked(const CBlock&, const BlockValidationState&) override {}
  20  };
  21  
  22  BOOST_AUTO_TEST_CASE(unregister_validation_interface_race)
  23  {
  24      std::atomic<bool> generate{true};
  25  
  26      // Start thread to generate notifications
  27      std::thread gen{[&] {
  28          const CBlock block_dummy;
  29          BlockValidationState state_dummy;
  30          while (generate) {
  31              m_node.validation_signals->BlockChecked(block_dummy, state_dummy);
  32          }
  33      }};
  34  
  35      // Start thread to consume notifications
  36      std::thread sub{[&] {
  37          // keep going for about 1 sec, which is 250k iterations
  38          for (int i = 0; i < 250000; i++) {
  39              auto sub = std::make_shared<TestSubscriberNoop>();
  40              m_node.validation_signals->RegisterSharedValidationInterface(sub);
  41              m_node.validation_signals->UnregisterSharedValidationInterface(sub);
  42          }
  43          // tell the other thread we are done
  44          generate = false;
  45      }};
  46  
  47      gen.join();
  48      sub.join();
  49      BOOST_CHECK(!generate);
  50  }
  51  
  52  class TestInterface : public CValidationInterface
  53  {
  54  public:
  55      TestInterface(ValidationSignals& signals, std::function<void()> on_call = nullptr, std::function<void()> on_destroy = nullptr)
  56          : m_on_call(std::move(on_call)), m_on_destroy(std::move(on_destroy)), m_signals{signals}
  57      {
  58      }
  59      virtual ~TestInterface()
  60      {
  61          if (m_on_destroy) m_on_destroy();
  62      }
  63      void BlockChecked(const CBlock& block, const BlockValidationState& state) override
  64      {
  65          if (m_on_call) m_on_call();
  66      }
  67      void Call()
  68      {
  69          CBlock block;
  70          BlockValidationState state;
  71          m_signals.BlockChecked(block, state);
  72      }
  73      std::function<void()> m_on_call;
  74      std::function<void()> m_on_destroy;
  75      ValidationSignals& m_signals;
  76  };
  77  
  78  // Regression test to ensure UnregisterAllValidationInterfaces calls don't
  79  // destroy a validation interface while it is being called. Bug:
  80  // https://github.com/limenka/limenka/pull/18551
  81  BOOST_AUTO_TEST_CASE(unregister_all_during_call)
  82  {
  83      bool destroyed = false;
  84      auto shared{std::make_shared<TestInterface>(
  85          *m_node.validation_signals,
  86          [&] {
  87              // First call should decrements reference count 2 -> 1
  88              m_node.validation_signals->UnregisterAllValidationInterfaces();
  89              BOOST_CHECK(!destroyed);
  90              // Second call should not decrement reference count 1 -> 0
  91              m_node.validation_signals->UnregisterAllValidationInterfaces();
  92              BOOST_CHECK(!destroyed);
  93          },
  94          [&] { destroyed = true; })};
  95      m_node.validation_signals->RegisterSharedValidationInterface(shared);
  96      BOOST_CHECK(shared.use_count() == 2);
  97      shared->Call();
  98      BOOST_CHECK(shared.use_count() == 1);
  99      BOOST_CHECK(!destroyed);
 100      shared.reset();
 101      BOOST_CHECK(destroyed);
 102  }
 103  
 104  BOOST_AUTO_TEST_SUITE_END()
 105