/root/bitcoin/src/test/fuzz/threadpool.cpp
Line | Count | Source |
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 <logging.h> |
6 | | #include <util/threadpool.h> |
7 | | |
8 | | #include <test/fuzz/FuzzedDataProvider.h> |
9 | | #include <test/fuzz/fuzz.h> |
10 | | |
11 | | #include <atomic> |
12 | | #include <future> |
13 | | #include <queue> |
14 | | |
15 | | struct ExpectedException : std::runtime_error { |
16 | 0 | explicit ExpectedException(const std::string& msg) : std::runtime_error(msg) {} |
17 | | }; |
18 | | |
19 | | struct ThrowTask { |
20 | 0 | void operator()() const { throw ExpectedException("fail"); } |
21 | | }; |
22 | | |
23 | | struct CounterTask { |
24 | | std::atomic_uint32_t& m_counter; |
25 | 0 | explicit CounterTask(std::atomic_uint32_t& counter) : m_counter{counter} {} |
26 | 0 | void operator()() const { m_counter.fetch_add(1, std::memory_order_relaxed); } |
27 | | }; |
28 | | |
29 | | // Waits for a future to complete. Increments 'fail_counter' if the expected exception is thrown. |
30 | | static void GetFuture(std::future<void>& future, uint32_t& fail_counter) |
31 | 0 | { |
32 | 0 | try { |
33 | 0 | future.get(); |
34 | 0 | } catch (const ExpectedException&) { |
35 | 0 | fail_counter++; |
36 | 0 | } catch (...) { |
37 | 0 | assert(false && "Unexpected exception type"); Branch (37:9): [Folded - Ignored]
Branch (37:9): [Folded - Ignored]
Branch (37:9): [Folded - Ignored]
|
38 | 0 | } |
39 | 0 | } |
40 | | |
41 | | // Global thread pool for fuzzing. Persisting it across iterations prevents |
42 | | // the excessive thread creation/destruction overhead that can lead to |
43 | | // instability in the fuzzing environment. |
44 | | // This is also how we use it in the app's lifecycle. |
45 | | ThreadPool g_pool{"fuzz"}; |
46 | | Mutex g_pool_mutex; |
47 | | // Global to verify we always have the same number of threads. |
48 | | size_t g_num_workers = 3; |
49 | | |
50 | | static void StartPoolIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(!g_pool_mutex) |
51 | 0 | { |
52 | 0 | LOCK(g_pool_mutex); |
53 | 0 | if (g_pool.WorkersCount() == g_num_workers) return; Branch (53:9): [True: 0, False: 0]
|
54 | 0 | g_pool.Start(g_num_workers); |
55 | 0 | } |
56 | | |
57 | | static void setup_threadpool_test() |
58 | 0 | { |
59 | | // Disable logging entirely. It seems to cause memory leaks. |
60 | 0 | LogInstance().DisableLogging(); |
61 | 0 | } |
62 | | |
63 | | FUZZ_TARGET(threadpool, .init = setup_threadpool_test) EXCLUSIVE_LOCKS_REQUIRED(!g_pool_mutex) |
64 | 0 | { |
65 | | // Because LibAFL calls fork() after calling the init setup function, |
66 | | // the child processes end up having one thread active and no workers. |
67 | | // To work around this limitation, start thread pool inside the first runner. |
68 | 0 | StartPoolIfNeeded(); |
69 | |
|
70 | 0 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
71 | |
|
72 | 0 | const uint32_t num_tasks = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, 1024); |
73 | 0 | assert(g_pool.WorkersCount() == g_num_workers); Branch (73:5): [True: 0, False: 0]
|
74 | 0 | assert(g_pool.WorkQueueSize() == 0); Branch (74:5): [True: 0, False: 0]
|
75 | | |
76 | | // Counters |
77 | 0 | std::atomic_uint32_t task_counter{0}; |
78 | 0 | uint32_t fail_counter{0}; |
79 | 0 | uint32_t expected_task_counter{0}; |
80 | 0 | uint32_t expected_fail_tasks{0}; |
81 | |
|
82 | 0 | std::queue<std::future<void>> futures; |
83 | 0 | for (uint32_t i = 0; i < num_tasks; ++i) { Branch (83:26): [True: 0, False: 0]
|
84 | 0 | const bool will_throw = fuzzed_data_provider.ConsumeBool(); |
85 | 0 | const bool wait_immediately = fuzzed_data_provider.ConsumeBool(); |
86 | |
|
87 | 0 | std::future<void> fut; |
88 | 0 | if (will_throw) { Branch (88:13): [True: 0, False: 0]
|
89 | 0 | expected_fail_tasks++; |
90 | 0 | fut = *Assert(g_pool.Submit(ThrowTask{})); |
91 | 0 | } else { |
92 | 0 | expected_task_counter++; |
93 | 0 | fut = *Assert(g_pool.Submit(CounterTask{task_counter})); |
94 | 0 | } |
95 | | |
96 | | // If caller wants to wait immediately, consume the future here (safe). |
97 | 0 | if (wait_immediately) { Branch (97:13): [True: 0, False: 0]
|
98 | | // Waits for this task to complete immediately; prior queued tasks may also complete |
99 | | // as they were queued earlier. |
100 | 0 | GetFuture(fut, fail_counter); |
101 | 0 | } else { |
102 | | // Store task for a posterior check |
103 | 0 | futures.emplace(std::move(fut)); |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | // Drain remaining futures |
108 | 0 | while (!futures.empty()) { Branch (108:12): [True: 0, False: 0]
|
109 | 0 | auto fut = std::move(futures.front()); |
110 | 0 | futures.pop(); |
111 | 0 | GetFuture(fut, fail_counter); |
112 | 0 | } |
113 | |
|
114 | 0 | assert(g_pool.WorkQueueSize() == 0); Branch (114:5): [True: 0, False: 0]
|
115 | 0 | assert(task_counter.load() == expected_task_counter); Branch (115:5): [True: 0, False: 0]
|
116 | 0 | assert(fail_counter == expected_fail_tasks); Branch (116:5): [True: 0, False: 0]
|
117 | 0 | } |