/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 | 12.2k | explicit ExpectedException(const std::string& msg) : std::runtime_error(msg) {} |
17 | | }; |
18 | | |
19 | | struct ThrowTask { |
20 | 12.3k | void operator()() const { throw ExpectedException("fail"); } |
21 | | }; |
22 | | |
23 | | struct CounterTask { |
24 | | std::atomic_uint32_t& m_counter; |
25 | 68.8k | explicit CounterTask(std::atomic_uint32_t& counter) : m_counter{counter} {} |
26 | 68.7k | 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 | 81.1k | { |
32 | 81.1k | try { |
33 | 81.1k | future.get(); |
34 | 81.1k | } catch (const ExpectedException&) { |
35 | 12.3k | fail_counter++; |
36 | 12.3k | } 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 | 81.1k | } |
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 | | // Global to verify we always have the same number of threads. |
47 | | size_t g_num_workers = 3; |
48 | | |
49 | | static void StartPoolIfNeeded() |
50 | 134 | { |
51 | 134 | if (g_pool.WorkersCount() == g_num_workers) return; Branch (51:9): [True: 133, False: 1]
|
52 | 1 | g_pool.Start(g_num_workers); |
53 | 1 | } |
54 | | |
55 | | static void setup_threadpool_test() |
56 | 0 | { |
57 | | // Disable logging entirely. It seems to cause memory leaks. |
58 | 0 | LogInstance().DisableLogging(); |
59 | 0 | } |
60 | | |
61 | | FUZZ_TARGET(threadpool, .init = setup_threadpool_test) |
62 | 134 | { |
63 | | // Because LibAFL calls fork() after calling the init setup function, |
64 | | // the child processes end up having one thread active and no workers. |
65 | | // To work around this limitation, start thread pool inside the first runner. |
66 | 134 | StartPoolIfNeeded(); |
67 | | |
68 | 134 | FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); |
69 | | |
70 | 134 | const uint32_t num_tasks = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, 1024); |
71 | 134 | assert(g_pool.WorkersCount() == g_num_workers); Branch (71:5): [True: 134, False: 0]
|
72 | 134 | assert(g_pool.WorkQueueSize() == 0); Branch (72:5): [True: 134, False: 0]
|
73 | | |
74 | | // Counters |
75 | 134 | std::atomic_uint32_t task_counter{0}; |
76 | 134 | uint32_t fail_counter{0}; |
77 | 134 | uint32_t expected_task_counter{0}; |
78 | 134 | uint32_t expected_fail_tasks{0}; |
79 | | |
80 | 134 | std::queue<std::future<void>> futures; |
81 | 81.3k | for (uint32_t i = 0; i < num_tasks; ++i) { Branch (81:26): [True: 81.1k, False: 134]
|
82 | 81.1k | const bool will_throw = fuzzed_data_provider.ConsumeBool(); |
83 | 81.1k | const bool wait_immediately = fuzzed_data_provider.ConsumeBool(); |
84 | | |
85 | 81.1k | std::future<void> fut; |
86 | 81.1k | if (will_throw) { Branch (86:13): [True: 12.3k, False: 68.8k]
|
87 | 12.3k | expected_fail_tasks++; |
88 | 12.3k | fut = *Assert(g_pool.Submit(ThrowTask{})); |
89 | 68.8k | } else { |
90 | 68.8k | expected_task_counter++; |
91 | 68.8k | fut = *Assert(g_pool.Submit(CounterTask{task_counter})); |
92 | 68.8k | } |
93 | | |
94 | | // If caller wants to wait immediately, consume the future here (safe). |
95 | 81.1k | if (wait_immediately) { Branch (95:13): [True: 12.2k, False: 68.9k]
|
96 | | // Waits for this task to complete immediately; prior queued tasks may also complete |
97 | | // as they were queued earlier. |
98 | 12.2k | GetFuture(fut, fail_counter); |
99 | 68.9k | } else { |
100 | | // Store task for a posterior check |
101 | 68.9k | futures.emplace(std::move(fut)); |
102 | 68.9k | } |
103 | 81.1k | } |
104 | | |
105 | | // Drain remaining futures |
106 | 69.0k | while (!futures.empty()) { Branch (106:12): [True: 68.9k, False: 134]
|
107 | 68.9k | auto fut = std::move(futures.front()); |
108 | 68.9k | futures.pop(); |
109 | 68.9k | GetFuture(fut, fail_counter); |
110 | 68.9k | } |
111 | | |
112 | 134 | assert(g_pool.WorkQueueSize() == 0); Branch (112:5): [True: 134, False: 0]
|
113 | 134 | assert(task_counter.load() == expected_task_counter); Branch (113:5): [True: 134, False: 0]
|
114 | 134 | assert(fail_counter == expected_fail_tasks); Branch (114:5): [True: 134, False: 0]
|
115 | 134 | } |