Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 148 additions & 2 deletions include/bitcoin/database/impl/memory/mmap_staging.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,22 @@ bool CLASS::stage_() NOEXCEPT
return true;
}

// Managed heads load released: the file holds the content and pages
// restore to anonymous per segment on first write, so load contributes
// no residency (an eager head population is the store's largest
// memory transient, the full head set at open).
if (!staged_ && dirty_)
{
if (!lazy_install_())
{
teardown_<Column>(error::mmap_failure);
return false;
}

loaded_.store(true);
return true;
}

// Commit anonymous pages above the settle boundary page floor.
const auto settled = page_floor(to_width<Column>(settled_.load()));

Expand Down Expand Up @@ -451,6 +467,133 @@ bool CLASS::stage_() NOEXCEPT
return true;
}

// Install the managed head lazily over its current logical span: a released
// (read-only file) full-page prefix restored to anonymous per segment on
// first write, an anonymous populated tail, and clean page tracking sized to
// a replacement reservation. Caller holds exclusive remap (or is loading).
TEMPLATE
bool CLASS::lazy_install_() NOEXCEPT
{
using namespace system;
const auto rows = logical_.load();
const auto logical = to_width<zero>(rows);

// Replace any standing reservation (sized as stage_).
if (!is_null(memory_map_[zero]))
mmap_unreserve(memory_map_[zero], reserved_[zero]);

const auto reserved = page_ceiling(to_width<zero>(
to_reservation(to_provision())));
const auto base = mmap_reserve(reserved);
if (base == MAP_FAILED)
{
set_first_code(error::mmap_failure);
return false;
}

memory_map_[zero] = pointer_cast<uint8_t>(base);
reserved_[zero] = reserved;

// Rebuild page tracking for the new reservation (value-initialized).
const auto pages = ceilinged_divide(reserved, page_);
words_ = ceilinged_divide(pages, page_bound);
dirty_ = std::make_unique<dirty_bitmaps>(words_);
intent_ = std::make_unique<dirty_bitmaps>(words_);
released_ = std::make_unique<dirty_bitmaps>(words_);
sweep_ = std::make_unique<uint64_t[]>(words_);
writers_.store(zero);

// Released file prefix (full pages below logical).
const auto floor = page_floor(logical);
if (is_nonzero(floor) && (mmap_settle(memory_map_[zero], floor,
opened_[zero], zero) == fail))
{
set_first_code(error::mmap_failure);
return false;
}

// Commit the remainder to the commitment target and populate the tail.
const auto target = std::max(page_ceiling(to_width<zero>(
capacity_.load())), page_ceiling(logical));
if ((target > floor) && (mmap_commit(std::next(memory_map_[zero], floor),
target - floor, headroom_) == fail))
{
set_first_code(error::mmap_failure);
return false;
}

if ((logical > floor) && !pread_all(opened_[zero],
std::next(memory_map_[zero], floor), logical - floor, floor))
{
set_first_code(error::fsync_failure);
return false;
}

// Declare the prefix released and engage the restore protocol.
const auto flags = floor / page_;
for (size_t word{}; word < ceilinged_divide(flags, page_bound); ++word)
{
const auto first = word * page_bound;
released_[word].store((flags >= (first + page_bound)) ?
bit_all<uint64_t> : unmask_right<uint64_t>(flags - first));
}

engaged_.store(true);

// Attribute the anonymous span for diagnostics (smaps decomposition).
mmap_name(std::next(memory_map_[zero], floor), reserved - floor,
filenames_.front().filename().string().c_str());

return true;
}

// Head-creation fill: the fill is file content, written sequentially to the
// file (page cache, no mapping involvement) and installed released, so
// creation contributes no memory residency (an in-memory backfill of the
// head set is the store's largest allocation transient).
TEMPLATE
size_t CLASS::allocate_filled_(size_t count, uint8_t backfill) NOEXCEPT
{
BC_ASSERT(!staged_ && dirty_);
std::unique_lock field_lock(field_mutex_);
std::unique_lock map_lock(remap_mutex_);

using namespace system;
const auto start = logical_.load();
if (!loaded_.load() || fault_.load() || is_add_overflow(start, count))
return storage::eof;

// Provision the file physically (disk full detected here).
const auto end = start + count;
if (!resize_<zero>(end))
return storage::eof;

// Write the fill.
const auto from = to_width<zero>(start);
const auto to = to_width<zero>(end);
std::vector<uint8_t> chunk(std::min(to - from, release_chunk), backfill);
for (auto at = from; at < to; )
{
const auto size = std::min(to - at, chunk.size());
if (!pwrite_all(opened_[zero], chunk.data(), size, at))
{
set_first_code(error::fsync_failure);
return storage::eof;
}

at += size;
}

logical_.store(end);
file_.store(std::max(file_.load(), end));
capacity_.store(std::max(capacity_.load(), end));
if (!lazy_install_())
return storage::eof;

check_invariants_();
return start;
}

// Commit failure results in unmapped when final (the default); a non-final
// refusal (in-reservation commit, replacement reservation, or replacement
// commit) returns false with the standing mapping untouched, so the caller
Expand Down Expand Up @@ -1198,8 +1341,11 @@ void CLASS::head_run_() NOEXCEPT

// Quiet is assured here (hot scarcity skipped above, and idle
// draining implies sixty still seconds).
if (engaged && !release_pages_())
continue;
// Lazy head load engages restore on all platforms; the release
// sweep remains a darwin response (see head_release).
if constexpr (head_release)
if (engaged && !release_pages_())
continue;
}

