Bitcoin Core 31.99.0
P2P Digital Currency
checkqueue_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2012-present 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 <checkqueue.h>
6#include <common/args.h>
7#include <sync.h>
8#include <test/util/random.h>
10#include <util/chaintype.h>
11#include <util/time.h>
12
13#include <boost/test/unit_test.hpp>
14
15#include <atomic>
16#include <condition_variable>
17#include <mutex>
18#include <thread>
19#include <unordered_set>
20#include <utility>
21#include <vector>
22
30#ifdef DEBUG_LOCKCONTENTION
31 : BasicTestingSetup{ChainType::MAIN, {.extra_args = { "-debugexclude=lock" } }} {}
32#else
34#endif
35};
36
38 void Correct_Queue_range(std::vector<size_t> range);
39};
40
41static const unsigned int QUEUE_BATCH_SIZE = 128;
42static const int SCRIPT_CHECK_THREADS = 3;
43
44struct FakeCheck {
45 std::optional<int> operator()() const
46 {
47 return std::nullopt;
48 }
49};
50
52 static std::atomic<size_t> n_calls;
53 std::optional<int> operator()()
54 {
55 n_calls.fetch_add(1, std::memory_order_relaxed);
56 return std::nullopt;
57 }
58};
59
61{
62 std::optional<int> m_result;
63 FixedCheck(std::optional<int> result) : m_result(result){};
64 std::optional<int> operator()() const { return m_result; }
65};
66
68 static Mutex m;
69 static std::unordered_multiset<size_t> results GUARDED_BY(m);
70 size_t check_id;
71 UniqueCheck(size_t check_id_in) : check_id(check_id_in){};
72 std::optional<int> operator()()
73 {
74 LOCK(m);
75 results.insert(check_id);
76 return std::nullopt;
77 }
78};
79
80
82 static std::atomic<size_t> fake_allocated_memory;
83 bool b {false};
84 std::optional<int> operator()() const
85 {
86 return std::nullopt;
87 }
89 {
90 // We have to do this to make sure that destructor calls are paired
91 //
92 // Really, copy constructor should be deletable, but CCheckQueue breaks
93 // if it is deleted because of internal push_back.
94 fake_allocated_memory.fetch_add(b, std::memory_order_relaxed);
95 };
96 MemoryCheck(bool b_) : b(b_)
97 {
98 fake_allocated_memory.fetch_add(b, std::memory_order_relaxed);
99 };
101 {
102 fake_allocated_memory.fetch_sub(b, std::memory_order_relaxed);
103 };
104};
105
107 static std::atomic<uint64_t> nFrozen;
108 static std::condition_variable cv;
109 static std::mutex m;
110 bool should_freeze{true};
111 std::optional<int> operator()() const
112 {
113 return std::nullopt;
114 }
117 {
118 if (should_freeze) {
119 std::unique_lock<std::mutex> l(m);
120 nFrozen.store(1, std::memory_order_relaxed);
121 cv.notify_one();
122 cv.wait(l, []{ return nFrozen.load(std::memory_order_relaxed) == 0;});
123 }
124 }
126 {
127 should_freeze = other.should_freeze;
128 other.should_freeze = false;
129 }
131 {
132 should_freeze = other.should_freeze;
133 other.should_freeze = false;
134 return *this;
135 }
136};
137
138// Static Allocations
139std::mutex FrozenCleanupCheck::m{};
140std::atomic<uint64_t> FrozenCleanupCheck::nFrozen{0};
141std::condition_variable FrozenCleanupCheck::cv{};
143std::unordered_multiset<size_t> UniqueCheck::results;
144std::atomic<size_t> FakeCheckCheckCompletion::n_calls{0};
145std::atomic<size_t> MemoryCheck::fake_allocated_memory{0};
146
147// Queue Typedefs
154
155
159void CheckQueueTest::Correct_Queue_range(std::vector<size_t> range)
160{
161 auto small_queue = std::make_unique<Correct_Queue>(QUEUE_BATCH_SIZE, SCRIPT_CHECK_THREADS);
162 // Make vChecks here to save on malloc (this test can be slow...)
163 std::vector<FakeCheckCheckCompletion> vChecks;
164 vChecks.reserve(9);
165 for (const size_t i : range) {
166 size_t total = i;
169 while (total) {
170 vChecks.clear();
171 vChecks.resize(std::min<size_t>(total, m_rng.randrange(10)));
172 total -= vChecks.size();
173 control.Add(std::move(vChecks));
174 }
175 BOOST_REQUIRE(!control.Complete().has_value());
176 BOOST_REQUIRE_EQUAL(FakeCheckCheckCompletion::n_calls, i);
177 }
178}
179
181
182
184BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_Zero)
185{
186 std::vector<size_t> range;
187 range.push_back(size_t{0});
188 Correct_Queue_range(range);
189}
192BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_One)
193{
194 std::vector<size_t> range;
195 range.push_back(size_t{1});
196 Correct_Queue_range(range);
197}
200BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_Max)
201{
202 std::vector<size_t> range;
203 range.push_back(100000);
204 Correct_Queue_range(range);
205}
208BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_Random)
209{
210 std::vector<size_t> range;
211 range.reserve(100000/1000);
212 for (size_t i = 2; i < 100000; i += std::max((size_t)1, (size_t)m_rng.randrange(std::min((size_t)1000, ((size_t)100000) - i))))
213 range.push_back(i);
214 Correct_Queue_range(range);
215}
216
217
219BOOST_AUTO_TEST_CASE(test_CheckQueue_Catches_Failure)
220{
221 auto fixed_queue = std::make_unique<Fixed_Queue>(QUEUE_BATCH_SIZE, SCRIPT_CHECK_THREADS);
222 for (size_t i = 0; i < 1001; ++i) {
223 CCheckQueueControl<FixedCheck> control(*fixed_queue);
224 size_t remaining = i;
225 while (remaining) {
226 size_t r = m_rng.randrange(10);
227
228 std::vector<FixedCheck> vChecks;
229 vChecks.reserve(r);
230 for (size_t k = 0; k < r && remaining; k++, remaining--)
231 vChecks.emplace_back(remaining == 1 ? std::make_optional<int>(17 * i) : std::nullopt);
232 control.Add(std::move(vChecks));
233 }
234 auto result = control.Complete();
235 if (i > 0) {
236 BOOST_REQUIRE(result.has_value());
237 BOOST_REQUIRE(*result == static_cast<int>(17 * i));
238 } else {
239 BOOST_REQUIRE(!result.has_value());
240 }
241 }
242}
243// Test that a block validation which fails does not interfere with
244// future blocks, ie, the bad state is cleared.
245BOOST_AUTO_TEST_CASE(test_CheckQueue_Recovers_From_Failure)
246{
247 auto fail_queue = std::make_unique<Fixed_Queue>(QUEUE_BATCH_SIZE, SCRIPT_CHECK_THREADS);
248 for (auto times = 0; times < 10; ++times) {
249 for (const bool end_fails : {true, false}) {
250 CCheckQueueControl<FixedCheck> control(*fail_queue);
251 {
252 std::vector<FixedCheck> vChecks;
253 vChecks.resize(100, FixedCheck(std::nullopt));
254 vChecks[99] = FixedCheck(end_fails ? std::make_optional<int>(2) : std::nullopt);
255 control.Add(std::move(vChecks));
256 }
257 bool r = !control.Complete().has_value();
258 BOOST_REQUIRE(r != end_fails);
259 }
260 }
261}
262
263// Test that unique checks are actually all called individually, rather than
264// just one check being called repeatedly. Test that checks are not called
265// more than once as well
266BOOST_AUTO_TEST_CASE(test_CheckQueue_UniqueCheck)
267{
268 auto queue = std::make_unique<Unique_Queue>(QUEUE_BATCH_SIZE, SCRIPT_CHECK_THREADS);
269 size_t COUNT = 100000;
270 size_t total = COUNT;
271 {
272 CCheckQueueControl<UniqueCheck> control(*queue);
273 while (total) {
274 size_t r = m_rng.randrange(10);
275 std::vector<UniqueCheck> vChecks;
276 for (size_t k = 0; k < r && total; k++)
277 vChecks.emplace_back(--total);
278 control.Add(std::move(vChecks));
279 }
280 }
281 {
283 bool r = true;
284 BOOST_REQUIRE_EQUAL(UniqueCheck::results.size(), COUNT);
285 for (size_t i = 0; i < COUNT; ++i) {
286 r = r && UniqueCheck::results.count(i) == 1;
287 }
288 BOOST_REQUIRE(r);
289 }
290}
291
292
293// Test that blocks which might allocate lots of memory free their memory aggressively.
294//
295// This test attempts to catch a pathological case where by lazily freeing
296// checks might mean leaving a check un-swapped out, and decreasing by 1 each
297// time could leave the data hanging across a sequence of blocks.
298BOOST_AUTO_TEST_CASE(test_CheckQueue_Memory)
299{
300 auto queue = std::make_unique<Memory_Queue>(QUEUE_BATCH_SIZE, SCRIPT_CHECK_THREADS);
301 for (size_t i = 0; i < 1000; ++i) {
302 size_t total = i;
303 {
304 CCheckQueueControl<MemoryCheck> control(*queue);
305 while (total) {
306 size_t r = m_rng.randrange(10);
307 std::vector<MemoryCheck> vChecks;
308 for (size_t k = 0; k < r && total; k++) {
309 total--;
310 // Each iteration leaves data at the front, back, and middle
311 // to catch any sort of deallocation failure
312 vChecks.emplace_back(total == 0 || total == i || total == i/2);
313 }
314 control.Add(std::move(vChecks));
315 }
316 }
317 BOOST_REQUIRE_EQUAL(MemoryCheck::fake_allocated_memory, 0U);
318 }
319}
320
321// Test that a new verification cannot occur until all checks
322// have been destructed
323BOOST_AUTO_TEST_CASE(test_CheckQueue_FrozenCleanup)
324{
325 auto queue = std::make_unique<FrozenCleanup_Queue>(QUEUE_BATCH_SIZE, SCRIPT_CHECK_THREADS);
326 bool fails = false;
327 std::thread t0([&]() {
329 std::vector<FrozenCleanupCheck> vChecks(1);
330 control.Add(std::move(vChecks));
331 auto result = control.Complete(); // Hangs here
332 assert(!result);
333 });
334 {
335 std::unique_lock<std::mutex> l(FrozenCleanupCheck::m);
336 // Wait until the queue has finished all jobs and frozen
337 FrozenCleanupCheck::cv.wait(l, [](){return FrozenCleanupCheck::nFrozen == 1;});
338 }
339 // Try to get control of the queue a bunch of times
340 for (auto x = 0; x < 100 && !fails; ++x) {
341 fails = queue->m_control_mutex.try_lock();
342 }
343 {
344 // Unfreeze (we need lock n case of spurious wakeup)
345 std::unique_lock<std::mutex> l(FrozenCleanupCheck::m);
347 }
348 // Awaken frozen destructor
349 FrozenCleanupCheck::cv.notify_one();
350 // Wait for control to finish
351 t0.join();
352 BOOST_REQUIRE(!fails);
353}
354
355
357BOOST_AUTO_TEST_CASE(test_CheckQueueControl_Locks)
358{
359 auto queue = std::make_unique<Standard_Queue>(QUEUE_BATCH_SIZE, SCRIPT_CHECK_THREADS);
360 {
361 std::vector<std::thread> tg;
362 tg.reserve(3);
363 std::atomic<int> nThreads {0};
364 std::atomic<int> fails {0};
365 for (size_t i = 0; i < 3; ++i) {
366 tg.emplace_back(
367 [&]{
368 CCheckQueueControl<FakeCheck> control(*queue);
369 // While sleeping, no other thread should execute to this point
370 auto observed = ++nThreads;
371 UninterruptibleSleep(std::chrono::milliseconds{10});
372 fails += observed != nThreads;
373 });
374 }
375 for (auto& thread: tg) {
376 if (thread.joinable()) thread.join();
377 }
378 BOOST_REQUIRE_EQUAL(fails, 0);
379 }
380 {
381 std::vector<std::thread> tg;
382 std::mutex m;
383 std::condition_variable cv;
384 bool has_lock{false};
385 bool has_tried{false};
386 bool done{false};
387 bool done_ack{false};
388 {
389 std::unique_lock<std::mutex> l(m);
390 tg.emplace_back([&]{
391 CCheckQueueControl<FakeCheck> control(*queue);
392 std::unique_lock<std::mutex> ll(m);
393 has_lock = true;
394 cv.notify_one();
395 cv.wait(ll, [&]{return has_tried;});
396 done = true;
397 cv.notify_one();
398 // Wait until the done is acknowledged
399 //
400 cv.wait(ll, [&]{return done_ack;});
401 });
402 // Wait for thread to get the lock
403 cv.wait(l, [&](){return has_lock;});
404 bool fails = false;
405 for (auto x = 0; x < 100 && !fails; ++x) {
406 fails = queue->m_control_mutex.try_lock();
407 }
408 has_tried = true;
409 cv.notify_one();
410 cv.wait(l, [&](){return done;});
411 // Acknowledge the done
412 done_ack = true;
413 cv.notify_one();
414 BOOST_REQUIRE(!fails);
415 }
416 for (auto& thread: tg) {
417 if (thread.joinable()) thread.join();
418 }
419 }
420}
CCheckQueue< FakeCheckCheckCompletion > Correct_Queue
CCheckQueue< FrozenCleanupCheck > FrozenCleanup_Queue
static const int SCRIPT_CHECK_THREADS
CCheckQueue< UniqueCheck > Unique_Queue
CCheckQueue< FixedCheck > Fixed_Queue
CCheckQueue< FakeCheck > Standard_Queue
CCheckQueue< MemoryCheck > Memory_Queue
BOOST_AUTO_TEST_CASE(test_CheckQueue_Correct_Zero)
Test that 0 checks is correct.
static const unsigned int QUEUE_BATCH_SIZE
RAII-style controller object for a CCheckQueue that guarantees the passed queue is finished before co...
Definition: checkqueue.h:210
std::optional< R > Complete()
Definition: checkqueue.h:222
void Add(std::vector< T > &&vChecks)
Definition: checkqueue.h:229
Queue for verifications that have to be performed.
Definition: checkqueue.h:34
I randrange(I range) noexcept
Generate a random integer in the range [0..range), with range > 0.
Definition: random.h:254
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
std::thread thread
Thread variable should be after other struct members so the thread does not start until the other mem...
Basic testing setup.
Definition: setup_common.h:58
FastRandomContext m_rng
Definition: setup_common.h:62
void Correct_Queue_range(std::vector< size_t > range)
This test case checks that the CCheckQueue works properly with each specified size_t Checks pushed.
static std::atomic< size_t > n_calls
std::optional< int > operator()()
std::optional< int > operator()() const
std::optional< int > m_result
FixedCheck(std::optional< int > result)
std::optional< int > operator()() const
static std::atomic< uint64_t > nFrozen
static std::condition_variable cv
static std::mutex m
std::optional< int > operator()() const
FrozenCleanupCheck & operator=(FrozenCleanupCheck &&other) noexcept
FrozenCleanupCheck(FrozenCleanupCheck &&other) noexcept
FrozenCleanupCheck()=default
static std::atomic< size_t > fake_allocated_memory
std::optional< int > operator()() const
MemoryCheck(bool b_)
MemoryCheck(const MemoryCheck &x)
Identical to BasicTestingSetup but excludes lock contention logging if DEBUG_LOCKCONTENTION is define...
static std::unordered_multiset< size_t > results GUARDED_BY(m)
static Mutex m
std::optional< int > operator()()
UniqueCheck(size_t check_id_in)
#define LOCK(cs)
Definition: sync.h:268
int COUNT
Definition: unit_test.c:28
void UninterruptibleSleep(const std::chrono::microseconds &n)
Definition: time.cpp:30
assert(!tx.IsCoinBase())