Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CCheckQueue Unit Tests #9497

Merged
merged 2 commits into from
Mar 14, 2017
Merged

CCheckQueue Unit Tests #9497

merged 2 commits into from
Mar 14, 2017

Conversation

JeremyRubin
Copy link
Contributor

This PR builds on #9495 to unit test the CCheckQueue for correctness.

The cases covered in these tests are:

  1. Standard usage
  2. Failing checks are caught
  3. Prior blocks failing don't interfere with future blocks
  4. No Memory leakage (all check destructors are called before new blocks allowed, memory is freed).
  5. Thread Safety

@@ -0,0 +1,395 @@
// Copyright (c) 2012-2015 The Bitcoin Core developers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple of years out of date :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will fix!

@fanquake fanquake added the Tests label Jan 9, 2017
@JeremyRubin JeremyRubin force-pushed the checkqueue-tests branch 2 times, most recently from 641f5b8 to f4f1426 Compare January 9, 2017 22:48
@JeremyRubin
Copy link
Contributor Author

Sorry for the line noise; the earlier build error should be addressed now.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall great and very clever tests (thread safety one was fun to figure out). Added a bunch of minor comments. The only two comments I would really urge you to consider are the ones on the Memory and FrozenCleanup tests. I think it would be good to check same conditions without allocating large chunks of memory or doing 1-second busy loops so these tests can be more efficient and more reliable.

#include <unordered_set>
#include <memory>
#include "random.h"
BOOST_FIXTURE_TEST_SUITE(checkqueue_tests, TestingSetup)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment noting BasicTestingSetup can't be used because it doesn't set nScriptCheckThreads.

