Branch data Line data Source code
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 [ + - ]: 5827 : explicit ExpectedException(const std::string& msg) : std::runtime_error(msg) {}
17 : : };
18 : :
19 : : struct ThrowTask {
20 [ + - ]: 11654 : void operator()() const { throw ExpectedException("fail"); }
21 : : };
22 : :
23 : : struct CounterTask {
24 : : std::atomic_uint32_t& m_counter;
25 : 35547 : explicit CounterTask(std::atomic_uint32_t& counter) : m_counter{counter} {}
26 : 35547 : 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 : 41374 : static void GetFuture(std::future<void>& future, uint32_t& fail_counter)
31 : : {
32 : 41374 : try {
33 [ + + ]: 41374 : future.get();
34 [ + - ]: 5827 : } catch (const ExpectedException&) {
35 : 5827 : fail_counter++;
36 : 5827 : } catch (...) {
37 : 0 : assert(false && "Unexpected exception type");
38 : : }
39 : 41374 : }
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 : 75 : static void StartPoolIfNeeded()
50 : : {
51 [ + + ]: 75 : if (g_pool.WorkersCount() == g_num_workers) return;
52 : 1 : g_pool.Start(g_num_workers);
53 : : }
54 : :
55 : 1 : static void setup_threadpool_test()
56 : : {
57 : : // Disable logging entirely. It seems to cause memory leaks.
58 : 1 : LogInstance().DisableLogging();
59 : 1 : }
60 : :
61 [ + - ]: 541 : FUZZ_TARGET(threadpool, .init = setup_threadpool_test)
62 : : {
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 : 75 : StartPoolIfNeeded();
67 : :
68 : 75 : FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
69 : :
70 : 75 : const uint32_t num_tasks = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, 1024);
71 [ - + ]: 75 : assert(g_pool.WorkersCount() == g_num_workers);
72 [ - + ]: 75 : assert(g_pool.WorkQueueSize() == 0);
73 : :
74 : : // Counters
75 : 75 : std::atomic_uint32_t task_counter{0};
76 : 75 : uint32_t fail_counter{0};
77 : 75 : uint32_t expected_task_counter{0};
78 : 75 : uint32_t expected_fail_tasks{0};
79 : :
80 : 75 : std::queue<std::future<void>> futures;
81 [ + + ]: 41449 : for (uint32_t i = 0; i < num_tasks; ++i) {
82 : 41374 : const bool will_throw = fuzzed_data_provider.ConsumeBool();
83 : 41374 : const bool wait_immediately = fuzzed_data_provider.ConsumeBool();
84 : :
85 : 41374 : std::future<void> fut;
86 [ + + ]: 41374 : if (will_throw) {
87 : 5827 : expected_fail_tasks++;
88 [ - + ]: 5827 : fut = *Assert(g_pool.Submit(ThrowTask{}));
89 : : } else {
90 : 35547 : expected_task_counter++;
91 [ - + ]: 35547 : fut = *Assert(g_pool.Submit(CounterTask{task_counter}));
92 : : }
93 : :
94 : : // If caller wants to wait immediately, consume the future here (safe).
95 [ + + ]: 41374 : if (wait_immediately) {
96 : : // Waits for this task to complete immediately; prior queued tasks may also complete
97 : : // as they were queued earlier.
98 : 5927 : GetFuture(fut, fail_counter);
99 : : } else {
100 : : // Store task for a posterior check
101 [ + - - + ]: 41374 : futures.emplace(std::move(fut));
102 : : }
103 : 41374 : }
104 : :
105 : : // Drain remaining futures
106 [ + + ]: 35522 : while (!futures.empty()) {
107 : 35447 : auto fut = std::move(futures.front());
108 : 35447 : futures.pop();
109 [ - + ]: 35447 : GetFuture(fut, fail_counter);
110 : 35447 : }
111 : :
112 [ + - - + ]: 75 : assert(g_pool.WorkQueueSize() == 0);
113 [ - + ]: 75 : assert(task_counter.load() == expected_task_counter);
114 [ - + ]: 75 : assert(fail_counter == expected_fail_tasks);
115 : 75 : }
|