const auto logical_size = *node.m_logical_size;
const auto block_count = ranged_cast<size_t>(
(logical_size + BLOCK_SIZE_SUMS - 1) / BLOCK_SIZE_SUMS);
+ BEESTRACE("logical_size " << to_hex(logical_size)
+ << " block_count " << block_count);
// Per-block intermediate state — coalesced into regions at end.
struct BlockState {
}
}
+ // Summarize pass 1's structural classification so an exception in
+ // pass 2 shows the extent shape the failing pread was derived from.
+ size_t data_blocks = 0;
+ size_t prealloc_blocks = 0;
+ for (size_t i = 0; i < block_count; ++i) {
+ if (has_data[i]) {
+ ++data_blocks;
+ }
+ if (blocks[i].state == BeesTreeBlockState::prealloc) {
+ ++prealloc_blocks;
+ }
+ }
+ BEESTRACE("refs " << refs_sp->size()
+ << " data_blocks " << data_blocks
+ << " prealloc_blocks " << prealloc_blocks
+ << " of " << block_count);
+
// Pre-compute total bytes pass 2 will pread, for the BEESNOTE
// progress meter below. Mirrors pass 2's run-extraction logic
// so the total stays accurate across multiple data ranges
const auto range_end = min<uint64_t>(
static_cast<uint64_t>(j) * BLOCK_SIZE_SUMS, logical_size);
const auto range_len = range_end - range_begin;
+ BEESTRACE("data run blocks [" << i << ".." << j << ") bytes "
+ << to_hex(range_begin) << ".." << to_hex(range_end));
for (uint64_t chunk_off = 0; chunk_off < range_len;
chunk_off += BATCH_SIZE) {
BEESNOTE("block_map_fetch pread bytenr "
const auto want_off = ranged_cast<uint64_t>(logical_offset);
const auto want_end = checked_add_u64(want_off, len,
"BeesBtrfsExtentNode::pread end");
+ BEESTRACE("bytenr " << to_hex(m_bytenr) << " want "
+ << to_hex(want_off) << ".." << to_hex(want_end));
auto refs_sp = atomic_load(&m_refs);
if (!refs_sp) {
[](const CoveringRef &a, const CoveringRef &b) {
return a.ext_begin < b.ext_begin;
});
+ BEESTRACE("refs " << refs_sp->size()
+ << " candidates " << candidates.size());
auto *dst = static_cast<uint8_t *>(buf);
uint64_t cursor = want_off;
const auto sectorsize = BeesContext::s_sectorsize;
THROW_CHECK1(runtime_error, sectorsize, sectorsize > 0);
+ BEESTRACE("cursor " << cursor);
while (cursor < want_end) {
// Find the first usable ref in extent_offset order that
// covers `cursor` and opens successfully.
const CoveringRef *picked = nullptr;
Fd fd;
+ size_t covering_count = 0;
+ size_t open_fail_count = 0;
+ vector<const CoveringRef *> open_fail_sample;
for (const auto &cand : candidates) {
if (cand.ext_begin > cursor) {
break;
if (cand.ext_begin + cand.ref_bytes <= cursor) {
continue;
}
+ ++covering_count;
Fd candidate_fd = cand.refp->open(*layerp);
if (!candidate_fd) {
+ BEESCOUNT(pread_ref_open_fail);
+ ++open_fail_count;
+ if (open_fail_sample.size() < BEES_PREAD_REF_LOG_MAX) {
+ open_fail_sample.push_back(&cand);
+ }
continue;
}
picked = &cand;
break;
}
if (!picked) {
+ // Two distinct failure classes: refs structurally cover
+ // `cursor` but none would open (the file or subvol went
+ // away between the ref fetch and the read), vs no ref
+ // covering `cursor` at all (the caller's coverage model
+ // disagrees with the candidate list). Blocks with no
+ // covering ref in the first place never reach pread —
+ // block_map_fetch pass 1 leaves them unreachable.
+ if (covering_count) {
+ BEESCOUNT(pread_no_ref_open);
+ } else {
+ BEESCOUNT(pread_no_ref_gap);
+ }
+ for (const auto *candp : open_fail_sample) {
+ BEESLOGDEBUG("pread bytenr " << to_hex(m_bytenr)
+ << " open failed: " << *candp->refp);
+ }
+ if (open_fail_count > open_fail_sample.size()) {
+ BEESLOGDEBUG("pread bytenr " << to_hex(m_bytenr)
+ << " ... and "
+ << open_fail_count - open_fail_sample.size()
+ << " more open failures not logged");
+ }
+ if (!covering_count) {
+ // Log the candidates bracketing the gap so the
+ // coverage disagreement is visible in the log.
+ const CoveringRef *prevp = nullptr;
+ const CoveringRef *nextp = nullptr;
+ for (const auto &cand : candidates) {
+ if (cand.ext_begin <= cursor) {
+ prevp = &cand;
+ } else {
+ nextp = &cand;
+ break;
+ }
+ }
+ if (prevp) {
+ BEESLOGDEBUG("pread bytenr " << to_hex(m_bytenr)
+ << " nearest ref below gap: " << *prevp->refp);
+ }
+ if (nextp) {
+ BEESLOGDEBUG("pread bytenr " << to_hex(m_bytenr)
+ << " nearest ref above gap: " << *nextp->refp);
+ }
+ }
THROW_ERROR(runtime_error,
"BeesBtrfsExtentNode::pread: bytenr "
<< to_hex(m_bytenr)
- << " no usable ref covers extent offset " << cursor);
+ << " no usable ref covers extent offset " << cursor
+ << " (covering " << covering_count
+ << " open_failed " << open_fail_count
+ << " candidates " << candidates.size()
+ << " refs " << refs_sp->size() << ")");
}
const auto file_begin = picked->refp->offset()