From 608cd007646a69092ef6432199abf56c04aa7f17 Mon Sep 17 00:00:00 2001 From: Antonio SJ Musumeci Date: Wed, 13 May 2026 11:30:39 -0500 Subject: [PATCH] Use atomic open file reference counts - Use relaxed increment under the map bucket lock when duplicating file handles. - Use acq_rel decrement when releasing and checking for final references. - Load moved ref_count with relaxed ordering during map insertion. --- src/fuse_open.cpp | 11 ++++++++++- src/fuse_release.cpp | 14 ++++++++++++-- src/state.hpp | 6 +++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/fuse_open.cpp b/src/fuse_open.cpp index 6bdc3e70..1d247079 100644 --- a/src/fuse_open.cpp +++ b/src/fuse_open.cpp @@ -367,10 +367,19 @@ _open(const fuse_req_ctx_t *ctx_, // The ref increment keeps fuse_release.cpp from erasing and // freeing the canonical entry while we duplicate its fd. + // + // memory_order_relaxed is sufficient for the increment. The + // concurrent_flat_map bucket lock held inside visit() already + // provides the happens-before relationship with other mutators + // of the same key (the release() erase_if() callback and other + // open() visitors.) The lock's release/acquire semantics + // publish the ref_count update to subsequent observers of the + // same entry, so we only need atomicity from the fetch_add + // itself. of.visit(ctx_->nodeid, [&](auto &v_) { - v_.second.ref_count++; + v_.second.ref_count.fetch_add(1,std::memory_order_relaxed); fi = v_.second.fi; backing_id = v_.second.backing_id; }); diff --git a/src/fuse_release.cpp b/src/fuse_release.cpp index 17bbb0cd..e8ddb1e6 100644 --- a/src/fuse_release.cpp +++ b/src/fuse_release.cpp @@ -81,11 +81,21 @@ FUSE::release(cu64 nodeid_, FileInfo *canonical_fi = nullptr; FileInfo *fh_fi_to_free = nullptr; + // memory_order_acq_rel on the decrement gives us the canonical + // refcount-publication pattern: + // - release: prior writes to the OpenFile by this thread are + // visible to whichever thread observes the final 0. + // - acquire: the thread that drops the last ref acquires every + // prior ref holder's writes before tearing down the entry. + // The concurrent_flat_map bucket lock already serializes concurrent + // visitors of this key so the acq_rel is good for any observer that + // might examine ref_count outside a visit(). of.erase_if(nodeid_, [&](auto &v_) { - v_.second.ref_count--; - if(v_.second.ref_count > 0) + const int prev = + v_.second.ref_count.fetch_sub(1,std::memory_order_acq_rel); + if(prev > 1) { if(fi_ != v_.second.fi) fh_fi_to_free = fi_; diff --git a/src/state.hpp b/src/state.hpp index f5e5867c..cabcbbd5 100644 --- a/src/state.hpp +++ b/src/state.hpp @@ -103,8 +103,12 @@ public: { } + // Move constructor runs during initial insertion into the + // concurrent_flat_map before the new entry is reachable by the + // other threads. There is no concurrent observer of the moved + // value at this point so relaxed load is sufficient. OpenFile(OpenFile &&o_) noexcept - : ref_count(o_.ref_count.load()), + : ref_count(o_.ref_count.load(std::memory_order_relaxed)), backing_id(o_.backing_id), fi(o_.fi) {