]> git.hungrycats.org Git - bees/commitdiff
fdcache: split BeesFdCache into its own file and own the tmpfile registry
authorZygo Blaxell <bees@furryterror.org>
Wed, 22 Apr 2026 15:37:39 +0000 (11:37 -0400)
committerZygo Blaxell <bees@furryterror.org>
Sun, 30 Aug 2026 23:48:46 +0000 (19:48 -0400)
Move BeesFdCache out of bees.h / bees-context.cc into a pair of dedicated
bees-fdcache.{cc,h} files.  This lets future standalone binaries link
the cache without pulling in bees.cc (which contains main()) and lets
the extent-layer and related future callers include a small header rather
than all of bees.h.

Along with the file split, move the temp-file registry (insert_tmpfile,
erase_tmpfile, m_tmpfiles map, mutex, and the early-exit path in
open_root_ino) from BeesRoots to BeesFdCache.  The registry has always
been consulted by BeesFdCache's lookup path; keeping the storage on
BeesRoots required BeesFdCache to call back into BeesRoots for every
inode open.  Owning it directly simplifies the call graph and scopes
the data next to the one class that reads it.

BeesFdCache uses PIMPL (class Impl) to hold its storage; the public
header exposes only the interface.  This avoids the circular include
that would otherwise arise from holding map<BeesFileId, Fd> directly
in a class whose declaration must be visible before bees.h defines
BeesFileId.

BeesTempFile switches its registry callback from m_roots to m_fd_cache
to match the new ownership.

No behavior change: the cache policy, lookup order (tmpfile then LRU),
and eviction are all preserved.

Assisted-by: Claude-Code:claude-opus-4-7
Signed-off-by: Zygo Blaxell <bees@furryterror.org>
src/Makefile
src/bees-context.cc
src/bees-fdcache.cc [new file with mode: 0644]
src/bees-fdcache.h [new file with mode: 0644]
src/bees-roots.cc
src/bees-tempfile.cc
src/bees-tempfile.h
src/bees.h

index 00c55f35ee1d9553513f4be82bc7a92070661fda..c656cdd620bf07c7881ab075d398ebfc43c3b4a4 100644 (file)
@@ -13,6 +13,7 @@ BEES_OBJS = \
        bees-config-v1.o \
        bees-config.o \
        bees-context.o \
+       bees-fdcache.o \
        bees-hash.o \
        bees-lib.o \
        bees-log.o \
index 462417abb843e5308773aec7d76b1924dea7b777..55293796714d0104ed963cee2fb9ebe9f1246796 100644 (file)
 using namespace crucible;
 using namespace std;
 
