LCOV - code coverage report
Current view: top level - src/test/fuzz - dbwrapper.cpp (source / functions) Coverage Total Hit
Test: fuzz_coverage.info Lines: 100.0 % 236 236
Test Date: 2026-06-29 07:05:34 Functions: 100.0 % 47 47
Branches: 62.0 % 458 284

             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 <dbwrapper.h>
       6                 :             : #include <compat/byteswap.h>
       7                 :             : #include <random.h>
       8                 :             : #include <sync.h>
       9                 :             : #include <test/fuzz/FuzzedDataProvider.h>
      10                 :             : #include <test/fuzz/fuzz.h>
      11                 :             : #include <test/fuzz/util.h>
      12                 :             : #include <test/util/random.h>
      13                 :             : #include <test/util/setup_common.h>
      14                 :             : #include <util/byte_units.h>
      15                 :             : #include <util/check.h>
      16                 :             : #include <util/threadpool.h>
      17                 :             : 
      18                 :             : #include <leveldb/env.h>
      19                 :             : #include <leveldb/helpers/memenv/memenv.h>
      20                 :             : 
      21                 :             : #include <algorithm>
      22                 :             : #include <cassert>
      23                 :             : #include <cstdint>
      24                 :             : #include <deque>
      25                 :             : #include <functional>
      26                 :             : #include <future>
      27                 :             : #include <latch>
      28                 :             : #include <map>
      29                 :             : #include <memory>
      30                 :             : #include <numeric>
      31                 :             : #include <optional>
      32                 :             : #include <set>
      33                 :             : #include <span>
      34                 :             : #include <string>
      35                 :             : #include <tuple>
      36                 :             : #include <vector>
      37                 :             : 
      38                 :             : namespace {
      39                 :             : 
      40                 :             : /**
      41                 :             :  * A leveldb::Env that wraps a memenv and captures scheduled background
      42                 :             :  * work (compaction) instead of dispatching to a real thread. The fuzz
      43                 :             :  * harness calls RunOne() or DrainWork() at fuzzer-chosen points to
      44                 :             :  * execute it, giving deterministic control over when compaction
      45                 :             :  * interleaves with foreground operations.
      46                 :             :  *
      47                 :             :  * Deadlock prevention: LevelDB's MakeRoomForWrite blocks on a condition
      48                 :             :  * variable when the previous immutable memtable is still awaiting compaction,
      49                 :             :  * or when the L0 file count hits kL0_StopWritesTrigger. Since both conditions
      50                 :             :  * can only be resolved by the (deferred) background work, the harness drains
      51                 :             :  * all pending work before every write to avoid a single-threaded deadlock.
      52                 :             :  * Callers must also DrainWork() before destroying the CDBWrapper, since the
      53                 :             :  * leveldb destructor waits for any pending background work to complete.
      54                 :             :  *
      55                 :             :  * The same reasoning rules out exercising DBOptions::force_compact under
      56                 :             :  * this env, because CompactRange(nullptr, nullptr) blocks waiting for
      57                 :             :  * background work that is queued on the (blocked) foreground thread. The
      58                 :             :  * sibling dbwrapper_threaded target covers that path.
      59                 :             :  */
      60                 :             : class DeterministicEnv final : public leveldb::EnvWrapper
      61                 :             : {
      62                 :             :     using WorkFunction = void (*)(void*);
      63                 :             : 
      64                 :             :     struct Work {
      65                 :             :         WorkFunction function;
      66                 :             :         void* arg;
      67                 :             :     };
      68                 :             : 
      69                 :             :     Mutex m_mutex;
      70                 :             :     std::deque<Work> m_queue GUARDED_BY(m_mutex);
      71                 :             : 
      72                 :             : public:
      73         [ +  - ]:         896 :     explicit DeterministicEnv(leveldb::Env* base) : EnvWrapper(base) {}
      74                 :             : 
      75                 :        9904 :     void Schedule(WorkFunction function, void* arg) override EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
      76                 :             :     {
      77                 :        9904 :         LOCK(m_mutex);
      78   [ +  -  +  - ]:        9904 :         m_queue.push_back({function, arg});
      79                 :        9904 :     }
      80                 :             : 
      81                 :             :     /** Execute one pending background task. The task may schedule a
      82                 :             :      *  successor which is left pending for a later call. */
      83                 :       79862 :     bool RunOne() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
      84                 :             :     {
      85                 :       79862 :         Work work;
      86                 :       79862 :         {
      87                 :       79862 :             LOCK(m_mutex);
      88   [ +  +  +  - ]:       79862 :             if (m_queue.empty()) return false;
      89                 :        9904 :             work = m_queue.front();
      90         [ +  - ]:        9904 :             m_queue.pop_front();
      91                 :       69958 :         }
      92                 :        9904 :         work.function(work.arg);
      93                 :        9904 :         return true;
      94                 :             :     }
      95                 :             : 
      96                 :             :     /** Execute pending background tasks until none remain. */
      97   [ +  -  +  +  :       76439 :     void DrainWork() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { while (RunOne()) {} }
          +  -  +  +  +  
             -  -  +  +  
                      + ]
      98                 :             : };
      99                 :             : 
     100                 :             : constexpr size_t MAX_VALUE_LEN{4096};
     101                 :             : constexpr uint8_t MAX_VALUE_MULTIPLIER{8};
     102                 :             : constexpr size_t WRITE_BATCH_HEADER{12}; // See kHeader in db/write_batch.cc
     103                 :             : 
     104                 :             : /** Mirror of CDBWrapper::OBFUSCATION_KEY, the fixed key under which leveldb
     105                 :             :  *  stores the obfuscation metadata entry when obfuscation is enabled. */
     106                 :             : const std::string OBFUSCATION_KEY{"\000obfuscate_key", 14};
     107                 :             : 
     108                 :             : /** Generate a deterministic value from key and size. The fuzz input picks
     109                 :             :  *  a 16-bit length (up to MAX_VALUE_LEN) and an 8-bit multiplier so that a
     110                 :             :  *  small amount of fuzz input can produce a wide range of value sizes. */
     111                 :     1241266 : std::vector<uint8_t> MakeValue(uint16_t key, uint32_t size)
     112                 :             : {
     113                 :     1241266 :     std::vector<uint8_t> v(size);
     114                 :     1241266 :     std::iota(v.begin(), v.end(), static_cast<uint8_t>(key ^ (key >> 8)));
     115                 :     1241266 :     return v;
     116                 :             : }
     117                 :             : 
     118                 :             : /** Equivalent to leveldb::BytewiseComparator() on 2-byte little-endian
     119                 :             :  *  serialized uint16_t keys, while keeping the oracle keyed by uint16_t. */
     120                 :             : struct LevelDBBytewiseU16Cmp {
     121   [ +  -  -  +  :     4188754 :     bool operator()(uint16_t a, uint16_t b) const { return internal_bswap_16(a) < internal_bswap_16(b); }
          -  -  -  -  +  
          +  +  +  +  -  
          +  -  +  -  -  
          -  -  -  +  +  
          -  -  -  -  -  
          -  -  -  -  -  
          -  -  -  -  +  
          +  +  +  +  +  
                   +  + ]
     122                 :             : };
     123                 :             : 
     124                 :             : /** key → value-size map ordered by LevelDB's bytewise comparator. */
     125                 :             : using Oracle = std::map<uint16_t, uint32_t, LevelDBBytewiseU16Cmp>;
     126                 :             : 
     127                 :             : struct FailUnserialize {
     128                 :             :     template <typename Stream>
     129         [ +  - ]:        2640 :     void Unserialize(Stream&) { throw std::ios_base::failure{"always fail"}; }
     130                 :             : };
     131                 :             : 
     132                 :     1005693 : uint16_t ConsumeKey(FuzzedDataProvider& provider) { return provider.ConsumeIntegral<uint16_t>(); }
     133                 :      748395 : uint32_t ConsumeValueSize(FuzzedDataProvider& provider)
     134                 :             : {
     135                 :      748395 :     const uint16_t len{provider.ConsumeIntegralInRange<uint16_t>(0, MAX_VALUE_LEN)};
     136                 :      748395 :     const uint8_t multiplier{provider.ConsumeIntegralInRange<uint8_t>(1, MAX_VALUE_MULTIPLIER)};
     137                 :      748395 :     return static_cast<uint32_t>(len) * multiplier;
     138                 :             : }
     139                 :             : 
     140                 :             : /** Verify that the DB iterator matches the oracle, handling the obfuscation
     141                 :             :  *  metadata entry (stored under a non-uint16_t key) when obfuscation is on. */
     142                 :       62089 : void VerifyIterator(CDBWrapper& dbw, const Oracle& oracle,
     143                 :             :                     bool obfuscate, std::optional<uint16_t> seek_key = std::nullopt)
     144                 :             : {
     145         [ +  + ]:       62089 :     const std::unique_ptr<CDBIterator> it{dbw.NewIterator()};
     146         [ +  + ]:       62089 :     auto oracle_it{seek_key ? oracle.lower_bound(*seek_key) : oracle.begin()};
     147         [ +  + ]:       62089 :     if (seek_key) {
     148         [ +  - ]:       13180 :         it->Seek(*seek_key);
     149                 :             :     } else {
     150         [ +  - ]:       48909 :         it->SeekToFirst();
     151                 :             :     }
     152   [ +  -  +  -  :      471724 :     for (; it->Valid(); it->Next()) {
                   +  + ]
     153                 :      409635 :         uint16_t db_key;
     154   [ +  -  -  + ]:      409635 :         assert(it->GetKey(db_key));
     155   [ +  +  +  + ]:      409635 :         if (oracle_it != oracle.end() && db_key == oracle_it->first) {
     156                 :      368609 :             std::vector<uint8_t> db_value;
     157   [ +  -  -  + ]:      368609 :             assert(it->GetValue(db_value));
     158   [ +  -  -  + ]:      368609 :             assert(db_value == MakeValue(db_key, oracle_it->second));
     159                 :      368609 :             ++oracle_it;
     160                 :      368609 :         } else {
     161         [ -  + ]:       41026 :             assert(obfuscate);
     162         [ +  - ]:       41026 :             std::string key_str;
     163   [ +  -  -  + ]:       41026 :             assert(it->GetKey(key_str));
     164         [ -  + ]:       41026 :             assert(key_str == OBFUSCATION_KEY);
     165                 :       41026 :         }
     166                 :             :     }
     167         [ -  + ]:       62089 :     assert(oracle_it == oracle.end());
     168                 :       62089 : }
     169                 :             : 
     170                 :             : /** Maximum number of concurrent reader threads in dbwrapper_concurrent_reads. */
     171                 :             : constexpr size_t MAX_READ_WORKERS{8};
     172                 :             : 
     173                 :             : /** Maximum number of queries each worker executes in dbwrapper_concurrent_reads. */
     174                 :             : constexpr size_t MAX_READ_QUERIES_PER_WORKER{128};
     175                 :             : 
     176                 :             : ThreadPool g_read_pool{"dbfuzz"};
     177                 :             : 
     178                 :         357 : void StartReadPoolIfNeeded()
     179                 :             : {
     180         [ +  + ]:         357 :     if (!g_read_pool.WorkersCount()) g_read_pool.Start(MAX_READ_WORKERS);
     181                 :         357 : }
     182                 :             : 
     183                 :             : /** Build randomized DBParams from the fuzz input, shared by all targets. */
     184                 :       48232 : DBParams ConsumeDBParams(FuzzedDataProvider& provider, leveldb::Env* testing_env,
     185                 :             :                          bool obfuscate, DBOptions options = {})
     186                 :             : {
     187                 :       96464 :     return DBParams{
     188                 :             :         .path = "dbwrapper_fuzz",
     189                 :       48232 :         .cache_bytes = provider.ConsumeIntegralInRange<size_t>(64 << 10, 1_MiB),
     190                 :             :         .obfuscate = obfuscate,
     191                 :             :         .options = options,
     192                 :             :         .testing_env = testing_env,
     193                 :       48232 :         .max_file_size = provider.ConsumeBool()
     194         [ +  + ]:       48232 :             ? DBWRAPPER_MAX_FILE_SIZE
     195                 :        5806 :             : provider.ConsumeIntegralInRange<size_t>(1_MiB, 4_MiB),
     196                 :       48232 :     };
     197                 :             : }
     198                 :             : 
     199                 :             : template <typename DrainWorkFn, typename RunOneFn>
     200                 :        1158 : void TestDbWrapper(FuzzedDataProvider& provider,
     201                 :             :                       leveldb::Env* testing_env,
     202                 :             :                       DrainWorkFn drain_work,
     203                 :             :                       RunOneFn run_one,
     204                 :             :                       bool allow_force_compact)
     205                 :             : {
     206                 :        1158 :     SeedRandomStateForTest(SeedRand::ZEROS);
     207                 :             : 
     208                 :        1158 :     const bool obfuscate{provider.ConsumeBool()};
     209                 :             : 
     210                 :       49033 :     const auto make_db{[&](DBOptions options = {}) {
     211   [ +  -  +  - ]:       95750 :         return std::make_unique<CDBWrapper>(ConsumeDBParams(provider, testing_env, obfuscate, options));
     212                 :             :     }};
     213                 :        1158 :     std::unique_ptr<CDBWrapper> dbw{make_db()};
     214                 :             : 
     215                 :             :     // Oracle: key → value size. Content is reconstructed via MakeValue().
     216                 :        1158 :     Oracle oracle;
     217                 :             : 
     218   [ +  +  +  + ]:      164610 :     LIMITED_WHILE(provider.ConsumeBool(), 1'000)
     219                 :             :     {
     220         [ +  - ]:      163452 :         CallOneOf(
     221                 :             :             provider,
     222                 :             :             // --- Mutations ---
     223                 :       24180 :             [&] {
     224                 :       12090 :                 const auto key{ConsumeKey(provider)};
     225                 :       12090 :                 const auto size{ConsumeValueSize(provider)};
     226                 :        6086 :                 drain_work();
     227   [ +  -  +  - ]:       12090 :                 dbw->Write(key, MakeValue(key, size), /*fSync=*/provider.ConsumeBool());
     228                 :       12090 :                 oracle[key] = size;
     229                 :             :             },
     230                 :       89886 :             [&] {
     231                 :       44943 :                 const auto key{ConsumeKey(provider)};
     232                 :       23943 :                 drain_work();
     233                 :       44943 :                 dbw->Erase(key, /*fSync=*/provider.ConsumeBool());
     234                 :       44943 :                 oracle.erase(key);
     235                 :             :             },
     236                 :        7093 :             [&] {
     237   [ +  -  +  - ]:        7093 :                 CDBBatch batch{*dbw};
     238   [ +  -  +  - ]:        7093 :                 std::map<uint16_t, uint32_t> batch_writes;
     239                 :        7093 :                 std::set<uint16_t> batch_erases;
     240                 :       19337 :                 const auto fill{[&] {
     241   [ +  +  +  +  :      132493 :                     LIMITED_WHILE(provider.ConsumeBool(), 20)
             +  +  +  + ]
     242                 :             :                     {
     243                 :      120249 :                         const auto key{ConsumeKey(provider)};
     244   [ +  +  +  + ]:      120249 :                         if (provider.ConsumeBool()) {
     245                 :       51526 :                             const auto size{ConsumeValueSize(provider)};
     246   [ +  -  +  - ]:       51526 :                             batch.Write(key, MakeValue(key, size));
     247                 :       51526 :                             batch_writes[key] = size;
     248                 :       51526 :                             batch_erases.erase(key);
     249                 :             :                         } else {
     250                 :       68723 :                             batch.Erase(key);
     251                 :       68723 :                             batch_erases.insert(key);
     252                 :       68723 :                             batch_writes.erase(key);
     253                 :             :                         }
     254                 :             :                     }
     255                 :             :                 }};
     256   [ +  -  +  - ]:        7093 :                 fill();
     257   [ +  +  +  + ]:        7093 :                 if (provider.ConsumeBool()) {
     258   [ +  -  -  +  :        5151 :                     assert(batch.ApproximateSize() >= WRITE_BATCH_HEADER);
             +  -  -  + ]
     259   [ +  -  +  - ]:        5151 :                     batch.Clear();
     260   [ +  -  -  +  :        5151 :                     assert(batch.ApproximateSize() == WRITE_BATCH_HEADER);
             +  -  -  + ]
     261                 :        5151 :                     batch_writes.clear();
     262                 :        5151 :                     batch_erases.clear();
     263   [ +  -  +  - ]:        5151 :                     fill();
     264                 :             :                 }
     265         [ +  - ]:        4521 :                 drain_work();
     266   [ +  -  +  - ]:        7093 :                 dbw->WriteBatch(batch, /*fSync=*/provider.ConsumeBool());
     267   [ +  -  +  +  :       31187 :                 for (const auto& [k, v] : batch_writes) oracle[k] = v;
             +  -  +  + ]
     268   [ +  +  +  + ]:       23389 :                 for (const auto& k : batch_erases) oracle.erase(k);
     269                 :        7093 :             },
     270                 :       93434 :             [&] {
     271                 :       29006 :                 drain_work();
     272   [ +  -  +  - ]:       46717 :                 dbw.reset();
     273                 :       46717 :                 DBOptions options{};
     274   [ +  -  +  +  :       46717 :                 if (allow_force_compact && provider.ConsumeBool()) {
             -  +  -  - ]
     275                 :       14234 :                     options.force_compact = true;
     276                 :             :                 }
     277                 :       46717 :                 dbw = make_db(options);
     278                 :       46717 :                 VerifyIterator(*dbw, oracle, obfuscate);
     279                 :             :             },
     280                 :             :             // --- Reads ---
     281                 :        9794 :             [&] {
     282                 :        4897 :                 const auto key{ConsumeKey(provider)};
     283                 :        4897 :                 std::vector<uint8_t> value;
     284   [ +  -  +  - ]:        4897 :                 const bool found{dbw->Read(key, value)};
     285   [ +  +  +  + ]:        4897 :                 if (const auto it{oracle.find(key)}; it != oracle.end()) {
     286   [ +  -  +  -  :        4820 :                     assert(found && value == MakeValue(key, it->second));
          -  +  +  -  +  
                -  -  + ]
     287                 :             :                 } else {
     288   [ -  +  -  + ]:        2487 :                     assert(!found);
     289                 :             :                 }
     290                 :        4897 :             },
     291                 :       37544 :             [&] {
     292                 :       18772 :                 const auto key{ConsumeKey(provider)};
     293   [ -  +  -  + ]:       18772 :                 assert(dbw->Exists(key) == oracle.contains(key));
     294                 :             :             },
     295                 :        3990 :             [&] {
     296                 :        1995 :                 uint16_t key{};
     297   [ +  +  +  +  :        1995 :                 if (!oracle.empty() && provider.ConsumeBool()) {
             +  +  +  + ]
     298                 :        1304 :                     auto it{oracle.begin()};
     299                 :        1304 :                     std::advance(it, provider.ConsumeIntegralInRange<size_t>(0, oracle.size() - 1));
     300                 :        1304 :                     key = it->first;
     301                 :             :                 } else {
     302                 :         691 :                     key = ConsumeKey(provider);
     303                 :             :                 }
     304                 :             :                 FailUnserialize wrong_type;
     305   [ -  +  -  + ]:        1995 :                 assert(!dbw->Read(key, wrong_type));
     306                 :             :             },
     307                 :       28428 :             [&] {
     308                 :       28428 :                 const auto seek_key{provider.ConsumeBool()
     309   [ +  +  +  + ]:       14214 :                                         ? std::optional<uint16_t>{ConsumeKey(provider)}
     310                 :             :                                         : std::nullopt};
     311                 :       14214 :                 VerifyIterator(*dbw, oracle, obfuscate, seek_key);
     312                 :             :             },
     313                 :             :             // --- Stats ---
     314                 :        3930 :             [&] {
     315   [ +  +  +  +  :        3218 :                 assert(dbw->IsEmpty() == (oracle.empty() && !obfuscate));
          -  +  +  +  +  
                +  -  + ]
     316                 :             :             },
     317                 :        8376 :             [&] {
     318                 :        4188 :                 const auto [k1, k2]{std::minmax({ConsumeKey(provider), ConsumeKey(provider)}, LevelDBBytewiseU16Cmp{})};
     319                 :        4188 :                 const size_t estimate_size{dbw->EstimateSize(k1, k2)};
     320   [ +  +  -  +  :        4188 :                 if (k1 == k2) assert(estimate_size == 0);
             +  +  -  + ]
     321                 :             :             },
     322                 :        1512 :             [&] {
     323                 :        1512 :                 (void)dbw->DynamicMemoryUsage();
     324                 :             :             },
     325                 :             :             // --- Compaction control (no-op when run_one is no-op) ---
     326                 :        3325 :             [&] {
     327                 :        3325 :                 run_one();
     328                 :             :             });
     329                 :             :     }
     330                 :             : 
     331         [ +  - ]:        1158 :     VerifyIterator(*dbw, oracle, obfuscate);
     332         [ +  - ]:        1158 :     drain_work();
     333                 :        1158 : }
     334                 :             : 
     335                 :             : } // namespace
     336                 :             : 
     337   [ +  -  +  -  :        1008 : FUZZ_TARGET(dbwrapper, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
                   +  - ]
     338                 :             : {
     339                 :         539 :     FuzzedDataProvider provider{buffer.data(), buffer.size()};
     340                 :             : 
     341         [ +  - ]:         539 :     const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
     342         [ +  - ]:         539 :     DeterministicEnv det_env{memenv.get()};
     343         [ +  - ]:         539 :     TestDbWrapper(
     344                 :             :         provider, &det_env,
     345                 :       64095 :         [&] { det_env.DrainWork(); },
     346                 :        3325 :         [&] { return det_env.RunOne(); },
     347                 :             :         /*allow_force_compact=*/false);
     348                 :         539 : }
     349                 :             : 
     350   [ +  -  +  -  :        1087 : FUZZ_TARGET(dbwrapper_threaded, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
                   +  - ]
     351                 :             : {
     352                 :         619 :     FuzzedDataProvider provider{buffer.data(), buffer.size()};
     353                 :             : 
     354         [ +  - ]:         619 :     const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
     355         [ +  - ]:         619 :     TestDbWrapper(
     356                 :             :         provider, memenv.get(),
     357                 :             :         /*drain_work=*/[] {},
     358                 :             :         /*run_one=*/[] { return false; },
     359                 :             :         /*allow_force_compact=*/true);
     360                 :         619 : }
     361                 :             : 
     362   [ +  -  +  -  :         825 : FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
                   +  - ]
     363                 :             : {
     364                 :         357 :     StartReadPoolIfNeeded();
     365                 :         357 :     SeedRandomStateForTest(SeedRand::ZEROS);
     366                 :             : 
     367                 :         357 :     FuzzedDataProvider provider{buffer.data(), buffer.size()};
     368                 :             : 
     369         [ +  - ]:         357 :     const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
     370         [ +  - ]:         357 :     DeterministicEnv det_env{memenv.get()};
     371                 :             : 
     372   [ +  -  +  - ]:         357 :     CDBWrapper db{ConsumeDBParams(provider, &det_env, /*obfuscate=*/provider.ConsumeBool())};
     373                 :             : 
     374                 :             :     // Seed the DB. Drain work after small batches so we don't deadlock on a
     375                 :             :     // scheduled compaction.
     376                 :         357 :     const size_t num_entries{provider.ConsumeIntegralInRange<size_t>(100, 5'000)};
     377                 :         357 :     std::vector<uint16_t> keys;
     378         [ +  - ]:         357 :     keys.reserve(num_entries);
     379                 :         357 :     Oracle oracle;
     380                 :         357 :     constexpr size_t SEED_BATCH_SIZE{400};
     381         [ +  + ]:        2301 :     for (size_t start{0}; start < num_entries; start += SEED_BATCH_SIZE) {
     382         [ +  - ]:        1944 :         CDBBatch batch{db};
     383         [ +  + ]:        1944 :         const size_t end{std::min(start + SEED_BATCH_SIZE, num_entries)};
     384         [ +  + ]:      686723 :         for (size_t i{start}; i < end; ++i) {
     385                 :      684779 :             const auto k{ConsumeKey(provider)};
     386                 :      684779 :             const auto size{ConsumeValueSize(provider)};
     387   [ +  -  +  - ]:      684779 :             batch.Write(k, MakeValue(k, size));
     388         [ +  - ]:      684779 :             keys.push_back(k);
     389         [ +  - ]:      684779 :             oracle[k] = size;
     390                 :             :         }
     391                 :             :         det_env.DrainWork();
     392         [ +  - ]:        1944 :         db.WriteBatch(batch, /*fSync=*/true);
     393                 :        1944 :     }
     394                 :             : 
     395   [ +  +  +  -  :         455 :     while (provider.ConsumeBool() && det_env.RunOne()) {}
             +  -  -  + ]
     396                 :             : 
     397                 :             :     // Build query list from seeded and random keys.
     398                 :         357 :     const size_t num_queries{provider.ConsumeIntegralInRange<size_t>(1, 2'000)};
     399                 :         357 :     enum class ReadOp { Read, Exists, IteratorSeek };
     400                 :         357 :     std::vector<std::tuple<ReadOp, uint16_t>> queries;
     401         [ +  - ]:         357 :     queries.reserve(num_queries);
     402         [ +  + ]:       99987 :     for (size_t i{0}; i < num_queries; ++i) {
     403                 :       99630 :         const auto op{provider.PickValueInArray({ReadOp::Read, ReadOp::Exists, ReadOp::IteratorSeek})};
     404                 :       99630 :         const uint16_t key{provider.ConsumeBool()
     405         [ +  + ]:      101544 :                                ? keys[provider.ConsumeIntegralInRange<size_t>(0, keys.size() - 1)]
     406                 :       97716 :                                : ConsumeKey(provider)};
     407         [ +  - ]:       99630 :         queries.emplace_back(op, key);
     408                 :             :     }
     409                 :             : 
     410                 :             : 
     411                 :             :     // Workers + main thread synchronize on the latch so all reads start together.
     412                 :         357 :     std::latch start_latch{static_cast<ptrdiff_t>(MAX_READ_WORKERS + 1)};
     413         [ +  - ]:         357 :     std::vector<std::function<void()>> tasks(MAX_READ_WORKERS);
     414                 :         357 :     FastRandomContext rng{ConsumeUInt256(provider)};
     415         [ +  - ]:        3213 :     std::ranges::generate(tasks, [&] {
     416                 :        2856 :         return [&, seed = rng.rand256()] {
     417                 :        2856 :             FastRandomContext thread_rng{seed};
     418   [ -  +  +  - ]:        2856 :             std::vector<size_t> order(queries.size());
     419                 :        2856 :             std::iota(order.begin(), order.end(), size_t{0});
     420                 :        2856 :             std::ranges::shuffle(order, thread_rng);
     421   [ -  +  +  + ]:        2856 :             const size_t queries_to_run{std::min(queries.size(), MAX_READ_QUERIES_PER_WORKER)};
     422                 :        2856 :             std::vector<uint8_t> v;
     423         [ +  + ]:        2856 :             std::string key_str;
     424         [ +  + ]:        2856 :             start_latch.arrive_and_wait();
     425   [ +  -  -  + ]:        2856 :             const std::unique_ptr<CDBIterator> it{db.NewIterator()};
     426                 :             :             // Every read must agree with the oracle, the source of truth.
     427   [ -  +  +  + ]:      141640 :             for (const auto i : std::span{order}.first(queries_to_run)) {
     428   [ +  +  +  - ]:      138784 :                 const auto& [op, key] = queries[i];
     429   [ +  +  +  - ]:      138784 :                 switch (op) {
     430                 :      126396 :                 case ReadOp::Read:
     431         [ +  + ]:      126396 :                     if (const auto oit{oracle.find(key)}; oit != oracle.end()) {
     432   [ +  -  +  -  :      223176 :                         assert(db.Read(key, v) && v == MakeValue(key, oit->second));
             +  -  -  + ]
     433                 :             :                     } else {
     434   [ +  -  -  + ]:       14808 :                         assert(!db.Read(key, v));
     435                 :             :                     }
     436                 :             :                     break;
     437                 :        1881 :                 case ReadOp::Exists:
     438   [ +  -  -  + ]:        1881 :                     assert(db.Exists(key) == oracle.contains(key));
     439                 :             :                     break;
     440                 :       10507 :                 case ReadOp::IteratorSeek:
     441         [ +  - ]:       10507 :                     it->Seek(key);
     442                 :             :                     // Skip the obfuscation metadata entry (a non-uint16_t key) if we land
     443                 :             :                     // on it, so the result matches the oracle, which only tracks user keys.
     444   [ +  -  +  +  :       10507 :                     if (it->Valid() && it->GetKey(key_str) && key_str == OBFUSCATION_KEY) it->Next();
          +  -  +  +  +  
                +  +  - ]
     445         [ +  + ]:       10507 :                     if (const auto oit{oracle.lower_bound(key)}; oit != oracle.end()) {
     446   [ +  -  -  + ]:       10264 :                         assert(it->Valid());
     447                 :       10264 :                         uint16_t actual_key;
     448   [ +  -  +  -  :       10264 :                         assert(it->GetKey(actual_key) && actual_key == oit->first);
                   -  + ]
     449   [ +  -  +  -  :       20528 :                         assert(it->GetValue(v) && v == MakeValue(actual_key, oit->second));
             +  -  -  + ]
     450                 :             :                     } else {
     451   [ +  -  -  + ]:         243 :                         assert(!it->Valid());
     452                 :             :                     }
     453                 :             :                     break;
     454                 :             :                 }
     455                 :             :             }
     456                 :        5712 :         };
     457                 :             :     });
     458         [ -  + ]:         357 :     auto futures{*Assert(g_read_pool.Submit(std::move(tasks)))};
     459                 :             : 
     460                 :             :     // Release the workers and immediately run the queued compaction on this
     461                 :             :     // thread, so compaction races against the concurrent reads.
     462         [ +  + ]:         357 :     start_latch.arrive_and_wait();
     463                 :         357 :     det_env.DrainWork();
     464                 :             : 
     465   [ +  -  +  + ]:        3213 :     for (auto& fut : futures) fut.get();
     466                 :             :     det_env.DrainWork();
     467                 :         357 : }
        

Generated by: LCOV version 2.0-1