From a4c3b003f8795babe603d5a4fabc474aacb8380a Mon Sep 17 00:00:00 2001 From: marcofleon Date: Fri, 12 Jun 2026 21:02:04 +0100 Subject: [PATCH 1/2] fuzz: Speed up dbwrapper_concurrent_reads harness Limit how many read queries each worker executes. This significantly speeds up the test, as each worker runs >90% fewer (2000 to 128) expensive LevelDB operations (like `IteratorSeek`) but still ends up hitting the intended target code. Revert the `num_entries` max from 3000 back to 5000, as that didn't have much effect on input speed and restores a bit of lost coverage. --- src/test/fuzz/dbwrapper.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/test/fuzz/dbwrapper.cpp b/src/test/fuzz/dbwrapper.cpp index 1b5cd7b025d..8f1d84e4b8a 100644 --- a/src/test/fuzz/dbwrapper.cpp +++ b/src/test/fuzz/dbwrapper.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,9 @@ void VerifyIterator(CDBWrapper& dbw, const Oracle& oracle, /** Maximum number of concurrent reader threads in dbwrapper_concurrent_reads. */ constexpr size_t MAX_READ_WORKERS{8}; +/** Maximum number of queries each worker executes in dbwrapper_concurrent_reads. */ +constexpr size_t MAX_READ_QUERIES_PER_WORKER{128}; + ThreadPool g_read_pool{"dbfuzz"}; Mutex g_read_pool_mutex; @@ -371,7 +375,7 @@ FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLog // Seed the DB. Drain work after small batches so we don't deadlock on a // scheduled compaction. - const size_t num_entries{provider.ConsumeIntegralInRange(100, 3'000)}; + const size_t num_entries{provider.ConsumeIntegralInRange(100, 5'000)}; std::vector keys; keys.reserve(num_entries); Oracle oracle; @@ -416,12 +420,13 @@ FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLog std::vector order(queries.size()); std::iota(order.begin(), order.end(), size_t{0}); std::ranges::shuffle(order, thread_rng); + const size_t queries_to_run{std::min(queries.size(), MAX_READ_QUERIES_PER_WORKER)}; std::vector v; std::string key_str; start_latch.arrive_and_wait(); const std::unique_ptr it{db.NewIterator()}; // Every read must agree with the oracle, the source of truth. - for (const auto i : order) { + for (const auto i : std::span{order}.first(queries_to_run)) { const auto& [op, key] = queries[i]; switch (op) { case ReadOp::Read: From 48df0939e7250a2de8a0c336bc3ddbcbe0578812 Mon Sep 17 00:00:00 2001 From: marcofleon Date: Wed, 17 Jun 2026 13:53:50 +0100 Subject: [PATCH 2/2] fuzz: Remove unnecessary thread pool mutexes Remove the `Mutex` from the `threadpool` and `dbwrapper_concurrent_reads` pool startup helpers. Fuzz targets are entered sequentially within a process and parallel fuzzing uses separate processes/forks, which each have their own copy of the global thread pool. Therefore, a mutex to prevent two in-process callers from racing to start the pool isn't needed. --- src/test/fuzz/dbwrapper.cpp | 6 ++---- src/test/fuzz/threadpool.cpp | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/test/fuzz/dbwrapper.cpp b/src/test/fuzz/dbwrapper.cpp index 8f1d84e4b8a..265f0ca3a09 100644 --- a/src/test/fuzz/dbwrapper.cpp +++ b/src/test/fuzz/dbwrapper.cpp @@ -174,11 +174,9 @@ constexpr size_t MAX_READ_WORKERS{8}; constexpr size_t MAX_READ_QUERIES_PER_WORKER{128}; ThreadPool g_read_pool{"dbfuzz"}; -Mutex g_read_pool_mutex; -void StartReadPoolIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(!g_read_pool_mutex) +void StartReadPoolIfNeeded() { - LOCK(g_read_pool_mutex); if (!g_read_pool.WorkersCount()) g_read_pool.Start(MAX_READ_WORKERS); } @@ -361,7 +359,7 @@ FUZZ_TARGET(dbwrapper_threaded, .init = [] { static auto setup{MakeNoLogFileCont /*allow_force_compact=*/true); } -FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLogFileContext<>()}; }) EXCLUSIVE_LOCKS_REQUIRED(!g_read_pool_mutex) +FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLogFileContext<>()}; }) { StartReadPoolIfNeeded(); SeedRandomStateForTest(SeedRand::ZEROS); diff --git a/src/test/fuzz/threadpool.cpp b/src/test/fuzz/threadpool.cpp index a5b01db1383..48ec3ec883a 100644 --- a/src/test/fuzz/threadpool.cpp +++ b/src/test/fuzz/threadpool.cpp @@ -43,13 +43,11 @@ static void GetFuture(std::future& future, uint32_t& fail_counter) // instability in the fuzzing environment. // This is also how we use it in the app's lifecycle. ThreadPool g_pool{"fuzz"}; -Mutex g_pool_mutex; // Global to verify we always have the same number of threads. size_t g_num_workers = 3; -static void StartPoolIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(!g_pool_mutex) +static void StartPoolIfNeeded() { - LOCK(g_pool_mutex); if (g_pool.WorkersCount() == g_num_workers) return; g_pool.Start(g_num_workers); } @@ -60,7 +58,7 @@ static void setup_threadpool_test() LogInstance().DisableLogging(); } -FUZZ_TARGET(threadpool, .init = setup_threadpool_test) EXCLUSIVE_LOCKS_REQUIRED(!g_pool_mutex) +FUZZ_TARGET(threadpool, .init = setup_threadpool_test) { // Because LibAFL calls fork() after calling the init setup function, // the child processes end up having one thread active and no workers.