// Default no-op — base layers without per-extent ref caches.
}
+void
+BeesExtentLayer::repair_refs(const BeesExtent &, const vector<BeesRef> &)
+{
+ // Default no-op — layers that do not cache ref lists.
+}
+
Fd
BeesExtentLayer::open_ref(const BeesRef &ref) const
{
if (!bti) {
return;
}
+ // A ref is a (root, inum, offset) tuple, and the EXTENT_DATA item at
+ // that file offset is whatever owns the range *now*. If the range has
+ // been redirected to another extent since the ref list was built --
+ // which a dedupe does to its dst on every successful operation -- the
+ // item found here belongs to a different extent entirely. Adopting
+ // its geometry silently reattributes another extent's ram_bytes,
+ // extent_offset and data to this one: block_map_fetch then preads the
+ // wrong file range and publishes hashes for data this extent does not
+ // contain, and the mismatch only surfaces later as a "NO Dedup!" when
+ // the kernel byte-compares what the planner paired. Leave the cached
+ // fields unset instead. Callers already skip refs whose geometry is
+ // not determined, so a foreign ref reads as absent rather than wrong.
+ {
+ auto owner_sp = node->m_extent.lock();
+ auto *owner = owner_sp ? dynamic_cast<BeesBtrfsExtentNode *>(owner_sp.get()) : nullptr;
+ if (owner && bti.file_extent_bytenr() != owner->m_bytenr) {
+ BEESCOUNT(ref_foreign);
+ BEESLOGC(DEBUG, Plan, "extent_data_fetch: ref root " << ref.root()
+ << " inum " << ref.inum() << " offset " << to_hex(ref.offset())
+ << " now belongs to bytenr " << to_hex(bti.file_extent_bytenr())
+ << ", not " << to_hex(owner->m_bytenr) << "; leaving undetermined");
+ return;
+ }
+ }
+
{
unique_lock<recursive_mutex> lock(node->m_lazy_mutex);
node->m_prealloc = (bti.file_extent_type() == BTRFS_FILE_EXTENT_PREALLOC);
}
}
+void
+BeesBtrfsExtentLayer::repair_refs(const BeesExtent &extent, const vector<BeesRef> &donors)
+{
+ const auto bytenr_o = extent.bytenr_opt();
+ if (!bytenr_o || donors.empty()) {
+ return;
+ }
+ auto *node = dynamic_cast<BeesBtrfsExtentNode *>(extent.extent_sp().get());
+ if (!node) {
+ return;
+ }
+
+ // Refetch first so the repair is applied on top of whatever btrfs is
+ // willing to report right now.
+ auto fetched = atomic_load(&node->m_refs);
+ if (!fetched) {
+ fetched = refs_fetch(*node);
+ }
+
+ // LOGICAL_INO answers from the commit root whenever it cannot attach
+ // to a running transaction (fs/btrfs/backref.c: btrfs_attach_transaction
+ // returns -ENOENT, find_parent_nodes then sets search_commit_root and
+ // skips delayed refs). A dedupe performed since the last commit is
+ // therefore invisible to a refetch that happens to land in a lull, and
+ // nothing in the result distinguishes that case from a genuinely
+ // short ref list. We know what we just created, so do not ask: the
+ // donors are the dst refs the dedupe redirected onto this extent.
+ // Adding one that btrfs already reported is harmless (skipped as a
+ // duplicate); adding one belonging to a different src is harmless too,
+ // because extent_data_fetch leaves a foreign ref undetermined and
+ // every caller skips it.
+ auto merged = *fetched;
+ size_t added = 0;
+ for (const auto &donor : donors) {
+ const auto root = donor.root();
+ const auto inum = donor.inum();
+ const auto offset = donor.offset();
+ bool present = false;
+ for (const auto &have : merged) {
+ if (have.root() == root && have.inum() == inum &&
+ have.offset() == offset) {
+ present = true;
+ break;
+ }
+ }
+ if (present) {
+ continue;
+ }
+ auto ref_node = make_shared<BeesBtrfsRefNode>(root, inum, offset);
+ ref_node->m_bytenr = *bytenr_o;
+ ref_node->m_extent = extent.extent_sp();
+ merged.push_back(BeesRef(static_pointer_cast<BeesBaseRefNode>(ref_node)));
+ ++added;
+ }
+ if (!added) {
+ return;
+ }
+ BEESCOUNTADD(ref_repaired, added);
+ atomic_store(&node->m_refs,
+ make_shared<const vector<BeesRef>>(std::move(merged)));
+}
+
void
BeesBtrfsExtentLayer::invalidate()
{
// Propagate so the base layer drops its per-inode ref cache too.
m_parent->invalidate_refs(extent);
}
+
+void
+BeesOverlayLayer::repair_refs(const BeesExtent &extent, const vector<BeesRef> &donors)
+{
+ // The overlay caches no ref lists of its own; the list lives on the
+ // node. Forward so the base layer performs the merge.
+ m_parent->repair_refs(extent, donors);
+}
/// returns without doing anything. Default: no-op.
virtual void invalidate_refs(const BeesExtent &extent);
+ /// Restore refs to @p extent that a just-completed dedupe created
+ /// but a refetch may not report. @p donors are the dst-side refs
+ /// the dedupe redirected onto this extent; each is added to the
+ /// extent's ref list if a (root, inum, offset) match is not already
+ /// present. Needed because LOGICAL_INO falls back to the commit
+ /// root when it cannot attach to a running transaction, making
+ /// uncommitted dedupes invisible to it. Default: no-op.
+ virtual void repair_refs(const BeesExtent &extent, const vector<BeesRef> &donors);
+
// -- Transitional: BeesContext access --
/// Return the underlying BeesContext. Only valid on layers
/// Leaves m_extent_cache entries in place because the extent
/// identity (bytenr + phys_size) is still valid after dedupe.
void invalidate_refs(const BeesExtent &extent) override;
+ void repair_refs(const BeesExtent &extent, const vector<BeesRef> &donors) override;
BeesContext &ctx() const override;
/// removes the extent's own neighbor entries plus back-edges from
/// other extents' neighbor sets. Propagates to the parent.
void invalidate_refs(const BeesExtent &extent) override;
+ void repair_refs(const BeesExtent &extent, const vector<BeesRef> &donors) override;
private:
shared_ptr<BeesExtentLayer> m_parent;
// - dst extents lose refs (may disappear entirely). Their
// bytenr-indexed hash-table cells are now stale — erase
// proactively so later discovery does not chase a removed
- // extent. Then drop in-memory block_map + hashes. Identity
- // and refs stay cached because they remain valid for the
- // current ref-count and for the post-removal verification
- // cycle. Hash tables are not a layered cache, so no layer
- // call is required for the dst path.
+ // extent. Then drop in-memory block_map + hashes and evict
+ // the extent from every cache layer, so nothing can reach it
+ // by bytenr again. A post-dedupe dst must take no further
+ // part in dedupe: its refs name file ranges that now belong
+ // to src, and resolving one yields another extent's geometry
+ // and another file's data. The BeesExtent object survives
+ // eviction -- the verification tracker holds it, ref list
+ // intact for the delayed removal check, block map dropped so
+ // the wait costs no memory. Those same refs are then the
+ // donor list for repairing the src side.
// - src extents gain refs to the deduped ranges. Block contents
// (and therefore hashes) are unchanged, but every ref-derived
// cache entry is now stale: overlay ref map + neighbor edges,
}
}
// Drop node-local cached hashes (m_full_block_map +
- // m_zero_eliminated). No layer call: hashes are not
- // layered.
+ // m_zero_eliminated). Hashes are not layered, so this
+ // needs no layer call of its own.
dst.invalidate_hashes();
+ // Evict from the overlay and, through it, the shared base:
+ // drops the m_extent_cache entry and every m_inode_ref_cache
+ // entry naming this bytenr. Deliberately not
+ // invalidate_refs(): the node keeps its own ref list for the
+ // verification cycle, now its only holder.
+ layer.invalidate(dst_bytenr);
m_filter_cache->invalidate(dst_bytenr);
};
+ // Dst refs redirected by this plan, used to repair the src lists.
+ vector<BeesRef> donor_refs;
auto invalidate_src_refs = [&](uint64_t src_bytenr) {
// Pull the extent from the layer cache — identity is
// still valid, so this is usually a free hit.
// that hits the overlay first sees no stale state.
layer.invalidate_refs(src);
src.invalidate_refs();
+ // Refetching alone is not enough. LOGICAL_INO answers
+ // from the commit root whenever it cannot attach to a
+ // running transaction, so a dedupe that has not reached
+ // a commit can be invisible to the refetch, and the
+ // result does not say which case it was. Hand it the
+ // dst refs the dedupe just redirected here so the list
+ // is complete either way.
+ layer.repair_refs(src, donor_refs);
}
m_filter_cache->invalidate(src_bytenr);
};
+ // Collect donors before evicting the dst extents: eviction leaves
+ // the node's ref list alone, but the list is what we need and
+ // reading it first keeps the dependency obvious.
+ for (const auto &dst_plan : plan.m_dst_plans) {
+ const auto dst_refs = dst_plan.m_dst.refs(&layer);
+ if (dst_refs) {
+ donor_refs.insert(donor_refs.end(),
+ dst_refs->begin(), dst_refs->end());
+ }
+ }
for (const auto &dst_plan : plan.m_dst_plans) {
invalidate_dst_hashes(dst_plan.m_dst);
}
void BeesExtentLayer::invalidate(uint64_t) {}
void BeesExtentLayer::invalidate() {}
void BeesExtentLayer::invalidate_refs(const BeesExtent &) {}
+void BeesExtentLayer::repair_refs(const BeesExtent &, const vector<BeesRef> &) {}
Fd BeesExtentLayer::open_ref(const BeesRef &) const { return {}; }
// bees-trace.o references exception_check() from bees-lib.o; stub it here