transferred = top;
Expand Down
27 changes: 27 additions & 0 deletions include/bitcoin/database/impl/memory/mmap_storage.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,33 @@ size_t CLASS::allocate(size_t count) NOEXCEPT
}
}

// Backfilled allocation (head creation). A managed head writes the fill to
// its file and maps it released, so the fill is never memory-resident; a
// native mapping fills through memory (file-backed, kernel writeback).
TEMPLATE
size_t CLASS::allocate(size_t count, uint8_t backfill) NOEXCEPT
{
#if defined(MANAGE_STAGING)
if (!staged_ && dirty_)
return allocate_filled_(count, backfill);
#endif

const auto start = allocate(count);
if (start == storage::eof)
return start;

const auto offset = to_width<zero>(start);
const auto size = to_width<zero>(count);
const auto ptr = get(offset);
if (!ptr)
return storage::eof;

prepare(offset, size);
std::fill_n(ptr.data(), size, backfill);
mark(offset, size);
return start;
}

} // namespace database
} // namespace libbitcoin

Expand Down
18 changes: 3 additions & 15 deletions include/bitcoin/database/impl/primitives/hashhead.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -57,24 +57,12 @@ bool CLASS::create() NOEXCEPT
if (is_nonzero(file_.size()))
return false;

const auto allocation = size();
const auto start = file_.allocate(allocation);

// Guards addition overflow in file_.get (start must be valid).
if (start == storage::eof)
return false;

const auto ptr = file_.get(start);
if (!ptr)
// The fill is file content: a managed head writes it to the file and
// maps it released, so creation contributes no memory residency.
if (file_.allocate(size(), system::bit_all<uint8_t>) == storage::eof)
return false;

BC_ASSERT_MSG(verify(), "unexpected head size");

// std::memset/fill_n have identical performance (on win32).
////std::memset(ptr.data(), system::bit_all<uint8_t>, allocation);
file_.prepare(start, allocation);
std::fill_n(ptr.data(), allocation, system::bit_all<uint8_t>);
file_.mark(start, allocation);
return set_body_count(zero);
}

Expand Down
4 changes: 4 additions & 0 deletions include/bitcoin/database/memory/interfaces/storage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ class storage
/// Increase logical by specified rows/bytes, return row of first (or eof).
virtual size_t allocate(size_t count) NOEXCEPT = 0;

/// Allocate backfilled rows/bytes, avoiding memory residency of the fill
/// where the backend supports it (head creation path).
virtual size_t allocate(size_t count, uint8_t backfill) NOEXCEPT = 0;

/// Report element write completion of count rows/bytes at offset.
virtual void complete(size_t offset, size_t count) NOEXCEPT = 0;

Expand Down
6 changes: 6 additions & 0 deletions include/bitcoin/database/memory/mmap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ class mmap
/// Increase logical by specified rows/bytes, return row of first (or eof).
size_t allocate(size_t count) NOEXCEPT override;

/// Allocate backfilled rows/bytes; managed heads write the fill to the
/// file and map it released (no memory residency of the fill).
size_t allocate(size_t count, uint8_t backfill) NOEXCEPT override;

/// Report element write completion of count rows/bytes at offset.
void complete(size_t offset, size_t count) NOEXCEPT override;

Expand Down Expand Up @@ -327,6 +331,8 @@ class mmap

// staging utilities, not thread safe (claim_ is lock-free thread safe).
struct extent;
size_t allocate_filled_(size_t count, uint8_t backfill) NOEXCEPT;
bool lazy_install_() NOEXCEPT;
size_t record_(size_t count) NOEXCEPT;
bool claim_(extent& record, size_t count) NOEXCEPT;
void maintain_() NOEXCEPT;
Expand Down
3 changes: 3 additions & 0 deletions include/bitcoin/database/memory/mstage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
/// Reserve inaccessible anonymous address space (MAP_FAILED on failure).
void* mmap_reserve(size_t size) NOEXCEPT;

/// Release reserved address space (with any mappings installed within it).
int mmap_unreserve(void* address, size_t size) NOEXCEPT;

/// Commit reserved pages as readable/writable anonymous memory.
int mmap_commit(void* address, size_t size, size_t headroom) NOEXCEPT;

Expand Down
5 changes: 5 additions & 0 deletions src/memory/mstage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ void* mmap_reserve(size_t size) NOEXCEPT
-1, 0);
}

int mmap_unreserve(void* address, size_t size) NOEXCEPT
{
return ::munmap(address, size);
}

#if defined(HAVE_APPLE)

// Darwin admits every anonymous ask (exhaustion arrives at first touch), so
Expand Down
22 changes: 22 additions & 0 deletions test/mocks/chunk_storage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,28 @@ class chunk_storages
return link;
}

size_t allocate(size_t count, uint8_t backfill) NOEXCEPT override
{
std::unique_lock field_lock(field_mutex_);
if (system::is_add_overflow<size_t>(logical_, count))
return chunk_storages::eof;

const auto end = logical_ + count;
for (size_t column{}; column < columns; ++column)
if (to_capacity(end, column) > at(column).max_size())
return chunk_storages::eof;

std::unique_lock map_lock(map_mutex_);
const auto link = logical_;

logical_ = end;
for (size_t column{}; column < columns; ++column)
if (to_capacity(logical_, column) > at(column).size())
at(column).resize(to_capacity(logical_, column), backfill);

return link;
}

void complete(size_t, size_t) NOEXCEPT override
{
}
Expand Down
Loading