-BeesFdCache::BeesFdCache(shared_ptr<BeesContext> ctx) :
-       m_ctx(ctx)
-{
-       m_root_cache.func([&](uint64_t root) -> Fd {
-               Timer open_timer;
-               auto rv = m_ctx->roots()->open_root_nocache(root);
-               BEESCOUNTADD(open_root_ms, open_timer.age() * 1000);
-               return rv;
-       });
-       m_root_cache.max_size(BEES_ROOT_FD_CACHE_SIZE);
-       m_file_cache.func([&](uint64_t root, uint64_t ino) -> Fd {
-               Timer open_timer;
-               auto rv = m_ctx->roots()->open_root_ino_nocache(root, ino);
-               BEESCOUNTADD(open_ino_ms, open_timer.age() * 1000);
-               return rv;
-       });
-       m_file_cache.max_size(BEES_FILE_FD_CACHE_SIZE);
-}
-
-void
-BeesFdCache::clear()
-{
-       BEESLOGDEBUG("Clearing root FD cache with size " << m_root_cache.size() << " to enable subvol delete");
-       BEESNOTE("Clearing root FD cache with size " << m_root_cache.size());
-       m_root_cache.clear();
-       BEESCOUNT(root_clear);
-
-       BEESLOGDEBUG("Clearing open FD cache with size " << m_file_cache.size() << " to enable file delete");
-       BEESNOTE("Clearing open FD cache with size " << m_file_cache.size());
-       m_file_cache.clear();
-       BEESCOUNT(open_clear);
-}
-
-Fd
-BeesFdCache::open_root(uint64_t root)
-{
-       return m_root_cache(root);
-}
-
-Fd
-BeesFdCache::open_root_ino(uint64_t root, uint64_t ino)
-{
-       return m_file_cache(root, ino);
-}
-
 void
 BeesContext::set_reporter(shared_ptr<BeesReporter> reporter)
 {
diff --git a/src/bees-fdcache.cc b/src/bees-fdcache.cc
new file mode 100644 (file)
index 0000000..7810b24
--- /dev/null
@@ -0,0 +1,97 @@
+#include "bees-fdcache.h"
+
+#include "bees.h"
+
+#include "crucible/string.h"
+
+#include <map>
+
+using namespace crucible;
+using namespace std;
+
+class BeesFdCache::Impl {
+public:
+       LRUCache<Fd, uint64_t>                  m_root_cache;
+       LRUCache<Fd, uint64_t, uint64_t>        m_file_cache;
+       Timer                                   m_root_cache_timer;
+       Timer                                   m_file_cache_timer;
+       mutex                                   m_tmpfiles_mutex;
+       map<BeesFileId, Fd>                     m_tmpfiles;
+};
+
+BeesFdCache::BeesFdCache(shared_ptr<BeesContext> ctx) :
+       m_ctx(ctx),
+       m_impl(make_unique<Impl>())
+{
+       m_impl->m_root_cache.func([this](uint64_t root) -> Fd {
+               Timer open_timer;
+               auto rv = m_ctx->roots()->open_root_nocache(root);
+               BEESCOUNTADD(open_root_ms, open_timer.age() * 1000);
+               return rv;
+       });
+       m_impl->m_root_cache.max_size(BEES_ROOT_FD_CACHE_SIZE);
+       m_impl->m_file_cache.func([this](uint64_t root, uint64_t ino) -> Fd {
+               Timer open_timer;
+               auto rv = m_ctx->roots()->open_root_ino_nocache(root, ino);
+               BEESCOUNTADD(open_ino_ms, open_timer.age() * 1000);
+               return rv;
+       });
+       m_impl->m_file_cache.max_size(BEES_FILE_FD_CACHE_SIZE);
+}
+
+BeesFdCache::~BeesFdCache() = default;
+
+void
+BeesFdCache::clear()
+{
+       BEESLOGDEBUG("Clearing root FD cache with size " << m_impl->m_root_cache.size() << " to enable subvol delete");
+       BEESNOTE("Clearing root FD cache with size " << m_impl->m_root_cache.size());
+       m_impl->m_root_cache.clear();
+       BEESCOUNT(root_clear);
+
+       BEESLOGDEBUG("Clearing open FD cache with size " << m_impl->m_file_cache.size() << " to enable file delete");
+       BEESNOTE("Clearing open FD cache with size " << m_impl->m_file_cache.size());
+       m_impl->m_file_cache.clear();
+       BEESCOUNT(open_clear);
+}
+
+Fd
+BeesFdCache::open_root(uint64_t root)
+{
+       return m_impl->m_root_cache(root);
+}
+
+Fd
+BeesFdCache::open_root_ino(uint64_t root, uint64_t ino)
+{
+       // The temp-file registry wins: O_TMPFILE files have no name, so we
+       // must return the registered Fd rather than trying to re-open by path.
+       {
+               unique_lock<mutex> lock(m_impl->m_tmpfiles_mutex);
+               auto found = m_impl->m_tmpfiles.find(BeesFileId(root, ino));
+               if (found != m_impl->m_tmpfiles.end()) {
+                       BEESCOUNT(open_tmpfile);
+                       return found->second;
+               }
+       }
+       return m_impl->m_file_cache(root, ino);
+}
+
+void
+BeesFdCache::insert_tmpfile(Fd fd)
+{
+       BeesFileId fid(fd);
+       unique_lock<mutex> lock(m_impl->m_tmpfiles_mutex);
+       auto rv = m_impl->m_tmpfiles.insert(make_pair(fid, fd));
+       THROW_CHECK1(runtime_error, fd, rv.second);
+}
+
+void
+BeesFdCache::erase_tmpfile(Fd fd)
+{
+       BeesFileId fid(fd);
+       unique_lock<mutex> lock(m_impl->m_tmpfiles_mutex);
+       auto found = m_impl->m_tmpfiles.find(fid);
+       THROW_CHECK1(runtime_error, fd, found != m_impl->m_tmpfiles.end());
+       m_impl->m_tmpfiles.erase(found);
+}
diff --git a/src/bees-fdcache.h b/src/bees-fdcache.h
new file mode 100644 (file)
index 0000000..77c8055
--- /dev/null
@@ -0,0 +1,45 @@
+#pragma once
+
+/// @file bees-fdcache.h
+/// LRU-cached Fds for subvolume roots and inodes, plus the temp-file registry.
+///
+/// LOGICAL_INO returns (root, inode, offset) tuples; resolving a tuple to an
+/// open Fd requires two open() calls and a tree search.  BeesFdCache keeps
+/// recently-used Fds in two LRU caches sized by BEES_ROOT_FD_CACHE_SIZE and
+/// BEES_FILE_FD_CACHE_SIZE.
+///
+/// BeesFdCache also owns the temp-file registry.  Temp files are created
+/// with O_TMPFILE and have no filesystem name, so open_root_ino() cannot
+/// find them by ordinary path resolution.  Callers register temp files via
+/// insert_tmpfile() so open_root_ino() can return them by (root, inode).
+
+#include "bees-fwd.h"
+
+#include "crucible/fd.h"
+
+#include <cstdint>
+#include <memory>
+
+using namespace crucible;
+using namespace std;
+
+class BeesFdCache {
+       shared_ptr<BeesContext> m_ctx;
+       class Impl;
+       unique_ptr<Impl> m_impl;
+
+public:
+       BeesFdCache(shared_ptr<BeesContext> ctx);
+       ~BeesFdCache();
+       /// Cached-or-freshly-opened Fd for subvolume root @p root.
+       Fd open_root(uint64_t root);
+       /// Cached-or-freshly-opened Fd for inode @p ino in root @p root.
+       /// Consults the temp-file registry first.
+       Fd open_root_ino(uint64_t root, uint64_t ino);
+       /// Register an O_TMPFILE Fd so open_root_ino() can find it by (root, inode).
+       void insert_tmpfile(Fd fd);
+       /// Deregister a temp file when it is returned to the pool.
+       void erase_tmpfile(Fd fd);
+       /// Evict all cached Fds.
+       void clear();
+};
index af3ff4949990e845f908ab0f4d42d8674a2a25b3..4d5fbdaeb79d3814ba51dd9df84cdbf231b8c248 100644 (file)
@@ -2445,16 +2445,6 @@ BeesRoots::open_root_ino_nocache(uint64_t root, uint64_t ino)
 {
        BEESTRACE("opening root " << root << " ino " << ino);
 
-       // Check the tmpfiles map first
-       {
-               unique_lock<mutex> lock(m_tmpfiles_mutex);
-               auto found = m_tmpfiles.find(BeesFileId(root, ino));
-               if (found != m_tmpfiles.end()) {
-                       BEESCOUNT(open_tmpfile);
-                       return found->second;
-               }
-       }
-
        Fd root_fd = open_root(root);
        if (!root_fd) {
                BEESCOUNT(open_no_root);
@@ -2586,25 +2576,6 @@ BeesRoots::open_root_ino(uint64_t root, uint64_t ino)
        return m_ctx->fd_cache()->open_root_ino(root, ino);
 }
 
-void
-BeesRoots::insert_tmpfile(Fd fd)
-{
-       BeesFileId fid(fd);
-       unique_lock<mutex> lock(m_tmpfiles_mutex);
-       auto rv = m_tmpfiles.insert(make_pair(fid, fd));
-       THROW_CHECK1(runtime_error, fd, rv.second);
-}
-
-void
-BeesRoots::erase_tmpfile(Fd fd)
-{
-       BeesFileId fid(fd);
-       unique_lock<mutex> lock(m_tmpfiles_mutex);
-       auto found = m_tmpfiles.find(fid);
-       THROW_CHECK1(runtime_error, fd, found != m_tmpfiles.end());
-       m_tmpfiles.erase(found);
-}
-
 BeesCrawl::BeesCrawl(shared_ptr<BeesContext> ctx, BeesCrawlState initial_state) :
        m_ctx(ctx),
        m_state(initial_state),
index 424401e8779598f20f469c1675d41f32f681b584..58d1544a9355b0d13e393e9453f59d405497c0e8 100644 (file)
@@ -65,7 +65,7 @@ BeesTempFile::~BeesTempFile()
        BEESLOGDEBUG("destroying temporary file " << this << " in " << m_ctx->root_path() << " fd " << name_fd(m_fd));
 
        // Remove this file from open_root_ino lookup table
-       m_roots->erase_tmpfile(m_fd);
+       m_fd_cache->erase_tmpfile(m_fd);
 
        // Remove from blacklist
        m_ctx->blacklist_erase(BeesFileId(m_fd));
@@ -102,7 +102,7 @@ workaround_iflags_kernel_regressions(const int fd, const uint32_t orig_flags, co
 
 BeesTempFile::BeesTempFile(shared_ptr<BeesContext> ctx, const BeesTempFileConfig &config) :
        m_ctx(ctx),
-       m_roots(ctx->roots()),
+       m_fd_cache(ctx->fd_cache()),
        m_end_offset(0)
 {
        BEESLOGDEBUG("creating temporary file " << this << " in " << m_ctx->root_path());
@@ -210,7 +210,7 @@ BeesTempFile::BeesTempFile(shared_ptr<BeesContext> ctx, const BeesTempFileConfig
        m_ctx->blacklist_insert(BeesFileId(m_fd));
 
        // Add this file to open_root_ino lookup table
-       m_roots->insert_tmpfile(m_fd);
+       m_fd_cache->insert_tmpfile(m_fd);
 
        // Count time spent here
        BEESCOUNTADD(tmp_create_ms, create_timer.age() * 1000);
index d598a17efceddede1145091e90921064423c6e29..10aadf2bec23b2f2d36c70fe5611930611f50c98 100644 (file)
@@ -34,7 +34,7 @@ struct BeesTempFileConfig {
  */
 class BeesTempFile {
        shared_ptr<BeesContext> m_ctx;
-       shared_ptr<BeesRoots>   m_roots;
+       shared_ptr<BeesFdCache> m_fd_cache;
        Fd                      m_fd;
        /// Current logical end of used data within the temp file.
        off_t                   m_end_offset;
index 4159dc5e276cc35d87309de9bf903cabdf3537eb..0f8fa9c67d803fa6592ca53469bf5ba912e635bd 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "bees-fwd.h"
 
+#include "bees-fdcache.h"
 #include "bees-lib.h"
 #include "bees-log.h"
 #include "bees-reporter.h"
@@ -952,9 +953,6 @@ class BeesRoots : public enable_shared_from_this<BeesRoots> {
 
        vector<shared_ptr<BeesScanMode>>        m_scanners;  ///< Active scan-mode strategy objects.
 
-       mutex                                   m_tmpfiles_mutex;
-       map<BeesFileId, Fd>                     m_tmpfiles;  ///< Temp files in use by active dedup operations.
-
        mutex                                   m_stop_mutex;
        condition_variable                      m_stop_condvar;
        bool                                    m_stop_requested = false;
@@ -1014,14 +1012,6 @@ public:
        /// Block until background threads have stopped.
        void stop_wait();
 
-       /// Register a temp file in the open_root_ino() lookup table.
-       /// Temp files have no names, so this registry is the only way to resolve
-       /// a BeesFileId back to a temp file fd.
-       void insert_tmpfile(Fd fd);
-       /// Deregister a temp file from the open_root_ino() lookup table
-       /// when it is returned to the pool.
-       void erase_tmpfile(Fd fd);
-
        /// Open (with caching) the root directory for subvolume @p root.
        Fd open_root(uint64_t root);
        /// Open (with caching) the file at inode @p ino in subvolume @p root.
@@ -1177,32 +1167,6 @@ public:
 friend ostream & operator<<(ostream &os, const BeesRangePair &brp);
 };
 
-/**
- * LRU cache for btrfs root and inode file descriptors.
- *
- * Opening a file in btrfs requires opening the subvolume root first, then
- * opening the inode within it.  Both operations are expensive (ioctl + open),
- * so BeesFdCache keeps recently-used FDs in two separate LRU caches.
- * Cache capacity is controlled by BEES_ROOT_FD_CACHE_SIZE and
- * BEES_FILE_FD_CACHE_SIZE.
- */
-class BeesFdCache {
-       shared_ptr<BeesContext>                 m_ctx;
-       LRUCache<Fd, uint64_t>                  m_root_cache;         ///< Cache keyed by root ID.
-       LRUCache<Fd, uint64_t, uint64_t>        m_file_cache;         ///< Cache keyed by (root, inode).
-       Timer                                   m_root_cache_timer;   ///< Tracks time since last cache clear.
-       Timer                                   m_file_cache_timer;
-
-public:
-       BeesFdCache(shared_ptr<BeesContext> ctx);
-       /// Return a cached (or freshly opened) Fd for the subvolume root @p root.
-       Fd open_root(uint64_t root);
-       /// Return a cached (or freshly opened) Fd for inode @p ino in root @p root.
-       Fd open_root_ino(uint64_t root, uint64_t ino);
-       /// Evict all cached file descriptors.
-       void clear();
-};
-
 /// Result of a LOGICAL_INO ioctl lookup for one physical block address.
 struct BeesResolveAddrResult {
        BeesResolveAddrResult();