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 : }
|