Merge bitcoin/bitcoin#35521: fuzz: Speed up dbwrapper_concurrent_reads harness

48df0939e7 fuzz: Remove unnecessary thread pool mutexes (marcofleon)
a4c3b003f8 fuzz: Speed up dbwrapper_concurrent_reads harness (marcofleon)

Pull request description:

  Limiting how many read queries each worker executes significantly speeds up this test, especially when running with sanitizers. This still builds the full query list, and then takes the first 128/2000 after each worker shuffles it. I think that keeps some more operation diversity vs just lowering the query max directly. It also allowed me to reuse and test against a corpus I already had. Let me know if I'm wrong, but I don't think this test needs every worker to execute an identical query list to be effective.

  This PR also reverts 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.

  Lastly, as https://github.com/bitcoin/bitcoin/pull/35455#discussion_r3394357385 points out, remove the unnecessary `Mutex` from `StartReadPoolIfNeeded()`. Fuzz targets are entered sequentially within a process and parallel fuzzing uses separate processes/forks, so a mutex to prevent two in-process callers from racing to start the pool isn't needed.

ACKs for top commit:
  sedited:
    ACK 48df0939e7
  brunoerg:
    ACK 48df0939e7

Tree-SHA512: 24c35e13fa26790b36e5190283b78dd8511165d576841cb701f541a36c5b7c0a73f9bc265e71598a879fa1df9a9438e7841736f72b967214196fa6fe68569eff
This commit is contained in:
merge-script
2026-06-22 14:37:01 +01:00
2 changed files with 12 additions and 11 deletions

View File

@@ -30,6 +30,7 @@
#include <numeric>
#include <optional>
#include <set>
#include <span>
#include <string>
#include <tuple>
#include <vector>
@@ -169,12 +170,13 @@ void VerifyIterator(CDBWrapper& dbw, const Oracle& oracle,
/** Maximum number of concurrent reader threads in dbwrapper_concurrent_reads. */
constexpr size_t MAX_READ_WORKERS{8};
ThreadPool g_read_pool{"dbfuzz"};
Mutex g_read_pool_mutex;
/** Maximum number of queries each worker executes in dbwrapper_concurrent_reads. */
constexpr size_t MAX_READ_QUERIES_PER_WORKER{128};
void StartReadPoolIfNeeded() EXCLUSIVE_LOCKS_REQUIRED(!g_read_pool_mutex)
ThreadPool g_read_pool{"dbfuzz"};
void StartReadPoolIfNeeded()
{
LOCK(g_read_pool_mutex);
if (!g_read_pool.WorkersCount()) g_read_pool.Start(MAX_READ_WORKERS);
}
@@ -357,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);
@@ -371,7 +373,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<size_t>(100, 3'000)};
const size_t num_entries{provider.ConsumeIntegralInRange<size_t>(100, 5'000)};
std::vector<uint16_t> keys;
keys.reserve(num_entries);
Oracle oracle;
@@ -416,12 +418,13 @@ FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLog
std::vector<size_t> 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<uint8_t> v;
std::string key_str;
start_latch.arrive_and_wait();
const std::unique_ptr<CDBIterator> 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:

View File

@@ -43,13 +43,11 @@ static void GetFuture(std::future<void>& 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.