* `block_bytes`: Number of data bytes read.
* `block_hash`: Number of block hashes computed.
+ * `block_map_ref_unopenable`: A data ref was excluded from block map coverage because its file could not be opened — typically a ref in a deleted file or snapshot that lingers in the ref list until the last open fd closes or the subvol cleaner runs. Blocks covered only by such refs are classified as unreachable.
* `block_ms`: Total time reading data blocks.
* `block_read`: Number of data blocks read.
* `block_zero`: Number of data blocks read with zero contents (i.e. candidates for replacement with a hole).
* `pread_fail`: A `pread()` call failed with an error other than `EINTR`.
* `pread_ms`: Total wallclock time spent in the `pread()` loop for a chunk.
* `pread_no_ref_gap`: A read failed because no ref covered the extent offset at all — the caller's coverage model disagreed with the ref list. The read is abandoned with an exception.
- * `pread_no_ref_open`: A read failed because every ref covering the extent offset failed to open — typically the referencing files or subvols were deleted between the metadata fetch and the read. The read is abandoned with an exception.
+ * `pread_no_ref_open`: A read failed because every ref covering the extent offset failed to open. Block map generation excludes refs that fail to open (`block_map_ref_unopenable`) before reading, so this counter indicates a referencing file or subvol that disappeared while the block map was being built. The read is abandoned with an exception.
* `pread_ok`: A `pread()` call returned successfully.
* `pread_ref_open_fail`: A ref covering the current read offset failed to open. Not fatal by itself — another covering ref may open successfully.
* `pread_try`: A `pread()` call was attempted (including retries after `EINTR`).
eof_analysis_fetch(node);
// Pass 1 — walk refs to compute the prealloc mask and the union of
- // data-ref ranges. No I/O here: prealloc state is purely structural,
- // and we use the data-ref union to avoid invoking extent-level pread
- // over ranges that have only prealloc refs (which would have no
- // usable ref to read from).
+ // openable data-ref ranges. Prealloc state is purely structural.
+ // Data coverage additionally requires the ref to open: refs in
+ // deleted files or snapshots linger in the ref list until the last
+ // open fd closes or the subvol cleaner runs, and blocks covered
+ // only by such refs are unreachable, not readable. The data-ref
+ // union then keeps pass 2 from invoking extent-level pread over
+ // ranges with no usable ref to read from.
auto refs_sp = atomic_load(&node.m_refs);
if (!refs_sp) refs_sp = refs_fetch(node);
vector<bool> has_data(block_count, false);
}
}
} else {
- for (size_t i = first; i < min(last, block_count); ++i) {
+ // Skip the openability probe when the ref adds no new
+ // coverage, so heavily-overlapping ref clusters don't
+ // probe every referencing file.
+ const auto limit = min(last, block_count);
+ bool new_coverage = false;
+ for (size_t i = first; i < limit; ++i) {
+ if (!has_data[i]) {
+ new_coverage = true;
+ break;
+ }
+ }
+ if (!new_coverage) {
+ continue;
+ }
+ if (!ref.open(self)) {
+ BEESCOUNT(block_map_ref_unopenable);
+ continue;
+ }
+ for (size_t i = first; i < limit; ++i) {
has_data[i] = true;
}
}