rrudb 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +26 -0
  5. data/examples/example.rb +39 -0
  6. data/ext/rudb/NuDB/include/nudb/CMakeLists.txt +104 -0
  7. data/ext/rudb/NuDB/include/nudb/_experimental/basic_seconds_clock.hpp +200 -0
  8. data/ext/rudb/NuDB/include/nudb/_experimental/chrono_util.hpp +58 -0
  9. data/ext/rudb/NuDB/include/nudb/_experimental/test/fail_file.hpp +343 -0
  10. data/ext/rudb/NuDB/include/nudb/_experimental/test/temp_dir.hpp +73 -0
  11. data/ext/rudb/NuDB/include/nudb/_experimental/test/test_store.hpp +451 -0
  12. data/ext/rudb/NuDB/include/nudb/_experimental/test/xor_shift_engine.hpp +105 -0
  13. data/ext/rudb/NuDB/include/nudb/_experimental/util.hpp +288 -0
  14. data/ext/rudb/NuDB/include/nudb/basic_store.hpp +461 -0
  15. data/ext/rudb/NuDB/include/nudb/concepts.hpp +205 -0
  16. data/ext/rudb/NuDB/include/nudb/context.hpp +144 -0
  17. data/ext/rudb/NuDB/include/nudb/create.hpp +117 -0
  18. data/ext/rudb/NuDB/include/nudb/detail/arena.hpp +296 -0
  19. data/ext/rudb/NuDB/include/nudb/detail/bucket.hpp +473 -0
  20. data/ext/rudb/NuDB/include/nudb/detail/buffer.hpp +86 -0
  21. data/ext/rudb/NuDB/include/nudb/detail/bulkio.hpp +196 -0
  22. data/ext/rudb/NuDB/include/nudb/detail/cache.hpp +236 -0
  23. data/ext/rudb/NuDB/include/nudb/detail/endian.hpp +93 -0
  24. data/ext/rudb/NuDB/include/nudb/detail/field.hpp +265 -0
  25. data/ext/rudb/NuDB/include/nudb/detail/format.hpp +630 -0
  26. data/ext/rudb/NuDB/include/nudb/detail/gentex.hpp +259 -0
  27. data/ext/rudb/NuDB/include/nudb/detail/mutex.hpp +26 -0
  28. data/ext/rudb/NuDB/include/nudb/detail/pool.hpp +243 -0
  29. data/ext/rudb/NuDB/include/nudb/detail/store_base.hpp +45 -0
  30. data/ext/rudb/NuDB/include/nudb/detail/stream.hpp +149 -0
  31. data/ext/rudb/NuDB/include/nudb/detail/xxhash.hpp +328 -0
  32. data/ext/rudb/NuDB/include/nudb/error.hpp +257 -0
  33. data/ext/rudb/NuDB/include/nudb/file.hpp +55 -0
  34. data/ext/rudb/NuDB/include/nudb/impl/basic_store.ipp +785 -0
  35. data/ext/rudb/NuDB/include/nudb/impl/context.ipp +241 -0
  36. data/ext/rudb/NuDB/include/nudb/impl/create.ipp +163 -0
  37. data/ext/rudb/NuDB/include/nudb/impl/error.ipp +175 -0
  38. data/ext/rudb/NuDB/include/nudb/impl/posix_file.ipp +248 -0
  39. data/ext/rudb/NuDB/include/nudb/impl/recover.ipp +209 -0
  40. data/ext/rudb/NuDB/include/nudb/impl/rekey.ipp +248 -0
  41. data/ext/rudb/NuDB/include/nudb/impl/verify.ipp +634 -0
  42. data/ext/rudb/NuDB/include/nudb/impl/visit.ipp +96 -0
  43. data/ext/rudb/NuDB/include/nudb/impl/win32_file.ipp +264 -0
  44. data/ext/rudb/NuDB/include/nudb/native_file.hpp +76 -0
  45. data/ext/rudb/NuDB/include/nudb/nudb.hpp +27 -0
  46. data/ext/rudb/NuDB/include/nudb/posix_file.hpp +228 -0
  47. data/ext/rudb/NuDB/include/nudb/progress.hpp +32 -0
  48. data/ext/rudb/NuDB/include/nudb/recover.hpp +73 -0
  49. data/ext/rudb/NuDB/include/nudb/rekey.hpp +110 -0
  50. data/ext/rudb/NuDB/include/nudb/store.hpp +27 -0
  51. data/ext/rudb/NuDB/include/nudb/type_traits.hpp +63 -0
  52. data/ext/rudb/NuDB/include/nudb/verify.hpp +200 -0
  53. data/ext/rudb/NuDB/include/nudb/version.hpp +21 -0
  54. data/ext/rudb/NuDB/include/nudb/visit.hpp +63 -0
  55. data/ext/rudb/NuDB/include/nudb/win32_file.hpp +246 -0
  56. data/ext/rudb/NuDB/include/nudb/xxhasher.hpp +45 -0
  57. data/ext/rudb/extconf.rb +12 -0
  58. data/ext/rudb/rudb.cpp +234 -0
  59. data/lib/rudb/version.rb +3 -0
  60. data/lib/rudb.rb +1 -0
  61. metadata +104 -0
@@ -0,0 +1,785 @@
1
+ //
2
+ // Copyright (c) 2015-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
3
+ //
4
+ // Distributed under the Boost Software License, Version 1.0. (See accompanying
5
+ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6
+ //
7
+
8
+ #ifndef NUDB_IMPL_BASIC_STORE_IPP
9
+ #define NUDB_IMPL_BASIC_STORE_IPP
10
+
11
+ #include <nudb/concepts.hpp>
12
+ #include <nudb/recover.hpp>
13
+ #include <boost/assert.hpp>
14
+ #include <cmath>
15
+ #include <memory>
16
+
17
+ #ifndef NUDB_DEBUG_LOG
18
+ #define NUDB_DEBUG_LOG 0
19
+ #endif
20
+ #if NUDB_DEBUG_LOG
21
+ #include <boost/beast/_experimental/unit_test/dstream.hpp>
22
+ #include <iostream>
23
+ #endif
24
+
25
+ namespace nudb {
26
+
27
+ template<class Hasher, class File>
28
+ basic_store<Hasher, File>::state::
29
+ state(File&& df_, File&& kf_, File&& lf_,
30
+ path_type const& dp_, path_type const& kp_,
31
+ path_type const& lp_,
32
+ detail::key_file_header const& kh_)
33
+ : df(std::move(df_))
34
+ , kf(std::move(kf_))
35
+ , lf(std::move(lf_))
36
+ , dp(dp_)
37
+ , kp(kp_)
38
+ , lp(lp_)
39
+ , hasher(kh_.salt)
40
+ , p0(kh_.key_size, "p0")
41
+ , p1(kh_.key_size, "p1")
42
+ , c1(kh_.key_size, kh_.block_size, "c1")
43
+ , kh(kh_)
44
+ {
45
+ static_assert(is_File<File>::value,
46
+ "File requirements not met");
47
+ }
48
+
49
+ //------------------------------------------------------------------------------
50
+
51
+ template<class Hasher, class File>
52
+ basic_store<Hasher, File>::
53
+ ~basic_store()
54
+ {
55
+ error_code ec;
56
+ // We call close here to make sure data is intact
57
+ // if an exception destroys the basic_store, but callers
58
+ // should always call close manually to receive the
59
+ // error code.
60
+ close(ec);
61
+ }
62
+
63
+ template<class Hasher, class File>
64
+ path_type const&
65
+ basic_store<Hasher, File>::
66
+ dat_path() const
67
+ {
68
+ BOOST_ASSERT(is_open());
69
+ return s_->dp;
70
+ }
71
+
72
+ template<class Hasher, class File>
73
+ path_type const&
74
+ basic_store<Hasher, File>::
75
+ key_path() const
76
+ {
77
+ BOOST_ASSERT(is_open());
78
+ return s_->kp;
79
+ }
80
+
81
+ template<class Hasher, class File>
82
+ path_type const&
83
+ basic_store<Hasher, File>::
84
+ log_path() const
85
+ {
86
+ BOOST_ASSERT(is_open());
87
+ return s_->lp;
88
+ }
89
+
90
+ template<class Hasher, class File>
91
+ std::uint64_t
92
+ basic_store<Hasher, File>::
93
+ appnum() const
94
+ {
95
+ BOOST_ASSERT(is_open());
96
+ return s_->kh.appnum;
97
+ }
98
+
99
+ template<class Hasher, class File>
100
+ std::size_t
101
+ basic_store<Hasher, File>::
102
+ key_size() const
103
+ {
104
+ BOOST_ASSERT(is_open());
105
+ return s_->kh.key_size;
106
+ }
107
+
108
+ template<class Hasher, class File>
109
+ std::size_t
110
+ basic_store<Hasher, File>::
111
+ block_size() const
112
+ {
113
+ BOOST_ASSERT(is_open());
114
+ return s_->kh.block_size;
115
+ }
116
+
117
+ template<class Hasher, class File>
118
+ template<class... Args>
119
+ void
120
+ basic_store<Hasher, File>::
121
+ open(
122
+ path_type const& dat_path,
123
+ path_type const& key_path,
124
+ path_type const& log_path,
125
+ error_code& ec,
126
+ Args&&... args)
127
+ {
128
+ static_assert(is_Hasher<Hasher>::value,
129
+ "Hasher requirements not met");
130
+ using namespace detail;
131
+ BOOST_ASSERT(! is_open());
132
+ ec_ = {};
133
+ ecb_.store(false);
134
+ recover<Hasher, File>(
135
+ dat_path, key_path, log_path, ec, args...);
136
+ if(ec)
137
+ return;
138
+ File df(args...);
139
+ File kf(args...);
140
+ File lf(args...);
141
+ df.open(file_mode::append, dat_path, ec);
142
+ if(ec)
143
+ return;
144
+ kf.open(file_mode::write, key_path, ec);
145
+ if(ec)
146
+ return;
147
+ lf.create(file_mode::append, log_path, ec);
148
+ if(ec)
149
+ return;
150
+ // VFALCO TODO Erase empty log file if this
151
+ // function subsequently fails.
152
+ dat_file_header dh;
153
+ read(df, dh, ec);
154
+ if(ec)
155
+ return;
156
+ verify(dh, ec);
157
+ if(ec)
158
+ return;
159
+ key_file_header kh;
160
+ read(kf, kh, ec);
161
+ if(ec)
162
+ return;
163
+ verify<Hasher>(kh, ec);
164
+ if(ec)
165
+ return;
166
+ verify<Hasher>(dh, kh, ec);
167
+ if(ec)
168
+ return;
169
+ boost::optional<state> s;
170
+ s.emplace(std::move(df), std::move(kf), std::move(lf),
171
+ dat_path, key_path, log_path, kh);
172
+ thresh_ = std::max<std::size_t>(65536UL,
173
+ kh.load_factor * kh.capacity);
174
+ frac_ = thresh_ / 2;
175
+ buckets_ = kh.buckets;
176
+ modulus_ = ceil_pow2(kh.buckets);
177
+ // VFALCO TODO This could be better
178
+ if(buckets_ < 1)
179
+ {
180
+ ec = error::short_key_file;
181
+ return;
182
+ }
183
+ dataWriteSize_ = 32 * nudb::block_size(dat_path);
184
+ logWriteSize_ = 32 * nudb::block_size(log_path);
185
+ s_.emplace(std::move(*s));
186
+ open_ = true;
187
+ ctx_->insert(*this);
188
+ }
189
+
190
+ template<class Hasher, class File>
191
+ void
192
+ basic_store<Hasher, File>::
193
+ close(error_code& ec)
194
+ {
195
+ if(open_)
196
+ {
197
+ open_ = false;
198
+ ctx_->erase(*this);
199
+ if(! s_->p1.empty())
200
+ {
201
+ std::size_t work;
202
+ detail::unique_lock_type m{m_};
203
+ commit(m, work, ec_);
204
+ if (ec_)
205
+ ecb_.store(true);
206
+ }
207
+ if(ecb_)
208
+ {
209
+ ec = ec_;
210
+ return;
211
+ }
212
+ s_->lf.close();
213
+ state s{std::move(*s_)};
214
+ File::erase(s.lp, ec_);
215
+ if(ec_)
216
+ ec = ec_;
217
+ }
218
+ }
219
+
220
+ template<class Hasher, class File>
221
+ template<class Callback>
222
+ void
223
+ basic_store<Hasher, File>::
224
+ fetch(
225
+ void const* key,
226
+ Callback && callback,
227
+ error_code& ec)
228
+ {
229
+ using namespace detail;
230
+ BOOST_ASSERT(is_open());
231
+ if(ecb_)
232
+ {
233
+ ec = ec_;
234
+ return;
235
+ }
236
+ auto const h =
237
+ hash(key, s_->kh.key_size, s_->hasher);
238
+ shared_lock_type m{m_};
239
+ {
240
+ auto iter = s_->p1.find(key);
241
+ if(iter == s_->p1.end())
242
+ {
243
+ iter = s_->p0.find(key);
244
+ if(iter == s_->p0.end())
245
+ goto cont;
246
+ }
247
+ callback(iter->first.data, iter->first.size);
248
+ return;
249
+ }
250
+ cont:
251
+ auto const n = bucket_index(h, buckets_, modulus_);
252
+ auto const iter = s_->c1.find(n);
253
+ if(iter != s_->c1.end())
254
+ return fetch(h, key, iter->second, callback, ec);
255
+ genlock<gentex> g{g_};
256
+ m.unlock();
257
+ buffer buf{s_->kh.block_size};
258
+ // b constructs from uninitialized buf
259
+ bucket b{s_->kh.block_size, buf.get()};
260
+ b.read(s_->kf, (n + 1) * b.block_size(), ec);
261
+ if(ec)
262
+ return;
263
+ fetch(h, key, b, callback, ec);
264
+ }
265
+
266
+ template<class Hasher, class File>
267
+ void
268
+ basic_store<Hasher, File>::
269
+ insert(
270
+ void const* key,
271
+ void const* data,
272
+ nsize_t size,
273
+ error_code& ec)
274
+ {
275
+ using namespace detail;
276
+ using namespace std::chrono;
277
+ BOOST_ASSERT(is_open());
278
+ if(ecb_)
279
+ {
280
+ ec = ec_;
281
+ return;
282
+ }
283
+ // Data Record
284
+ BOOST_ASSERT(size > 0); // zero disallowed
285
+ BOOST_ASSERT(size <= field<uint32_t>::max); // too large
286
+ auto const h =
287
+ hash(key, s_->kh.key_size, s_->hasher);
288
+ std::lock_guard<std::mutex> u{u_};
289
+ {
290
+ shared_lock_type m{m_};
291
+ if(s_->p1.find(key) != s_->p1.end() ||
292
+ s_->p0.find(key) != s_->p0.end())
293
+ {
294
+ ec = error::key_exists;
295
+ return;
296
+ }
297
+ auto const n = bucket_index(h, buckets_, modulus_);
298
+ auto const iter = s_->c1.find(n);
299
+ if(iter != s_->c1.end())
300
+ {
301
+ auto const found = exists(
302
+ h, key, &m, iter->second, ec);
303
+ if(ec)
304
+ return;
305
+ if(found)
306
+ {
307
+ ec = error::key_exists;
308
+ return;
309
+ }
310
+ // m is now unlocked
311
+ }
312
+ else
313
+ {
314
+ // VFALCO Audit for concurrency
315
+ genlock<gentex> g{g_};
316
+ m.unlock();
317
+ buffer buf;
318
+ buf.reserve(s_->kh.block_size);
319
+ bucket b{s_->kh.block_size, buf.get()};
320
+ b.read(s_->kf,
321
+ static_cast<noff_t>(n + 1) * s_->kh.block_size, ec);
322
+ if(ec)
323
+ return;
324
+ auto const found = exists(h, key, nullptr, b, ec);
325
+ if(ec)
326
+ return;
327
+ if(found)
328
+ {
329
+ ec = error::key_exists;
330
+ return;
331
+ }
332
+ }
333
+ }
334
+ // Perform insert
335
+ unique_lock_type m{m_};
336
+ s_->p1.insert(h, key, data, size);
337
+ auto const now = clock_type::now();
338
+ auto const elapsed = duration_cast<duration<float>>(
339
+ now > s_->when ? now - s_->when : clock_type::duration{1});
340
+ auto const work = s_->p1.data_size() +
341
+ 3 * s_->p1.size() * s_->kh.block_size;
342
+ auto const rate = static_cast<std::size_t>(
343
+ std::ceil(work / elapsed.count()));
344
+ auto const sleep =
345
+ s_->rate && rate > s_->rate;
346
+ m.unlock();
347
+ if(sleep)
348
+ std::this_thread::sleep_for(milliseconds{25});
349
+ }
350
+
351
+ // Fetch key in loaded bucket b or its spills.
352
+ //
353
+ template<class Hasher, class File>
354
+ template<class Callback>
355
+ void
356
+ basic_store<Hasher, File>::
357
+ fetch(
358
+ detail::nhash_t h,
359
+ void const* key,
360
+ detail::bucket b,
361
+ Callback&& callback,
362
+ error_code& ec)
363
+ {
364
+ using namespace detail;
365
+ buffer buf0;
366
+ buffer buf1;
367
+ for(;;)
368
+ {
369
+ for(auto i = b.lower_bound(h); i < b.size(); ++i)
370
+ {
371
+ auto const item = b[i];
372
+ if(item.hash != h)
373
+ break;
374
+ // Data Record
375
+ auto const len =
376
+ s_->kh.key_size + // Key
377
+ item.size; // Value
378
+ buf0.reserve(len);
379
+ s_->df.read(item.offset +
380
+ field<uint48_t>::size, // Size
381
+ buf0.get(), len, ec);
382
+ if(ec)
383
+ return;
384
+ if(std::memcmp(buf0.get(), key,
385
+ s_->kh.key_size) == 0)
386
+ {
387
+ callback(
388
+ buf0.get() + s_->kh.key_size, item.size);
389
+ return;
390
+ }
391
+ }
392
+ auto const spill = b.spill();
393
+ if(! spill)
394
+ break;
395
+ buf1.reserve(s_->kh.block_size);
396
+ b = bucket(s_->kh.block_size,
397
+ buf1.get());
398
+ b.read(s_->df, spill, ec);
399
+ if(ec)
400
+ return;
401
+ }
402
+ ec = error::key_not_found;
403
+ }
404
+
405
+ // Returns `true` if the key exists
406
+ // lock is unlocked after the first bucket processed
407
+ //
408
+ template<class Hasher, class File>
409
+ bool
410
+ basic_store<Hasher, File>::
411
+ exists(
412
+ detail::nhash_t h,
413
+ void const* key,
414
+ detail::shared_lock_type* lock,
415
+ detail::bucket b,
416
+ error_code& ec)
417
+ {
418
+ using namespace detail;
419
+ buffer buf{s_->kh.key_size + s_->kh.block_size};
420
+ void* pk = buf.get();
421
+ void* pb = buf.get() + s_->kh.key_size;
422
+ for(;;)
423
+ {
424
+ for(auto i = b.lower_bound(h); i < b.size(); ++i)
425
+ {
426
+ auto const item = b[i];
427
+ if(item.hash != h)
428
+ break;
429
+ // Data Record
430
+ s_->df.read(item.offset +
431
+ field<uint48_t>::size, // Size
432
+ pk, s_->kh.key_size, ec); // Key
433
+ if(ec)
434
+ return false;
435
+ if(std::memcmp(pk, key, s_->kh.key_size) == 0)
436
+ return true;
437
+ }
438
+ auto spill = b.spill();
439
+ if(lock && lock->owns_lock())
440
+ lock->unlock();
441
+ if(! spill)
442
+ break;
443
+ b = bucket(s_->kh.block_size, pb);
444
+ b.read(s_->df, spill, ec);
445
+ if(ec)
446
+ return false;
447
+ }
448
+ return false;
449
+ }
450
+
451
+ // Split the bucket in b1 to b2
452
+ // b1 must be loaded
453
+ // tmp is used as a temporary buffer
454
+ // splits are written but not the new buckets
455
+ //
456
+ template<class Hasher, class File>
457
+ void
458
+ basic_store<Hasher, File>::
459
+ split(
460
+ detail::bucket& b1,
461
+ detail::bucket& b2,
462
+ detail::bucket& tmp,
463
+ nbuck_t n1,
464
+ nbuck_t n2,
465
+ nbuck_t buckets,
466
+ nbuck_t modulus,
467
+ detail::bulk_writer<File>& w,
468
+ error_code& ec)
469
+ {
470
+ using namespace detail;
471
+ // Trivial case: split empty bucket
472
+ if(b1.empty())
473
+ return;
474
+ // Split
475
+ for(std::size_t i = 0; i < b1.size();)
476
+ {
477
+ auto const e = b1[i];
478
+ auto const n = bucket_index(e.hash, buckets, modulus);
479
+ (void)n1;
480
+ (void)n2;
481
+ BOOST_ASSERT(n==n1 || n==n2);
482
+ if(n == n2)
483
+ {
484
+ b2.insert(e.offset, e.size, e.hash);
485
+ b1.erase(i);
486
+ }
487
+ else
488
+ {
489
+ ++i;
490
+ }
491
+ }
492
+ noff_t spill = b1.spill();
493
+ if(spill)
494
+ {
495
+ b1.spill(0);
496
+ do
497
+ {
498
+ // If any part of the spill record is
499
+ // in the write buffer then flush first
500
+ if(spill + bucket_size(s_->kh.capacity) >
501
+ w.offset() - w.size())
502
+ {
503
+ w.flush(ec);
504
+ if(ec)
505
+ return;
506
+ }
507
+ tmp.read(s_->df, spill, ec);
508
+ if(ec)
509
+ return;
510
+ for(std::size_t i = 0; i < tmp.size(); ++i)
511
+ {
512
+ auto const e = tmp[i];
513
+ auto const n = bucket_index(
514
+ e.hash, buckets, modulus);
515
+ BOOST_ASSERT(n==n1 || n==n2);
516
+ if(n == n2)
517
+ {
518
+ maybe_spill(b2, w, ec);
519
+ if(ec)
520
+ return;
521
+ b2.insert(e.offset, e.size, e.hash);
522
+ }
523
+ else
524
+ {
525
+ maybe_spill(b1, w, ec);
526
+ if(ec)
527
+ return;
528
+ b1.insert(e.offset, e.size, e.hash);
529
+ }
530
+ }
531
+ spill = tmp.spill();
532
+ }
533
+ while(spill);
534
+ }
535
+ }
536
+
537
+ template<class Hasher, class File>
538
+ detail::bucket
539
+ basic_store<Hasher, File>::
540
+ load(
541
+ nbuck_t n,
542
+ detail::cache& c1,
543
+ detail::cache& c0,
544
+ void* buf,
545
+ error_code& ec)
546
+ {
547
+ using namespace detail;
548
+ auto iter = c1.find(n);
549
+ if(iter != c1.end())
550
+ return iter->second;
551
+ iter = c0.find(n);
552
+ if(iter != c0.end())
553
+ return c1.insert(n, iter->second)->second;
554
+ bucket tmp{s_->kh.block_size, buf};
555
+ tmp.read(s_->kf,
556
+ static_cast<noff_t>(n + 1) * s_->kh.block_size, ec);
557
+ if(ec)
558
+ return {};
559
+ c0.insert(n, tmp);
560
+ return c1.insert(n, tmp)->second;
561
+ }
562
+
563
+ template<class Hasher, class File>
564
+ void
565
+ basic_store<Hasher, File>::
566
+ commit(detail::unique_lock_type& m,
567
+ std::size_t& work, error_code& ec)
568
+ {
569
+ using namespace detail;
570
+ BOOST_ASSERT(m.owns_lock());
571
+ BOOST_ASSERT(! s_->p1.empty());
572
+ swap(s_->p0, s_->p1);
573
+ m.unlock();
574
+ work = s_->p0.data_size();
575
+ cache c0(s_->kh.key_size, s_->kh.block_size, "c0");
576
+ cache c1(s_->kh.key_size, s_->kh.block_size, "c1");
577
+ // 0.63212 ~= 1 - 1/e
578
+ {
579
+ auto const size = static_cast<std::size_t>(
580
+ std::ceil(0.63212 * s_->p0.size()));
581
+ c0.reserve(size);
582
+ c1.reserve(size);
583
+ }
584
+ buffer buf1{s_->kh.block_size};
585
+ buffer buf2{s_->kh.block_size};
586
+ bucket tmp{s_->kh.block_size, buf1.get()};
587
+ // Prepare rollback information
588
+ log_file_header lh;
589
+ lh.version = currentVersion; // Version
590
+ lh.uid = s_->kh.uid; // UID
591
+ lh.appnum = s_->kh.appnum; // Appnum
592
+ lh.key_size = s_->kh.key_size; // Key Size
593
+ lh.salt = s_->kh.salt; // Salt
594
+ lh.pepper = pepper<Hasher>(lh.salt); // Pepper
595
+ lh.block_size = s_->kh.block_size; // Block Size
596
+ lh.key_file_size = s_->kf.size(ec); // Key File Size
597
+ if(ec)
598
+ return;
599
+ lh.dat_file_size = s_->df.size(ec); // Data File Size
600
+ if(ec)
601
+ return;
602
+ write(s_->lf, lh, ec);
603
+ if(ec)
604
+ return;
605
+ // Checkpoint
606
+ s_->lf.sync(ec);
607
+ if(ec)
608
+ return;
609
+ // Append data and spills to data file
610
+ auto modulus = modulus_;
611
+ auto buckets = buckets_;
612
+ {
613
+ // Bulk write to avoid write amplification
614
+ auto const size = s_->df.size(ec);
615
+ if(ec)
616
+ return;
617
+ bulk_writer<File> w{s_->df, size, dataWriteSize_};
618
+ // Write inserted data to the data file
619
+ for(auto& e : s_->p0)
620
+ {
621
+ // VFALCO This could be UB since other
622
+ // threads are reading other data members
623
+ // of this object in memory
624
+ e.second = w.offset();
625
+ auto os = w.prepare(value_size(
626
+ e.first.size, s_->kh.key_size), ec);
627
+ if(ec)
628
+ return;
629
+ // Data Record
630
+ write<uint48_t>(os, e.first.size); // Size
631
+ write(os, e.first.key, s_->kh.key_size); // Key
632
+ write(os, e.first.data, e.first.size); // Data
633
+ }
634
+ // Do inserts, splits, and build view
635
+ // of original and modified buckets
636
+ for(auto const& e : s_->p0)
637
+ {
638
+ // VFALCO Should this be >= or > ?
639
+ if((frac_ += 65536) >= thresh_)
640
+ {
641
+ // split
642
+ frac_ -= thresh_;
643
+ if(buckets == modulus)
644
+ modulus *= 2;
645
+ auto const n1 = buckets - (modulus / 2);
646
+ auto const n2 = buckets++;
647
+ auto b1 = load(n1, c1, c0, buf2.get(), ec);
648
+ if(ec)
649
+ return;
650
+ auto b2 = c1.create(n2);
651
+ // If split spills, the writer is
652
+ // flushed which can amplify writes.
653
+ split(b1, b2, tmp, n1, n2,
654
+ buckets, modulus, w, ec);
655
+ if(ec)
656
+ return;
657
+ }
658
+ // Insert
659
+ auto const n = bucket_index(
660
+ e.first.hash, buckets, modulus);
661
+ auto b = load(n, c1, c0, buf2.get(), ec);
662
+ if(ec)
663
+ return;
664
+ // This can amplify writes if it spills.
665
+ maybe_spill(b, w, ec);
666
+ if(ec)
667
+ return;
668
+ b.insert(e.second, e.first.size, e.first.hash);
669
+ }
670
+ w.flush(ec);
671
+ if(ec)
672
+ return;
673
+ }
674
+ work += s_->kh.block_size * (2 * c0.size() + c1.size());
675
+ // Give readers a view of the new buckets.
676
+ // This might be slightly better than the old
677
+ // view since there could be fewer spills.
678
+ m.lock();
679
+ swap(c1, s_->c1);
680
+ s_->p0.clear();
681
+ buckets_ = buckets;
682
+ modulus_ = modulus;
683
+ g_.start();
684
+ m.unlock();
685
+ // Write clean buckets to log file
686
+ {
687
+ auto const size = s_->lf.size(ec);
688
+ if(ec)
689
+ return;
690
+ bulk_writer<File> w{s_->lf, size, logWriteSize_};
691
+ for(auto const e : c0)
692
+ {
693
+ // Log Record
694
+ auto os = w.prepare(
695
+ field<std::uint64_t>::size + // Index
696
+ e.second.actual_size(), ec); // Bucket
697
+ if(ec)
698
+ return;
699
+ // Log Record
700
+ write<std::uint64_t>(os, e.first); // Index
701
+ e.second.write(os); // Bucket
702
+ }
703
+ c0.clear();
704
+ w.flush(ec);
705
+ if(ec)
706
+ return;
707
+ s_->lf.sync(ec);
708
+ if(ec)
709
+ return;
710
+ }
711
+ g_.finish();
712
+ // Write new buckets to key file
713
+ for(auto const e : s_->c1)
714
+ {
715
+ e.second.write(s_->kf,
716
+ (e.first + 1) * s_->kh.block_size, ec);
717
+ if(ec)
718
+ return;
719
+ }
720
+ // Finalize the commit
721
+ s_->df.sync(ec);
722
+ if(ec)
723
+ return;
724
+ s_->kf.sync(ec);
725
+ if(ec)
726
+ return;
727
+ s_->lf.trunc(0, ec);
728
+ if(ec)
729
+ return;
730
+ s_->lf.sync(ec);
731
+ if(ec)
732
+ return;
733
+ // Cache is no longer needed, all fetches will go straight
734
+ // to disk again. Do this after the sync, otherwise readers
735
+ // might get blocked longer due to the extra I/O.
736
+ m.lock();
737
+ s_->c1.clear();
738
+ }
739
+
740
+ template<class Hasher, class File>
741
+ void
742
+ basic_store<Hasher, File>::
743
+ flush()
744
+ {
745
+ using namespace std::chrono;
746
+ using namespace detail;
747
+
748
+ #if NUDB_DEBUG_LOG
749
+ beast::unit_test::dstream dout{std::cout};
750
+ #endif
751
+ {
752
+ unique_lock_type m{m_};
753
+ if(! s_->p1.empty())
754
+ {
755
+ std::size_t work;
756
+ commit(m, work, ec_);
757
+ if(ec_)
758
+ {
759
+ ecb_.store(true);
760
+ return;
761
+ }
762
+ BOOST_ASSERT(m.owns_lock());
763
+ auto const now = clock_type::now();
764
+ auto const elapsed = duration_cast<duration<float>>(
765
+ now > s_->when ? now - s_->when : clock_type::duration{1});
766
+ s_->rate = static_cast<std::size_t>(
767
+ std::ceil(work / elapsed.count()));
768
+ #if NUDB_DEBUG_LOG
769
+ dout <<
770
+ "work=" << work <<
771
+ ", time=" << elapsed.count() <<
772
+ ", rate=" << s_->rate <<
773
+ "\n";
774
+ #endif
775
+ }
776
+ s_->p1.periodic_activity();
777
+ s_->when = clock_type::now();
778
+ }
779
+ if(ec_)
780
+ ecb_.store(true);
781
+ }
782
+
783
+ } // nudb
784
+
785
+ #endif