*/
void Correct_Queue_range(std::vector<size_t> range)
{
auto small_queue = std::shared_ptr<Correct_Queue>(new Correct_Queue {128});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe declare 128 and any other common parameters as constants above.

while (total) {
size_t r = GetRand(10);
std::vector<FakeCheckCheckCompletion> vChecks;
for (size_t k = 0; k < r && total; k++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe replace the loop with vCheck.resize(min(total, r)).

*/
void Correct_Queue_range(std::vector<size_t> range)
{
auto small_queue = std::shared_ptr<Correct_Queue>(new Correct_Queue {128});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is small_queue a shared pointer, not a unique pointer or just plain stack variable? Maybe add a comment explaining. Also, you could probably use make_shared if it does need to be a shared pointer.

}
result[end_fails ? 0 : 1] = control.Wait();
}
BOOST_CHECK(!result[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would seem more direct to just BOOST_CHECK the control.Wait() call instead of putting the results in an intermediate array. This way is ok too, though.

auto small_queue = std::shared_ptr<Correct_Queue>(new Correct_Queue {128});
boost::thread_group tg;
for (auto x = 0; x < nScriptCheckThreads; ++x) {
tg.create_thread([=]{small_queue->Thread();});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replacing [=] with [&] might allow small_queue not to be a shared_ptr.

bool r = true;
for (size_t i = 0; i < COUNT; ++i)
r = r && UniqueCheck::results.count(i) == 1;
BOOST_REQUIRE(r);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also check that UniqueCheck::results.size == COUNT.


// Test that blocks which might allocate lots of memory free their memory agressively.
//
// This test attempts to catch a pathalogical case where by lazily freeing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pathological (spelling)

// checks might mean leaving a check un-swapped out, and decreasing by 1 each
// time could leave the data hanging across a sequence of blocks.
//
// This test (failing) is dependent on not being able to handle
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having the test be nondeterministic in this way, would anything be lost if you had the MemoryCheck constructor increment a static counter when passed a true arg, and the MemoryCheck destructor decrement the counter if the object was constructed with a true arg. Then you could detect the error case explicitly by checking the counter, and not have to allocate big chunks of memory.

//
// Note that this cannot cause a spurious failure, only could mean that
// the test doesn't actually end up checking that control waited.
MilliSleep(1000);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could easily make this test deterministic as well, eliminating the long sleep and the while (frozen) busy loops. Would just need to have ~FrozenCleanupCheck increment a counter and signal a conditional variable so you could wait here for enough jobs to be frozen, and then do the boost check. Then this could signal another condition variable to unfreeze the jobs. I think it would be worth changing this to make the test more efficient and reliable.

@JeremyRubin JeremyRubin force-pushed the checkqueue-tests branch 2 times, most recently from 95316c2 to 4beddec Compare January 12, 2017 16:58
Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK d44af13ddc3615686a3e76cf9a3412999db0d692. Left one minor comment, feel free to ignore.

while (!done && !fails2) {
fails2 = queue->ControlMutex.try_lock();
std::mutex m;
bool has_lock {false};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be clearer to replace all these bools with an enum like { STARTED, TRY_LOCK, TRY_LOCK_DONE, DONE, DONE_ACK }.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to ignore this, I don't think it makes it more clear (to me, it's easier to debug several variables). If someone disagrees strongly, will change.

@laanwj
Copy link
Member

laanwj commented Jan 19, 2017

ACK, needs squashing

@JeremyRubin
Copy link
Contributor Author

Squashed!

@JeremyRubin
Copy link
Contributor Author

Rebased to be on top of #9495.

control.Add(vChecks);
}
}
BOOST_REQUIRE(MemoryCheck::fake_allocated_memory == 0);
Copy link
Contributor

@kallewoof kallewoof Jan 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MemoryCheck struct destructor does not --, so this should not be == 0 unless no MemoryCheck constructors are ever called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was the latter. The for loop never made anything (i = 9999; i<9999). Will fix :)

};
~MemoryCheck(){
if (b) {
fake_allocated_memory += 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fake_allocated_memory -= 1

BOOST_AUTO_TEST_CASE(test_CheckQueue_Recovers_From_Failure)
{
auto fail_queue = std::unique_ptr<Failing_Queue>(new Failing_Queue {QUEUE_BATCH_SIZE});
std::array<FailingCheck, 100> checks;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable.

// Wait until the queue has finished all jobs and frozen
FrozenCleanupCheck::cv.wait(l, [](){return FrozenCleanupCheck::nFrozen == 1;});
// Try to get control of the queue a bunch of times
for (auto x = 0; x < 100; ++x) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing !fails && x < 100 here and simply fails = queue->ControlMutex.try_lock(); would break iteration on first fail rather than iterate over all 100 (e.g. if first try_lock() fails).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure..

// Wait for thread to get the lock
cv.wait(l, [&](){return has_lock;});
bool fails = false;
for (auto x = 0; x < 100; ++x) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here with !fails && x < 100 as above.

@JeremyRubin
Copy link
Contributor Author

Fixed the issues that @kallewoof raised, and squashed.

Unsquashed preserved here https://github.com/JeremyRubin/bitcoin/tree/checkqueue-tests-unsquashed.

@kallewoof
Copy link
Contributor

kallewoof commented Feb 25, 2017

utACK 96c7f2c

I'm a bit concerned about non-deterministic behavior in tests as this tends to be a pain when you do run into a problem. Or is this fixed seed / PRNG so that the numbers are always the same each time? (for GetRand())

@JeremyRubin
Copy link
Contributor Author

I could make them deterministic if that's desirable, but realistically these tests are already non-deterministic by virtue of being multithreaded. None of the uses of GetRand are particularly dangerous here, although perhaps they area a little slower than could be.

@kallewoof
Copy link
Contributor

I think that would be desirable, even if the multithreading makes it not 100%.

@kallewoof
Copy link
Contributor

ACK 96c7f2c

@JeremyRubin JeremyRubin mentioned this pull request Mar 7, 2017
@laanwj laanwj merged commit 96c7f2c into bitcoin:master Mar 14, 2017
laanwj added a commit that referenced this pull request Mar 14, 2017
96c7f2c Add CheckQueue Tests (Jeremy Rubin)
e207342 Fix CCheckQueue IsIdle (potential) race condition and remove dangerous constructors. (Jeremy Rubin)

Tree-SHA512: 5989743ad0f8b08998335e7ca9256e168fa319053f91b9dece9dbb134885bef7753b567b591acc7135785f23d19799ed7e6375917f59fe0178d389e961633d62
luke-jr added a commit to luke-jr/bitcoin that referenced this pull request Jun 5, 2017
codablock pushed a commit to codablock/dash that referenced this pull request Jan 26, 2018
96c7f2c Add CheckQueue Tests (Jeremy Rubin)
e207342 Fix CCheckQueue IsIdle (potential) race condition and remove dangerous constructors. (Jeremy Rubin)

Tree-SHA512: 5989743ad0f8b08998335e7ca9256e168fa319053f91b9dece9dbb134885bef7753b567b591acc7135785f23d19799ed7e6375917f59fe0178d389e961633d62
sickpig referenced this pull request in sickpig/BitcoinUnlimited Apr 27, 2018
96c7f2c Add CheckQueue Tests (Jeremy Rubin)
e207342 Fix CCheckQueue IsIdle (potential) race condition and remove dangerous constructors. (Jeremy Rubin)
michelvankessel added a commit to michelvankessel/blackcoin-more that referenced this pull request Jan 5, 2019
andvgal pushed a commit to energicryptocurrency/gen2-energi that referenced this pull request Jan 6, 2019
96c7f2c Add CheckQueue Tests (Jeremy Rubin)
e207342 Fix CCheckQueue IsIdle (potential) race condition and remove dangerous constructors. (Jeremy Rubin)

Tree-SHA512: 5989743ad0f8b08998335e7ca9256e168fa319053f91b9dece9dbb134885bef7753b567b591acc7135785f23d19799ed7e6375917f59fe0178d389e961633d62
CryptoCentric pushed a commit to absolute-community/absolute that referenced this pull request Feb 27, 2019
96c7f2c Add CheckQueue Tests (Jeremy Rubin)
e207342 Fix CCheckQueue IsIdle (potential) race condition and remove dangerous constructors. (Jeremy Rubin)

Tree-SHA512: 5989743ad0f8b08998335e7ca9256e168fa319053f91b9dece9dbb134885bef7753b567b591acc7135785f23d19799ed7e6375917f59fe0178d389e961633d62
LarryRuane pushed a commit to LarryRuane/zcash that referenced this pull request Apr 2, 2021
LarryRuane pushed a commit to LarryRuane/zcash that referenced this pull request Apr 2, 2021
zcash: cherry picked from commit 96c7f2c
zcash: bitcoin/bitcoin#9497
zkbot added a commit to zcash/zcash that referenced this pull request Apr 2, 2021
Bitcoin 0.15 locking PRs

These are locking changes from upstream (bitcoin core) release 0.15, oldest to newest (when merged to the master branch).

Each commit also includes a reference both to the PR and the upstream commit.

- bitcoin/bitcoin#9497
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Sep 8, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants