1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 #ifndef CEPH_OS_BLUESTORE_BLUEFS_H
4 #define CEPH_OS_BLUESTORE_BLUEFS_H
10 #include "bluefs_types.h"
11 #include "blk/BlockDevice.h"
13 #include "common/RefCountedObj.h"
14 #include "common/ceph_context.h"
15 #include "global/global_context.h"
16 #include "include/common_fwd.h"
18 #include "boost/intrusive/list.hpp"
19 #include "boost/dynamic_bitset.hpp"
24 l_bluefs_first
= 732600,
25 l_bluefs_db_total_bytes
,
26 l_bluefs_db_used_bytes
,
27 l_bluefs_wal_total_bytes
,
28 l_bluefs_wal_used_bytes
,
29 l_bluefs_slow_total_bytes
,
30 l_bluefs_slow_used_bytes
,
33 l_bluefs_log_compactions
,
34 l_bluefs_log_write_count
,
35 l_bluefs_logged_bytes
,
36 l_bluefs_files_written_wal
,
37 l_bluefs_files_written_sst
,
38 l_bluefs_write_count_wal
,
39 l_bluefs_write_count_sst
,
40 l_bluefs_bytes_written_wal
,
41 l_bluefs_bytes_written_sst
,
42 l_bluefs_bytes_written_slow
,
43 l_bluefs_max_bytes_wal
,
44 l_bluefs_max_bytes_db
,
45 l_bluefs_max_bytes_slow
,
46 l_bluefs_main_alloc_unit
,
47 l_bluefs_db_alloc_unit
,
48 l_bluefs_wal_alloc_unit
,
49 l_bluefs_read_random_count
,
50 l_bluefs_read_random_bytes
,
51 l_bluefs_read_random_disk_count
,
52 l_bluefs_read_random_disk_bytes
,
53 l_bluefs_read_random_disk_bytes_wal
,
54 l_bluefs_read_random_disk_bytes_db
,
55 l_bluefs_read_random_disk_bytes_slow
,
56 l_bluefs_read_random_buffer_count
,
57 l_bluefs_read_random_buffer_bytes
,
60 l_bluefs_read_disk_count
,
61 l_bluefs_read_disk_bytes
,
62 l_bluefs_read_disk_bytes_wal
,
63 l_bluefs_read_disk_bytes_db
,
64 l_bluefs_read_disk_bytes_slow
,
65 l_bluefs_read_prefetch_count
,
66 l_bluefs_read_prefetch_bytes
,
68 l_bluefs_write_disk_count
,
70 l_bluefs_compaction_lat
,
71 l_bluefs_compaction_lock_lat
,
72 l_bluefs_alloc_shared_dev_fallbacks
,
73 l_bluefs_alloc_shared_size_fallbacks
,
74 l_bluefs_read_zeros_candidate
,
75 l_bluefs_read_zeros_errors
,
79 class BlueFSVolumeSelector
{
81 typedef std::vector
<std::pair
<std::string
, uint64_t>> paths
;
83 virtual ~BlueFSVolumeSelector() {
85 virtual void* get_hint_for_log() const = 0;
86 virtual void* get_hint_by_dir(std::string_view dirname
) const = 0;
88 virtual void add_usage(void* file_hint
, const bluefs_fnode_t
& fnode
) = 0;
89 virtual void sub_usage(void* file_hint
, const bluefs_fnode_t
& fnode
) = 0;
90 virtual void add_usage(void* file_hint
, uint64_t fsize
) = 0;
91 virtual void sub_usage(void* file_hint
, uint64_t fsize
) = 0;
92 virtual uint8_t select_prefer_bdev(void* hint
) = 0;
93 virtual void get_paths(const std::string
& base
, paths
& res
) const = 0;
94 virtual void dump(std::ostream
& sout
) = 0;
96 /* used for sanity checking of vselector */
97 virtual BlueFSVolumeSelector
* clone_empty() const { return nullptr; }
98 virtual bool compare(BlueFSVolumeSelector
* other
) { return true; };
101 struct bluefs_shared_alloc_context_t
{
102 bool need_init
= false;
103 Allocator
* a
= nullptr;
104 uint64_t alloc_unit
= 0;
106 std::atomic
<uint64_t> bluefs_used
= 0;
108 void set(Allocator
* _a
, uint64_t _au
) {
123 static constexpr unsigned MAX_BDEV
= 5;
124 static constexpr unsigned BDEV_WAL
= 0;
125 static constexpr unsigned BDEV_DB
= 1;
126 static constexpr unsigned BDEV_SLOW
= 2;
127 static constexpr unsigned BDEV_NEWWAL
= 3;
128 static constexpr unsigned BDEV_NEWDB
= 4;
136 struct File
: public RefCountedObject
{
137 MEMPOOL_CLASS_HELPERS();
139 bluefs_fnode_t fnode
;
145 boost::intrusive::list_member_hook
<> dirty_item
;
147 std::atomic_int num_readers
, num_writers
;
148 std::atomic_int num_reading
;
150 void* vselector_hint
= nullptr;
151 /* lock protects fnode and other the parts that can be modified during read & write operations.
152 Does not protect values that are fixed
153 Does not need to be taken when doing one-time operations:
154 _replay, device_migrate_to_existing, device_migrate_to_new */
155 ceph::mutex lock
= ceph::make_mutex("BlueFS::File::lock");
158 FRIEND_MAKE_REF(File
);
169 vselector_hint(nullptr)
172 ceph_assert(num_readers
.load() == 0);
173 ceph_assert(num_writers
.load() == 0);
174 ceph_assert(num_reading
.load() == 0);
175 ceph_assert(!locked
);
178 using FileRef
= ceph::ref_t
<File
>;
180 typedef boost::intrusive::list
<
182 boost::intrusive::member_hook
<
184 boost::intrusive::list_member_hook
<>,
185 &File::dirty_item
> > dirty_file_list_t
;
187 struct Dir
: public RefCountedObject
{
188 MEMPOOL_CLASS_HELPERS();
190 mempool::bluefs::map
<std::string
, FileRef
, std::less
<>> file_map
;
193 FRIEND_MAKE_REF(Dir
);
196 using DirRef
= ceph::ref_t
<Dir
>;
199 MEMPOOL_CLASS_HELPERS();
202 uint64_t pos
= 0; ///< start offset for buffer
204 ceph::buffer::list buffer
; ///< new data to write (at end of file)
205 ceph::buffer::list tail_block
; ///< existing partial block at end of file, if any
207 unsigned get_buffer_length() const {
208 return buffer
.length();
210 ceph::bufferlist
flush_buffer(
213 const unsigned length
,
214 const bluefs_super_t
& super
);
215 ceph::buffer::list::page_aligned_appender buffer_appender
; //< for const char* only
217 int writer_type
= 0; ///< WRITER_*
218 int write_hint
= WRITE_LIFE_NOT_SET
;
220 ceph::mutex lock
= ceph::make_mutex("BlueFS::FileWriter::lock");
221 std::array
<IOContext
*,MAX_BDEV
> iocv
; ///< for each bdev
222 std::array
<bool, MAX_BDEV
> dirty_devs
;
224 FileWriter(FileRef f
)
225 : file(std::move(f
)),
226 buffer_appender(buffer
.get_page_aligned_appender(
227 g_conf()->bluefs_alloc_size
/ CEPH_PAGE_SIZE
)) {
230 dirty_devs
.fill(false);
231 if (file
->fnode
.ino
== 1) {
232 write_hint
= WRITE_LIFE_MEDIUM
;
235 // NOTE: caller must call BlueFS::close_writer()
240 // note: BlueRocksEnv uses this append exclusively, so it's safe
241 // to use buffer_appender exclusively here (e.g., its notion of
242 // offset will remain accurate).
243 void append(const char *buf
, size_t len
) {
244 uint64_t l0
= get_buffer_length();
245 ceph_assert(l0
+ len
<= std::numeric_limits
<unsigned>::max());
246 buffer_appender
.append(buf
, len
);
249 void append(const std::byte
*buf
, size_t len
) {
250 // allow callers to use byte type instead of char* as we simply pass byte array
251 append((const char*)buf
, len
);
254 // note: used internally only, for ino 1 or 0.
255 void append(ceph::buffer::list
& bl
) {
256 uint64_t l0
= get_buffer_length();
257 ceph_assert(l0
+ bl
.length() <= std::numeric_limits
<unsigned>::max());
258 buffer
.claim_append(bl
);
261 void append_zero(size_t len
) {
262 uint64_t l0
= get_buffer_length();
263 ceph_assert(l0
+ len
<= std::numeric_limits
<unsigned>::max());
264 buffer_appender
.append_zero(len
);
267 uint64_t get_effective_write_pos() {
268 return pos
+ buffer
.length();
272 struct FileReaderBuffer
{
273 MEMPOOL_CLASS_HELPERS();
275 uint64_t bl_off
= 0; ///< prefetch buffer logical offset
276 ceph::buffer::list bl
; ///< prefetch buffer
277 uint64_t pos
= 0; ///< current logical offset
278 uint64_t max_prefetch
; ///< max allowed prefetch
280 explicit FileReaderBuffer(uint64_t mpf
)
281 : max_prefetch(mpf
) {}
283 uint64_t get_buf_end() const {
284 return bl_off
+ bl
.length();
286 uint64_t get_buf_remaining(uint64_t p
) const {
287 if (p
>= bl_off
&& p
< bl_off
+ bl
.length())
288 return bl_off
+ bl
.length() - p
;
292 void skip(size_t n
) {
296 // For the sake of simplicity, we invalidate completed rather than
297 // for the provided extent
298 void invalidate_cache(uint64_t offset
, uint64_t length
) {
299 if (offset
>= bl_off
&& offset
< get_buf_end()) {
307 MEMPOOL_CLASS_HELPERS();
310 FileReaderBuffer buf
;
312 bool ignore_eof
; ///< used when reading our log file
314 ceph::shared_mutex lock
{
315 ceph::make_shared_mutex(std::string(), false, false, false)
319 FileReader(FileRef f
, uint64_t mpf
, bool rand
, bool ie
)
332 MEMPOOL_CLASS_HELPERS();
335 explicit FileLock(FileRef f
) : file(std::move(f
)) {}
339 PerfCounters
*logger
= nullptr;
341 uint64_t max_bytes
[MAX_BDEV
] = {0};
342 uint64_t max_bytes_pcounters
[MAX_BDEV
] = {
343 l_bluefs_max_bytes_wal
,
344 l_bluefs_max_bytes_db
,
345 l_bluefs_max_bytes_slow
,
346 l_bluefs_max_bytes_wal
,
347 l_bluefs_max_bytes_db
,
352 ceph::mutex lock
= ceph::make_mutex("BlueFS::nodes.lock");
353 mempool::bluefs::map
<std::string
, DirRef
, std::less
<>> dir_map
; ///< dirname -> Dir
354 mempool::bluefs::unordered_map
<uint64_t, FileRef
> file_map
; ///< ino -> File
357 bluefs_super_t super
; ///< latest superblock (as last written)
358 uint64_t ino_last
= 0; ///< last assigned ino (this one is in use)
361 ceph::mutex lock
= ceph::make_mutex("BlueFS::log.lock");
362 uint64_t seq_live
= 1; //seq that log is currently writing to; mirrors dirty.seq_live
363 FileWriter
*writer
= 0;
364 bluefs_transaction_t t
;
368 ceph::mutex lock
= ceph::make_mutex("BlueFS::dirty.lock");
369 uint64_t seq_stable
= 0; //seq that is now stable on disk
370 uint64_t seq_live
= 1; //seq that is ongoing and dirty files will be written to
371 // map of dirty files, files of same dirty_seq are grouped into list.
372 std::map
<uint64_t, dirty_file_list_t
> files
;
373 std::vector
<interval_set
<uint64_t>> pending_release
; ///< extents to release
374 // TODO: it should be examined what makes pending_release immune to
375 // eras in a way similar to dirty_files. Hints:
376 // 1) we have actually only 2 eras: log_seq and log_seq+1
377 // 2) we usually not remove extents from files. And when we do, we force log-syncing.
380 ceph::condition_variable log_cond
; ///< used for state control between log flush / log compaction
381 std::atomic
<bool> log_is_compacting
{false}; ///< signals that bluefs log is already ongoing compaction
382 std::atomic
<bool> log_forbidden_to_expand
{false}; ///< used to signal that async compaction is in state
383 /// that prohibits expansion of bluefs log
385 * There are up to 3 block devices:
387 * BDEV_DB db/ - the primary db device
388 * BDEV_WAL db.wal/ - a small, fast device, specifically for the WAL
389 * BDEV_SLOW db.slow/ - a big, slow device, to spill over to as BDEV_DB fills
391 std::vector
<BlockDevice
*> bdev
; ///< block devices we can use
392 std::vector
<IOContext
*> ioc
; ///< IOContexts for bdevs
393 std::vector
<uint64_t> block_reserved
; ///< starting reserve extent per device
394 std::vector
<Allocator
*> alloc
; ///< allocators for bdevs
395 std::vector
<uint64_t> alloc_size
; ///< alloc size for each device
397 //std::vector<interval_set<uint64_t>> block_unused_too_granular;
399 BlockDevice::aio_callback_t discard_cb
[3]; //discard callbacks for each dev
401 std::unique_ptr
<BlueFSVolumeSelector
> vselector
;
403 bluefs_shared_alloc_context_t
* shared_alloc
= nullptr;
404 unsigned shared_alloc_id
= unsigned(-1);
405 inline bool is_shared_alloc(unsigned id
) const {
406 return id
== shared_alloc_id
;
408 std::atomic
<int64_t> cooldown_deadline
= 0;
411 SocketHook
* asok_hook
= nullptr;
412 // used to trigger zeros into read (debug / verify)
413 std::atomic
<uint64_t> inject_read_zeros
{0};
416 void _shutdown_logger();
417 void _update_logger_stats();
422 ///< pad ceph::buffer::list to max(block size, pad_size) w/ zeros
423 void _pad_bl(ceph::buffer::list
& bl
, uint64_t pad_size
= 0);
425 uint64_t _get_used(unsigned id
) const;
426 uint64_t _get_total(unsigned id
) const;
429 FileRef
_get_file(uint64_t ino
);
430 void _drop_link_D(FileRef f
);
432 unsigned _get_slow_device_id() {
433 return bdev
[BDEV_SLOW
] ? BDEV_SLOW
: BDEV_DB
;
435 const char* get_device_name(unsigned id
);
436 int _allocate(uint8_t bdev
, uint64_t len
,
438 bluefs_fnode_t
* node
,
439 size_t alloc_attempts
= 0,
440 bool permit_dev_fallback
= true);
442 /* signal replay log to include h->file in nearest log flush */
443 int _signal_dirty_to_log_D(FileWriter
*h
);
444 int _flush_range_F(FileWriter
*h
, uint64_t offset
, uint64_t length
);
445 int _flush_data(FileWriter
*h
, uint64_t offset
, uint64_t length
, bool buffered
);
446 int _flush_F(FileWriter
*h
, bool force
, bool *flushed
= nullptr);
447 uint64_t _flush_special(FileWriter
*h
);
448 int _fsync(FileWriter
*h
);
451 void _claim_completed_aios(FileWriter
*h
, std::list
<aio_t
> *ls
);
452 void _wait_for_aio(FileWriter
*h
); // safe to call without a lock
455 int64_t _maybe_extend_log();
457 uint64_t _log_advance_seq();
458 void _consume_dirty(uint64_t seq
);
459 void _clear_dirty_set_stable_D(uint64_t seq_stable
);
460 void _release_pending_allocations(std::vector
<interval_set
<uint64_t>>& to_release
);
462 void _flush_and_sync_log_core(int64_t available_runway
);
463 int _flush_and_sync_log_jump_D(uint64_t jump_to
,
464 int64_t available_runway
);
465 int _flush_and_sync_log_LD(uint64_t want_seq
= 0);
467 uint64_t _estimate_transaction_size(bluefs_transaction_t
* t
);
468 uint64_t _make_initial_transaction(uint64_t start_seq
,
469 bluefs_fnode_t
& fnode
,
470 uint64_t expected_final_size
,
472 uint64_t _estimate_log_size_N();
473 bool _should_start_compact_log_L_N();
481 void _compact_log_dump_metadata_NF(uint64_t start_seq
,
482 bluefs_transaction_t
*t
,
484 uint64_t capture_before_seq
);
486 void _compact_log_sync_LNF_LD();
487 void _compact_log_async_LD_LNF_D();
489 void _rewrite_log_and_layout_sync_LNF_LD(bool permit_dev_fallback
,
494 std::optional
<bluefs_layout_t
> layout
);
496 //void _aio_finish(void *priv);
498 void _flush_bdev(FileWriter
*h
, bool check_mutex_locked
= true);
499 void _flush_bdev(); // this is safe to call without a lock
500 void _flush_bdev(std::array
<bool, MAX_BDEV
>& dirty_bdevs
); // this is safe to call without a lock
502 int _preallocate(FileRef f
, uint64_t off
, uint64_t len
);
503 int _truncate(FileWriter
*h
, uint64_t off
);
506 FileReader
*h
, ///< [in] read from here
507 uint64_t offset
, ///< [in] offset
508 size_t len
, ///< [in] this many bytes
509 ceph::buffer::list
*outbl
, ///< [out] optional: reference the result here
510 char *out
); ///< [out] optional: or copy it here
511 int64_t _read_random(
512 FileReader
*h
, ///< [in] read from here
513 uint64_t offset
, ///< [in] offset
514 uint64_t len
, ///< [in] this many bytes
515 char *out
); ///< [out] optional: or copy it here
518 int _write_super(int dev
);
519 int _check_allocations(const bluefs_fnode_t
& fnode
,
520 boost::dynamic_bitset
<uint64_t>* used_blocks
,
521 bool is_alloc
, //true when allocating, false when deallocating
522 const char* op_name
);
523 int _verify_alloc_granularity(
524 __u8 id
, uint64_t offset
, uint64_t length
,
527 int _replay(bool noop
, bool to_stdout
= false); ///< replay journal
529 FileWriter
*_create_writer(FileRef f
);
530 void _drain_writer(FileWriter
*h
);
531 void _close_writer(FileWriter
*h
);
533 // always put the super in the second 4k block. FIXME should this be
534 // block size independent?
535 unsigned get_super_offset() {
538 unsigned get_super_length() {
541 void _maybe_check_vselector_LNF() {
542 if (cct
->_conf
->bluefs_check_volume_selector_often
) {
543 _check_vselector_LNF();
547 BlueFS(CephContext
* cct
);
550 // the super is always stored on bdev 0
551 int mkfs(uuid_d osd_uuid
, const bluefs_layout_t
& layout
);
553 int maybe_verify_layout(const bluefs_layout_t
& layout
) const;
554 void umount(bool avoid_compact
= false);
555 int prepare_new_device(int id
, const bluefs_layout_t
& layout
);
559 void collect_metadata(std::map
<std::string
,std::string
> *pm
, unsigned skip_bdev_id
);
560 void get_devices(std::set
<std::string
> *ls
);
561 uint64_t get_alloc_size(int id
) {
562 return alloc_size
[id
];
566 int device_migrate_to_new(
568 const std::set
<int>& devs_source
,
570 const bluefs_layout_t
& layout
);
571 int device_migrate_to_existing(
573 const std::set
<int>& devs_source
,
575 const bluefs_layout_t
& layout
);
578 uint64_t get_total(unsigned id
);
579 uint64_t get_free(unsigned id
);
580 uint64_t get_used(unsigned id
);
581 void dump_perf_counters(ceph::Formatter
*f
);
583 void dump_block_extents(std::ostream
& out
);
585 /// get current extents that we own for given block device
586 void foreach_block_extents(
588 std::function
<void(uint64_t, uint32_t)> cb
);
591 std::string_view dir
,
592 std::string_view file
,
597 std::string_view dir
,
598 std::string_view file
,
600 bool random
= false);
602 // data added after last fsync() is lost
603 void close_writer(FileWriter
*h
);
605 int rename(std::string_view old_dir
, std::string_view old_file
,
606 std::string_view new_dir
, std::string_view new_file
);
608 int readdir(std::string_view dirname
, std::vector
<std::string
> *ls
);
610 int unlink(std::string_view dirname
, std::string_view filename
);
611 int mkdir(std::string_view dirname
);
612 int rmdir(std::string_view dirname
);
613 bool wal_is_rotational();
614 bool db_is_rotational();
616 bool dir_exists(std::string_view dirname
);
617 int stat(std::string_view dirname
, std::string_view filename
,
618 uint64_t *size
, utime_t
*mtime
);
620 int lock_file(std::string_view dirname
, std::string_view filename
, FileLock
**p
);
621 int unlock_file(FileLock
*l
);
625 /// sync any uncommitted state to disk
626 void sync_metadata(bool avoid_compact
);
628 void set_volume_selector(BlueFSVolumeSelector
* s
) {
631 void dump_volume_selector(std::ostream
& sout
) {
632 vselector
->dump(sout
);
634 void get_vselector_paths(const std::string
& base
,
635 BlueFSVolumeSelector::paths
& res
) const {
636 return vselector
->get_paths(base
, res
);
639 int add_block_device(unsigned bdev
, const std::string
& path
, bool trim
,
641 bluefs_shared_alloc_context_t
* _shared_alloc
= nullptr);
642 bool bdev_support_label(unsigned id
);
643 uint64_t get_block_device_size(unsigned bdev
) const;
645 // handler for discard event
646 void handle_discard(unsigned dev
, interval_set
<uint64_t>& to_release
);
648 void flush(FileWriter
*h
, bool force
= false);
650 void append_try_flush(FileWriter
*h
, const char* buf
, size_t len
);
651 void flush_range(FileWriter
*h
, uint64_t offset
, uint64_t length
);
652 int fsync(FileWriter
*h
);
653 int64_t read(FileReader
*h
, uint64_t offset
, size_t len
,
654 ceph::buffer::list
*outbl
, char *out
) {
655 // no need to hold the global lock here; we only touch h and
656 // h->file, and read vs write or delete is already protected (via
657 // atomics and asserts).
658 return _read(h
, offset
, len
, outbl
, out
);
660 int64_t read_random(FileReader
*h
, uint64_t offset
, size_t len
,
662 // no need to hold the global lock here; we only touch h and
663 // h->file, and read vs write or delete is already protected (via
664 // atomics and asserts).
665 return _read_random(h
, offset
, len
, out
);
667 void invalidate_cache(FileRef f
, uint64_t offset
, uint64_t len
);
668 int preallocate(FileRef f
, uint64_t offset
, uint64_t len
);
669 int truncate(FileWriter
*h
, uint64_t offset
);
671 size_t probe_alloc_avail(int dev
, uint64_t alloc_size
);
673 /// test purpose methods
674 const PerfCounters
* get_perf_counters() const {
677 uint64_t debug_get_dirty_seq(FileWriter
*h
);
678 bool debug_get_is_dev_dirty(FileWriter
*h
, uint8_t dev
);
681 // Wrappers for BlockDevice::read(...) and BlockDevice::read_random(...)
682 // They are used for checking if read values are all 0, and reread if so.
683 int _read_and_check(uint8_t ndev
, uint64_t off
, uint64_t len
,
684 ceph::buffer::list
*pbl
, IOContext
*ioc
, bool buffered
);
685 int _read_random_and_check(uint8_t ndev
, uint64_t off
, uint64_t len
, char *buf
, bool buffered
);
687 int _bdev_read(uint8_t ndev
, uint64_t off
, uint64_t len
,
688 ceph::buffer::list
* pbl
, IOContext
* ioc
, bool buffered
);
689 int _bdev_read_random(uint8_t ndev
, uint64_t off
, uint64_t len
, char* buf
, bool buffered
);
691 /// test and compact log, if necessary
692 void _maybe_compact_log_LNF_NF_LD_D();
693 int _do_replay_recovery_read(FileReader
*log
,
698 void _check_vselector_LNF();
701 class OriginalVolumeSelector
: public BlueFSVolumeSelector
{
707 OriginalVolumeSelector(
710 uint64_t _slow_total
)
711 : wal_total(_wal_total
), db_total(_db_total
), slow_total(_slow_total
) {}
713 void* get_hint_for_log() const override
;
714 void* get_hint_by_dir(std::string_view dirname
) const override
;
716 void add_usage(void* hint
, const bluefs_fnode_t
& fnode
) override
{
720 void sub_usage(void* hint
, const bluefs_fnode_t
& fnode
) override
{
724 void add_usage(void* hint
, uint64_t fsize
) override
{
728 void sub_usage(void* hint
, uint64_t fsize
) override
{
733 uint8_t select_prefer_bdev(void* hint
) override
;
734 void get_paths(const std::string
& base
, paths
& res
) const override
;
735 void dump(std::ostream
& sout
) override
;
738 class FitToFastVolumeSelector
: public OriginalVolumeSelector
{
740 FitToFastVolumeSelector(
743 uint64_t _slow_total
)
744 : OriginalVolumeSelector(_wal_total
, _db_total
, _slow_total
) {}
746 void get_paths(const std::string
& base
, paths
& res
) const override
;
749 * Directional graph of locks.
750 * Vertices - Locks. Edges (directed) - locking progression.
751 * Edge A->B exist if last taken lock was A and next taken lock is B.
753 * Row represents last lock taken.
754 * Column represents next lock taken.
756 * > | W | L | N | D | F
757 * -------------|---|---|---|---|---
758 * FileWriter W | | > | > | > | >
759 * log L | | > | > | >
764 * Claim: Deadlock is possible IFF graph contains cycles.