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 % 237 237
Test Date: 2026-07-21 06:28:42 Functions: 100.0 % 47 47
Branches: 61.8 % 458 283

             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                 :         513 :     void Schedule(WorkFunction function, void* arg) override EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
      76                 :             :     {
      77                 :         513 :         LOCK(m_mutex);
      78   [ +  -  +  - ]:         513 :         m_queue.push_back({function, arg});
      79                 :         513 :     }
      80                 :             : 
      81                 :             :     /** Execute one pending background task. The task may schedule a
      82                 :             :      *  successor which is left pending for a later call. */
      83                 :       11116 :     bool RunOne() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
      84                 :             :     {
      85                 :       11116 :         Work work;
      86                 :       11116 :         {
      87                 :       11116 :             LOCK(m_mutex);
      88   [ +  +  +  - ]:       11116 :             if (m_queue.empty()) return false;
      89                 :         513 :             work = m_queue.front();
      90         [ +  - ]:         513 :             m_queue.pop_front();
      91                 :       10603 :         }
      92                 :         513 :         work.function(work.arg);
      93                 :         513 :         return true;
      94                 :             :     }
      95                 :             : 
      96                 :             :     /** Execute pending background tasks until none remain. */
      97   [ +  -  +  +  :       10829 :     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                 :      841598 : std::vector<uint8_t> MakeValue(uint16_t key, uint32_t size)
     112                 :             : {
     113                 :      841598 :     std::vector<uint8_t> v(size);
     114                 :      841598 :     std::iota(v.begin(), v.end(), static_cast<uint8_t>(key ^ (key >> 8)));
     115                 :      841598 :     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   [ +  -  -  +  :     4212512 :     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         [ +  - ]:          58 :     void Unserialize(Stream&) { throw std::ios_base::failure{"always fail"}; }
     130                 :             : };
     131                 :             : 
     132                 :      842629 : uint16_t ConsumeKey(FuzzedDataProvider& provider) { return provider.ConsumeIntegral<uint16_t>(); }
     133                 :      794038 : uint32_t ConsumeValueSize(FuzzedDataProvider& provider)
     134                 :             : {
     135                 :      794038 :     const uint16_t len{provider.ConsumeIntegralInRange<uint16_t>(0, MAX_VALUE_LEN)};
     136                 :      794038 :     const uint8_t multiplier{provider.ConsumeIntegralInRange<uint8_t>(1, MAX_VALUE_MULTIPLIER)};
     137                 :      794038 :     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                 :       12173 : void VerifyIterator(CDBWrapper& dbw, const Oracle& oracle,
     143                 :             :                     bool obfuscate, std::optional<uint16_t> seek_key = std::nullopt)
     144                 :             : {
     145         [ +  + ]:       12173 :     const std::unique_ptr<CDBIterator> it{dbw.NewIterator()};
     146         [ +  + ]:       12173 :     auto oracle_it{seek_key ? oracle.lower_bound(*seek_key) : oracle.begin()};
     147         [ +  + ]:       12173 :     if (seek_key) {
     148         [ +  - ]:        2603 :         it->Seek(*seek_key);
     149                 :             :     } else {
     150         [ +  - ]:        9570 :         it->SeekToFirst();
     151                 :             :     }
     152   [ +  -  +  -  :       25175 :     for (; it->Valid(); it->Next()) {
                   +  + ]
     153                 :       13002 :         uint16_t db_key;
     154   [ +  -  -  + ]:       13002 :         assert(it->GetKey(db_key));
     155   [ +  +  +  + ]:       13002 :         if (oracle_it != oracle.end() && db_key == oracle_it->first) {
     156                 :        5770 :             std::vector<uint8_t> db_value;
     157   [ +  -  -  + ]:        5770 :             assert(it->GetValue(db_value));
     158   [ +  -  -  + ]:        5770 :             assert(db_value == MakeValue(db_key, oracle_it->second));
     159                 :        5770 :             ++oracle_it;
     160                 :        5770 :         } else {
     161         [ -  + ]:        7232 :             assert(obfuscate);
     162         [ +  - ]:        7232 :             std::string key_str;
     163   [ +  -  -  + ]:        7232 :             assert(it->GetKey(key_str));
     164         [ -  + ]:        7232 :             assert(key_str == OBFUSCATION_KEY);
     165                 :        7232 :         }
     166                 :             :     }
     167         [ -  + ]:       12173 :     assert(oracle_it == oracle.end());
     168                 :       12173 : }
     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                 :        9861 : DBParams ConsumeDBParams(FuzzedDataProvider& provider, leveldb::Env* testing_env,
     185                 :             :                          bool obfuscate, DBOptions options = {})
     186                 :             : {
     187                 :       19722 :     return DBParams{
     188                 :             :         .path = "dbwrapper_fuzz",
     189                 :        9861 :         .cache_bytes = provider.ConsumeIntegralInRange<size_t>(64 << 10, 1_MiB),
     190                 :             :         .obfuscate = obfuscate,
     191                 :        9861 :         .bloom_filter = provider.ConsumeBool(),
     192                 :             :         .options = options,
     193                 :             :         .testing_env = testing_env,
     194                 :        9861 :         .max_file_size = provider.ConsumeBool()
     195         [ +  + ]:        9861 :             ? DBWRAPPER_MAX_FILE_SIZE
     196                 :         819 :             : provider.ConsumeIntegralInRange<size_t>(1_MiB, 4_MiB),
     197                 :        9861 :     };
     198                 :             : }
     199                 :             : 
     200                 :             : template <typename DrainWorkFn, typename RunOneFn>
     201                 :        1158 : void TestDbWrapper(FuzzedDataProvider& provider,
     202                 :             :                       leveldb::Env* testing_env,
     203                 :             :                       DrainWorkFn drain_work,
     204                 :             :                       RunOneFn run_one,
     205                 :             :                       bool allow_force_compact)
     206                 :             : {
     207                 :        1158 :     SeedRandomStateForTest(SeedRand::ZEROS);
     208                 :             : 
     209                 :        1158 :     const bool obfuscate{provider.ConsumeBool()};
     210                 :             : 
     211                 :       10662 :     const auto make_db{[&](DBOptions options = {}) {
     212   [ +  -  +  - ]:       19008 :         return std::make_unique<CDBWrapper>(ConsumeDBParams(provider, testing_env, obfuscate, options));
     213                 :             :     }};
     214                 :        1158 :     std::unique_ptr<CDBWrapper> dbw{make_db()};
     215                 :             : 
     216                 :             :     // Oracle: key → value size. Content is reconstructed via MakeValue().
     217                 :        1158 :     Oracle oracle;
     218                 :             : 
     219   [ +  +  +  + ]:       19038 :     LIMITED_WHILE (provider.ConsumeBool(), 1'000) {
     220         [ +  - ]:       17880 :         CallOneOf(
     221                 :             :             provider,
     222                 :             :             // --- Mutations ---
     223                 :        1088 :             [&] {
     224                 :         544 :                 const auto key{ConsumeKey(provider)};
     225                 :         544 :                 const auto size{ConsumeValueSize(provider)};
     226                 :         158 :                 drain_work();
     227   [ +  -  +  - ]:         544 :                 dbw->Write(key, MakeValue(key, size), /*fSync=*/provider.ConsumeBool());
     228                 :         544 :                 oracle[key] = size;
     229                 :             :             },
     230                 :        7760 :             [&] {
     231                 :        3880 :                 const auto key{ConsumeKey(provider)};
     232                 :        2306 :                 drain_work();
     233                 :        3880 :                 dbw->Erase(key, /*fSync=*/provider.ConsumeBool());
     234                 :        3880 :                 oracle.erase(key);
     235                 :             :             },
     236                 :         120 :             [&] {
     237   [ +  -  +  - ]:         120 :                 CDBBatch batch{*dbw};
     238   [ +  -  +  - ]:         120 :                 std::map<uint16_t, uint32_t> batch_writes;
     239                 :         120 :                 std::set<uint16_t> batch_erases;
     240                 :         315 :                 const auto fill{[&] {
     241   [ +  +  +  +  :        1298 :                     LIMITED_WHILE (provider.ConsumeBool(), 20) {
             +  +  +  + ]
     242                 :        1103 :                         const auto key{ConsumeKey(provider)};
     243   [ +  +  +  + ]:        1103 :                         if (provider.ConsumeBool()) {
     244                 :         893 :                             const auto size{ConsumeValueSize(provider)};
     245   [ +  -  +  - ]:         893 :                             batch.Write(key, MakeValue(key, size));
     246                 :         893 :                             batch_writes[key] = size;
     247                 :         893 :                             batch_erases.erase(key);
     248                 :             :                         } else {
     249                 :         210 :                             batch.Erase(key);
     250                 :         210 :                             batch_erases.insert(key);
     251                 :         210 :                             batch_writes.erase(key);
     252                 :             :                         }
     253                 :             :                     }
     254                 :             :                 }};
     255   [ +  -  +  - ]:         120 :                 fill();
     256   [ +  +  +  + ]:         120 :                 if (provider.ConsumeBool()) {
     257   [ +  -  -  +  :          75 :                     assert(batch.ApproximateSize() >= WRITE_BATCH_HEADER);
             +  -  -  + ]
     258   [ +  -  +  - ]:          75 :                     batch.Clear();
     259   [ +  -  -  +  :          75 :                     assert(batch.ApproximateSize() == WRITE_BATCH_HEADER);
             +  -  -  + ]
     260                 :          75 :                     batch_writes.clear();
     261                 :          75 :                     batch_erases.clear();
     262   [ +  -  +  - ]:          75 :                     fill();
     263                 :             :                 }
     264         [ +  - ]:          61 :                 drain_work();
     265   [ +  -  +  - ]:         120 :                 dbw->WriteBatch(batch, /*fSync=*/provider.ConsumeBool());
     266   [ +  -  +  +  :         407 :                 for (const auto& [k, v] : batch_writes) oracle[k] = v;
             +  -  +  + ]
     267   [ +  +  +  + ]:         204 :                 for (const auto& k : batch_erases) oracle.erase(k);
     268                 :         120 :             },
     269                 :       16692 :             [&] {
     270                 :        4330 :                 drain_work();
     271   [ +  -  +  - ]:        8346 :                 dbw.reset();
     272                 :        8346 :                 DBOptions options{};
     273   [ +  -  +  +  :        8346 :                 if (allow_force_compact && provider.ConsumeBool()) {
             -  +  -  - ]
     274                 :        3836 :                     options.force_compact = true;
     275                 :             :                 }
     276                 :        8346 :                 dbw = make_db(options);
     277                 :        8346 :                 VerifyIterator(*dbw, oracle, obfuscate);
     278                 :             :             },
     279                 :             :             // --- Reads ---
     280                 :         232 :             [&] {
     281                 :         116 :                 const auto key{ConsumeKey(provider)};
     282                 :         116 :                 std::vector<uint8_t> value;
     283   [ +  -  +  - ]:         116 :                 const bool found{dbw->Read(key, value)};
     284   [ +  +  +  + ]:         116 :                 if (const auto it{oracle.find(key)}; it != oracle.end()) {
     285   [ +  -  +  -  :          30 :                     assert(found && value == MakeValue(key, it->second));
          -  +  +  -  +  
                -  -  + ]
     286                 :             :                 } else {
     287   [ -  +  -  + ]:         101 :                     assert(!found);
     288                 :             :                 }
     289                 :         116 :             },
     290                 :        1720 :             [&] {
     291                 :         860 :                 const auto key{ConsumeKey(provider)};
     292   [ -  +  -  + ]:         860 :                 assert(dbw->Exists(key) == oracle.contains(key));
     293                 :             :             },
     294                 :         204 :             [&] {
     295                 :         102 :                 uint16_t key{};
     296   [ +  +  +  +  :         102 :                 if (!oracle.empty() && provider.ConsumeBool()) {
             +  +  +  + ]
     297                 :          27 :                     auto it{oracle.begin()};
     298                 :          27 :                     std::advance(it, provider.ConsumeIntegralInRange<size_t>(0, oracle.size() - 1));
     299                 :          27 :                     key = it->first;
     300                 :             :                 } else {
     301                 :          75 :                     key = ConsumeKey(provider);
     302                 :             :                 }
     303                 :             :                 FailUnserialize wrong_type;
     304   [ -  +  -  + ]:         102 :                 assert(!dbw->Read(key, wrong_type));
     305                 :             :             },
     306                 :        5338 :             [&] {
     307                 :        5338 :                 const auto seek_key{provider.ConsumeBool()
     308   [ +  +  +  + ]:        2669 :                                         ? std::optional<uint16_t>{ConsumeKey(provider)}
     309                 :             :                                         : std::nullopt};
     310                 :        2669 :                 VerifyIterator(*dbw, oracle, obfuscate, seek_key);
     311                 :             :             },
     312                 :             :             // --- Stats ---
     313                 :         158 :             [&] {
     314   [ +  +  +  -  :         147 :                 assert(dbw->IsEmpty() == (oracle.empty() && !obfuscate));
          -  +  +  +  +  
                +  -  + ]
     315                 :             :             },
     316                 :         940 :             [&] {
     317                 :         470 :                 const auto [k1, k2]{std::minmax({ConsumeKey(provider), ConsumeKey(provider)}, LevelDBBytewiseU16Cmp{})};
     318                 :         470 :                 const size_t estimate_size{dbw->EstimateSize(k1, k2)};
     319   [ +  +  -  +  :         470 :                 if (k1 == k2) assert(estimate_size == 0);
             +  +  -  + ]
     320                 :             :             },
     321                 :         103 :             [&] {
     322                 :         103 :                 (void)dbw->DynamicMemoryUsage();
     323                 :             :             },
     324                 :             :             // --- Compaction control (no-op when run_one is no-op) ---
     325                 :         260 :             [&] {
     326                 :         260 :                 run_one();
     327                 :             :             });
     328                 :             :     }
     329                 :             : 
     330         [ +  - ]:        1158 :     VerifyIterator(*dbw, oracle, obfuscate);
     331         [ +  - ]:        1158 :     drain_work();
     332                 :        1158 : }
     333                 :             : 
     334                 :             : } // namespace
     335                 :             : 
     336   [ +  -  +  -  :        1016 : FUZZ_TARGET(dbwrapper, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
                   +  - ]
     337                 :             : {
     338                 :         539 :     FuzzedDataProvider provider{buffer.data(), buffer.size()};
     339                 :             : 
     340         [ +  - ]:         539 :     const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
     341         [ +  - ]:         539 :     DeterministicEnv det_env{memenv.get()};
     342         [ +  - ]:         539 :     TestDbWrapper(
     343                 :             :         provider, &det_env,
     344                 :        7394 :         [&] { det_env.DrainWork(); },
     345                 :         260 :         [&] { return det_env.RunOne(); },
     346                 :             :         /*allow_force_compact=*/false);
     347                 :         539 : }
     348                 :             : 
     349   [ +  -  +  -  :        1095 : FUZZ_TARGET(dbwrapper_threaded, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
                   +  - ]
     350                 :             : {
     351                 :         619 :     FuzzedDataProvider provider{buffer.data(), buffer.size()};
     352                 :             : 
     353         [ +  - ]:         619 :     const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
     354         [ +  - ]:         619 :     TestDbWrapper(
     355                 :             :         provider, memenv.get(),
     356                 :             :         /*drain_work=*/[] {},
     357                 :             :         /*run_one=*/[] { return false; },
     358                 :             :         /*allow_force_compact=*/true);
     359                 :         619 : }
     360                 :             : 
     361   [ +  -  +  -  :         833 : FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
                   +  - ]
     362                 :             : {
     363                 :         357 :     StartReadPoolIfNeeded();
     364                 :         357 :     SeedRandomStateForTest(SeedRand::ZEROS);
     365                 :             : 
     366                 :         357 :     FuzzedDataProvider provider{buffer.data(), buffer.size()};
     367                 :             : 
     368         [ +  - ]:         357 :     const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
     369         [ +  - ]:         357 :     DeterministicEnv det_env{memenv.get()};
     370                 :             : 
     371   [ +  -  +  - ]:         357 :     CDBWrapper db{ConsumeDBParams(provider, &det_env, /*obfuscate=*/provider.ConsumeBool())};
     372                 :             : 
     373                 :             :     // Seed the DB. Drain work after small batches so we don't deadlock on a
     374                 :             :     // scheduled compaction.
     375                 :         357 :     const size_t num_entries{provider.ConsumeIntegralInRange<size_t>(100, 5'000)};
     376                 :         357 :     std::vector<uint16_t> keys;
     377         [ +  - ]:         357 :     keys.reserve(num_entries);
     378                 :         357 :     Oracle oracle;
     379                 :         357 :     constexpr size_t SEED_BATCH_SIZE{400};
     380         [ +  + ]:        2567 :     for (size_t start{0}; start < num_entries; start += SEED_BATCH_SIZE) {
     381         [ +  - ]:        2210 :         CDBBatch batch{db};
     382         [ +  + ]:        2210 :         const size_t end{std::min(start + SEED_BATCH_SIZE, num_entries)};
     383         [ +  + ]:      794811 :         for (size_t i{start}; i < end; ++i) {
     384                 :      792601 :             const auto k{ConsumeKey(provider)};
     385                 :      792601 :             const auto size{ConsumeValueSize(provider)};
     386   [ +  -  +  - ]:      792601 :             batch.Write(k, MakeValue(k, size));
     387         [ +  - ]:      792601 :             keys.push_back(k);
     388         [ +  - ]:      792601 :             oracle[k] = size;
     389                 :             :         }
     390                 :             :         det_env.DrainWork();
     391         [ +  - ]:        2210 :         db.WriteBatch(batch, /*fSync=*/true);
     392                 :        2210 :     }
     393                 :             : 
     394   [ +  +  +  -  :         384 :     while (provider.ConsumeBool() && det_env.RunOne()) {}
             +  -  -  + ]
     395                 :             : 
     396                 :             :     // Build query list from seeded and random keys.
     397                 :         357 :     const size_t num_queries{provider.ConsumeIntegralInRange<size_t>(1, 2'000)};
     398                 :         357 :     enum class ReadOp { Read, Exists, IteratorSeek };
     399                 :         357 :     std::vector<std::tuple<ReadOp, uint16_t>> queries;
     400         [ +  - ]:         357 :     queries.reserve(num_queries);
     401         [ +  + ]:       40693 :     for (size_t i{0}; i < num_queries; ++i) {
     402                 :       40336 :         const auto op{provider.PickValueInArray({ReadOp::Read, ReadOp::Exists, ReadOp::IteratorSeek})};
     403                 :       40336 :         const uint16_t key{provider.ConsumeBool()
     404         [ +  + ]:       40765 :                                ? keys[provider.ConsumeIntegralInRange<size_t>(0, keys.size() - 1)]
     405                 :       39907 :                                : ConsumeKey(provider)};
     406         [ +  - ]:       40336 :         queries.emplace_back(op, key);
     407                 :             :     }
     408                 :             : 
     409                 :             : 
     410                 :             :     // Workers + main thread synchronize on the latch so all reads start together.
     411                 :         357 :     std::latch start_latch{static_cast<ptrdiff_t>(MAX_READ_WORKERS + 1)};
     412         [ +  - ]:         357 :     std::vector<std::function<void()>> tasks(MAX_READ_WORKERS);
     413                 :         357 :     FastRandomContext rng{ConsumeUInt256(provider)};
     414         [ +  - ]:        3213 :     std::ranges::generate(tasks, [&] {
     415                 :        2856 :         return [&, seed = rng.rand256()] {
     416                 :        2856 :             FastRandomContext thread_rng{seed};
     417   [ -  +  +  - ]:        2856 :             std::vector<size_t> order(queries.size());
     418                 :        2856 :             std::iota(order.begin(), order.end(), size_t{0});
     419                 :        2856 :             std::ranges::shuffle(order, thread_rng);
     420   [ -  +  +  + ]:        2856 :             const size_t queries_to_run{std::min(queries.size(), MAX_READ_QUERIES_PER_WORKER)};
     421                 :        2856 :             std::vector<uint8_t> v;
     422         [ +  + ]:        2856 :             std::string key_str;
     423         [ +  + ]:        2856 :             start_latch.arrive_and_wait();
     424   [ +  -  -  + ]:        2856 :             const std::unique_ptr<CDBIterator> it{db.NewIterator()};
     425                 :             :             // Every read must agree with the oracle, the source of truth.
     426   [ -  +  +  + ]:       48800 :             for (const auto i : std::span{order}.first(queries_to_run)) {
     427   [ +  +  +  - ]:       45944 :                 const auto& [op, key] = queries[i];
     428   [ +  +  +  - ]:       45944 :                 switch (op) {
     429                 :       42539 :                 case ReadOp::Read:
     430         [ +  + ]:       42539 :                     if (const auto oit{oracle.find(key)}; oit != oracle.end()) {
     431   [ +  -  +  -  :       78366 :                         assert(db.Read(key, v) && v == MakeValue(key, oit->second));
             +  -  -  + ]
     432                 :             :                     } else {
     433   [ +  -  -  + ]:        3356 :                         assert(!db.Read(key, v));
     434                 :             :                     }
     435                 :             :                     break;
     436                 :         805 :                 case ReadOp::Exists:
     437   [ +  -  -  + ]:         805 :                     assert(db.Exists(key) == oracle.contains(key));
     438                 :             :                     break;
     439                 :        2600 :                 case ReadOp::IteratorSeek:
     440         [ +  - ]:        2600 :                     it->Seek(key);
     441                 :             :                     // Skip the obfuscation metadata entry (a non-uint16_t key) if we land
     442                 :             :                     // on it, so the result matches the oracle, which only tracks user keys.
     443   [ +  -  +  +  :        2600 :                     if (it->Valid() && it->GetKey(key_str) && key_str == OBFUSCATION_KEY) it->Next();
          +  -  +  +  +  
                +  +  - ]
     444         [ +  + ]:        2600 :                     if (const auto oit{oracle.lower_bound(key)}; oit != oracle.end()) {
     445   [ +  -  -  + ]:        2592 :                         assert(it->Valid());
     446                 :        2592 :                         uint16_t actual_key;
     447   [ +  -  +  -  :        2592 :                         assert(it->GetKey(actual_key) && actual_key == oit->first);
                   -  + ]
     448   [ +  -  +  -  :        5184 :                         assert(it->GetValue(v) && v == MakeValue(actual_key, oit->second));
             +  -  -  + ]
     449                 :             :                     } else {
     450   [ +  -  -  + ]:           8 :                         assert(!it->Valid());
     451                 :             :                     }
     452                 :             :                     break;
     453                 :             :                 }
     454                 :             :             }
     455                 :        5712 :         };
     456                 :             :     });
     457         [ -  + ]:         357 :     auto futures{*Assert(g_read_pool.Submit(std::move(tasks)))};
     458                 :             : 
     459                 :             :     // Release the workers and immediately run the queued compaction on this
     460                 :             :     // thread, so compaction races against the concurrent reads.
     461         [ +  + ]:         357 :     start_latch.arrive_and_wait();
     462                 :         357 :     det_env.DrainWork();
     463                 :             : 
     464   [ +  -  +  + ]:        3213 :     for (auto& fut : futures) fut.get();
     465                 :             :     det_env.DrainWork();
     466                 :         357 : }
        

Generated by: LCOV version 2.0-1