coins: introduce thread pool in CoinsViewOverlay

Introduce a ThreadPool shared pointer to CoinsViewOverlay. A pool managed
externally can be passed in the constructor.

A global thread pool is used in fuzz harnesses since iterations can happen
faster than the OS can create and tear down thread pools.
This can cause a memory leak when fuzzing.

Co-authored-by: l0rinc <pap.lorinc@gmail.com>
This commit is contained in:
Andrew Toth
2026-03-07 19:29:20 -05:00
parent 5bf1c32008
commit f82043af50
10 changed files with 75 additions and 15 deletions

View File

@@ -7,6 +7,7 @@
#include <consensus/tx_check.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <kernel/chainstatemanager_opts.h>
#include <kernel/cs_main.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
@@ -17,6 +18,7 @@
#include <test/util/setup_common.h>
#include <txdb.h>
#include <util/hasher.h>
#include <util/threadpool.h>
#include <cassert>
#include <algorithm>
@@ -84,6 +86,12 @@ public:
using CCoinsViewCache::CCoinsViewCache;
};
// Reuse a single global thread pool across fuzz iterations. Creating and destroying a pool every
// iteration leaks memory, since iterations can run faster than the OS can tear down the threads.
std::shared_ptr<ThreadPool> g_thread_pool{std::make_shared<ThreadPool>("view_fuzz")};
Mutex g_thread_pool_mutex;
} // namespace
void initialize_coins_view()
@@ -376,10 +384,10 @@ FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
// This allows us to exercise all methods on a CoinsViewOverlay, while also
// ensuring that nothing can mutate the underlying cache until Flush or Sync is
// called.
FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view)
FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view) EXCLUSIVE_LOCKS_REQUIRED(!g_thread_pool_mutex)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
MutationGuardCoinsViewCache backend_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
CoinsViewOverlay coins_view_cache{&backend_cache, /*deterministic=*/true};
CoinsViewOverlay coins_view_cache{&backend_cache, g_thread_pool, /*deterministic=*/true};
TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache);
}

View File

@@ -4,10 +4,13 @@
#include <coins.h>
#include <crypto/sha256.h>
#include <kernel/chainstatemanager_opts.h>
#include <primitives/transaction.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/fuzz/util.h>
#include <test/util/setup_common.h>
#include <util/threadpool.h>
#include <cassert>
#include <cstdint>
@@ -182,10 +185,22 @@ public:
}
};
// Reuse a single global thread pool across fuzz iterations. Creating and destroying a pool every
// iteration leaks memory, since iterations can run faster than the OS can tear down the threads.
std::shared_ptr<ThreadPool> g_thread_pool{std::make_shared<ThreadPool>("cache_fuzz")};
Mutex g_thread_pool_mutex;
void StartPoolIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(!g_thread_pool_mutex)
{
LOCK(g_thread_pool_mutex);
if (!g_thread_pool->WorkersCount()) g_thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
}
} // namespace
FUZZ_TARGET(coinscache_sim)
FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<>()}; }) EXCLUSIVE_LOCKS_REQUIRED(!g_thread_pool_mutex)
{
StartPoolIfNeeded();
/** Precomputed COutPoint and CCoins values. */
static const PrecomputedData data;
@@ -372,7 +387,7 @@ FUZZ_TARGET(coinscache_sim)
if (provider.ConsumeBool()) {
caches.emplace_back(new CCoinsViewCache(&*caches.back(), /*deterministic=*/true));
} else {
caches.emplace_back(new CoinsViewOverlay(&*caches.back(), /*deterministic=*/true));
caches.emplace_back(new CoinsViewOverlay(&*caches.back(), g_thread_pool, /*deterministic=*/true));
}
// Apply to simulation data.
sim_caches[caches.size()].Wipe();