Remove timed node removal

This commit is contained in:
Antonio SJ Musumeci 2026-05-05 16:01:55 -05:00 • committed by trapexit
parent 8117998205
commit f1034a3ad6
14 changed files with 176 additions and 257 deletions

View file

@ -15,6 +15,7 @@ function or have been replaced. **They should not be used.**
* **hard_remove**: Does nothing. * **hard_remove**: Does nothing.
* **kernel_cache**: Use [cache.files=full](cache.md). * **kernel_cache**: Use [cache.files=full](cache.md).
* **negative_entry**: Use [cache.negative-entry](cache.md). * **negative_entry**: Use [cache.negative-entry](cache.md).
* **remember**: Use `never-forget-nodes`.
* **noforget**: Use `never-forget-nodes`. * **noforget**: Use `never-forget-nodes`.
* **nonempty**: Does nothing. * **nonempty**: Does nothing.
* **splice_move**: Does nothing. * **splice_move**: Does nothing.

View file

@ -213,12 +213,11 @@ config file.
* **passthrough.max-stack-depth=INT**: Set to `1` another filesystem * **passthrough.max-stack-depth=INT**: Set to `1` another filesystem
can be stacked on mergerfs. Set to `2` to have mergerfs stacked over can be stacked on mergerfs. Set to `2` to have mergerfs stacked over
another filesystem. (default: 1) another filesystem. (default: 1)
* **remember-nodes=INT**: The number of seconds to keep the internal * **never-forget-nodes=BOOL**: Keep the internal representation of
representation of a file once the OS tells mergerfs it is no longer a file after the OS tells mergerfs it is no longer needed, until the
needed. Really only needed for [exporting mergerfs via node is unlinked, removed, or replaced. Really only needed for
NFS](../remote_filesystems.md) (default: 0) [exporting mergerfs via NFS](../remote_filesystems.md). Formerly
* **never-forget-nodes=BOOL**: Sets `remember-nodes` to `noforget`. (default: false)
infinity. Formerly `noforget`. (default: false)
* **allow-idmap=BOOL**: Enables idmap (identity mapping). (default: * **allow-idmap=BOOL**: Enables idmap (identity mapping). (default:
true) true)
* **debug**: Enable debugging information. At the moment limited to a * **debug**: Enable debugging information. At the moment limited to a

View file

@ -57,7 +57,7 @@ NFS export settings:
* `softreval`: NFS client will serve up cached data after `retrans` * `softreval`: NFS client will serve up cached data after `retrans`
attempts to revalidate the data. Helps with intermittent network attempts to revalidate the data. Helps with intermittent network
issues. issues.
* `timeo=150`: Timeout till retrying request. * `timeo=150`: Timeout till retrying request.
* `retrans=3`: Number of retrying a request. * `retrans=3`: Number of retrying a request.
* `rsize=131072`: Read size. * `rsize=131072`: Read size.
* `wsize=131072`: Write size. * `wsize=131072`: Write size.
@ -76,12 +76,11 @@ but if you run into problems it may be worth trying Samba/SMB.
**mergerfs settings:** **mergerfs settings:**
* `never-forget-nodes=true` or `remember-nodes=SECONDS` * `never-forget-nodes=true`
* `inodecalc=path-hash` * `inodecalc=path-hash`
* `lazy-umount-mountpoint=false` * `lazy-umount-mountpoint=false`
`never-forget-nodes=true` (or `remember-nodes` with a large value such `never-forget-nodes=true` is needed because NFS uses the
as `86400` for a full day) is needed because NFS uses the
`name_to_handle_at` and `open_by_handle_at` functions which allow a `name_to_handle_at` and `open_by_handle_at` functions which allow a
program to keep a reference to a file without technically having it program to keep a reference to a file without technically having it
open in the typical sense. The problem is that FUSE has no way to know open in the typical sense. The problem is that FUSE has no way to know
@ -89,8 +88,8 @@ that NFS has a handle that it will later use to open the file
again. As a result, it is possible for the kernel to tell mergerfs to again. As a result, it is possible for the kernel to tell mergerfs to
forget about the file node and should NFS ever ask for that node's forget about the file node and should NFS ever ask for that node's
details in the future it would have nothing to respond with. Keeping details in the future it would have nothing to respond with. Keeping
nodes around forever (or however long is set with `remember-nodes`) is nodes around until they are unlinked, removed, or replaced is not ideal
not ideal but at the moment the only way to manage the situation. but at the moment the only way to manage the situation.
`inodecalc=path-hash` is needed because NFS is sensitive to `inodecalc=path-hash` is needed because NFS is sensitive to
out-of-band changes. FUSE doesn't care if a file's inode value changes out-of-band changes. FUSE doesn't care if a file's inode value changes

View file

@ -12,7 +12,7 @@
depending on [fuse-msg-size](config/fuse-msg-size.md) depending on [fuse-msg-size](config/fuse-msg-size.md)
* buffers allocated temporarily for reading directories * buffers allocated temporarily for reading directories
* FUSE nodes * FUSE nodes
* never-forget-nodes=true nodes * never-forget-nodes=true retained nodes, until unlinked or removed
## Management ## Management

View file

@ -135,10 +135,6 @@ Config::Config()
_max_background(fuse_cfg.max_background), _max_background(fuse_cfg.max_background),
_mount(mountpoint), _mount(mountpoint),
_mountpoint(mountpoint), _mountpoint(mountpoint),
_never_forget_nodes(),
_noforget(),
_remember(fuse_cfg.remember_nodes),
_remember_nodes(fuse_cfg.remember_nodes),
_srcmounts(branches), _srcmounts(branches),
_threads(fuse_cfg.read_thread_count), _threads(fuse_cfg.read_thread_count),
_uid(fuse_cfg.uid), _uid(fuse_cfg.uid),
@ -147,13 +143,22 @@ Config::Config()
_initialized(false) _initialized(false)
{ {
allow_idmap.ro = _congestion_threshold.ro =
_max_background.ro =
_mount.ro =
_mountpoint.ro =
_never_forget_nodes.ro =
_noforget.ro =
_remember.ro =
_remember_nodes.ro =
_srcmounts.ro =
_version.ro =
allow_idmap.ro =
async_read.ro = async_read.ro =
branches_mount_timeout.ro = branches_mount_timeout.ro =
branches_mount_timeout_fail.ro = branches_mount_timeout_fail.ro =
cache_symlinks.ro = cache_symlinks.ro =
cache_writeback.ro = cache_writeback.ro =
_congestion_threshold.ro =
direct_io_allow_mmap.ro = direct_io_allow_mmap.ro =
export_support.ro = export_support.ro =
fsname.ro = fsname.ro =
@ -161,9 +166,6 @@ Config::Config()
handle_killpriv.ro = handle_killpriv.ro =
handle_killpriv_v2.ro = handle_killpriv_v2.ro =
kernel_permissions_check.ro = kernel_permissions_check.ro =
_max_background.ro =
_mount.ro =
_mountpoint.ro =
nullrw.ro = nullrw.ro =
pid.ro = pid.ro =
pin_threads.ro = pin_threads.ro =
@ -172,8 +174,6 @@ Config::Config()
process_thread_queue_depth.ro = process_thread_queue_depth.ro =
read_thread_count.ro = read_thread_count.ro =
scheduling_priority.ro = scheduling_priority.ro =
_srcmounts.ro =
_version.ro =
true; true;
_congestion_threshold.display = _congestion_threshold.display =
_gid.display = _gid.display =
@ -181,6 +181,7 @@ Config::Config()
_mount.display = _mount.display =
_noforget.display = _noforget.display =
_remember.display = _remember.display =
_remember_nodes.display =
_srcmounts.display = _srcmounts.display =
_threads.display = _threads.display =
_uid.display = _uid.display =

View file

@ -176,8 +176,8 @@ private:
TFSRef<fs::path> _mountpoint; TFSRef<fs::path> _mountpoint;
CfgNoforget _never_forget_nodes; CfgNoforget _never_forget_nodes;
CfgNoforget _noforget; CfgNoforget _noforget;
TFSRef<s64> _remember; CfgNoforget _remember;
TFSRef<s64> _remember_nodes; CfgNoforget _remember_nodes;
SrcMounts _srcmounts; SrcMounts _srcmounts;
TFSRef<int> _threads; TFSRef<int> _threads;
TFSRef<s64> _uid; TFSRef<s64> _uid;

View file

@ -32,18 +32,24 @@ public:
{ {
int rv; int rv;
bool b = false; bool b = false;
s64 i = 0;
rv = str::from(s_,&b); if(s_.empty())
if((b == true) || s_.empty())
{ {
fuse_cfg.remember_nodes = -1; b = true;
return 0; }
else if((rv = str::from(s_,&i)) == 0)
{
b = (i != 0);
}
else
{
rv = str::from(s_,&b);
if(rv)
return rv;
} }
if(rv) fuse_cfg.remember_nodes = b;
return rv;
fuse_cfg.remember_nodes = 0;
return 0; return 0;
} }
@ -51,7 +57,7 @@ public:
std::string std::string
to_string() const to_string() const
{ {
if(fuse_cfg.remember_nodes == -1) if(fuse_cfg.remember_nodes)
return "true"; return "true";
return "false"; return "false";
} }

View file

@ -100,8 +100,8 @@ Legend:
| Option | Integration tests | Unit tests | | Option | Integration tests | Unit tests |
|---|---|---| |---|---|---|
| `lazy-umount-mountpoint` | Live-upgrade remount scenario with stale previous mount | Startup control flow and error handling | | `lazy-umount-mountpoint` | Live-upgrade remount scenario with stale previous mount | Startup control flow and error handling |
| `remember-nodes` | NFS-like lookup stability over configured retention interval | Node cache retention/expiry logic | | `remember-nodes` | NFS-like lookup stability until unlink/remove/rename replacement | Boolean and numeric compatibility parsing |
| `noforget` | No node expiry over extended period | Infinite retention mode mapping | | `noforget` | Retained nodes released on unlink/remove/rename replacement | Alias mapping to retained-node mode |
| `allow-idmap` | UID/GID mapping behavior in idmapped mounts | Init flag propagation and compatibility checks | | `allow-idmap` | UID/GID mapping behavior in idmapped mounts | Init flag propagation and compatibility checks |
| `debug` | FUSE trace generation enabled/disabled | Debug option parse and logger toggling | | `debug` | FUSE trace generation enabled/disabled | Debug option parse and logger toggling |
| `log.file` | Trace output to stderr vs file path | Path parser and file sink setup | | `log.file` | Trace output to stderr vs file path | Path parser and file sink setup |

View file

@ -2730,6 +2730,61 @@ test_config_set_kv_form()
TEST_CHECK(val == "true"); TEST_CHECK(val == "true");
} }
void
test_config_remember_nodes_bool_numeric()
{
bool old_remember_nodes = fuse_cfg.remember_nodes;
Config cfg;
std::string val;
fuse_cfg.remember_nodes = false;
// remember-nodes accepts ints: 0 disables, non-zero enables
TEST_CHECK(cfg.set("remember-nodes", "0") == 0);
TEST_CHECK(cfg.get("remember-nodes", &val) == 0);
TEST_CHECK(val == "false");
TEST_CHECK(cfg.set("remember-nodes", "86400") == 0);
TEST_CHECK(cfg.get("remember-nodes", &val) == 0);
TEST_CHECK(val == "true");
TEST_CHECK(cfg.set("remember", "-1") == 0);
TEST_CHECK(cfg.get("remember", &val) == 0);
TEST_CHECK(val == "true");
// remember-nodes also accepts bool strings
TEST_CHECK(cfg.set("remember-nodes", "false") == 0);
TEST_CHECK(cfg.get("remember-nodes", &val) == 0);
TEST_CHECK(val == "false");
// never-forget-nodes is bool; it reflects the feature state
TEST_CHECK(cfg.set("never-forget-nodes", "true") == 0);
TEST_CHECK(cfg.get("never-forget-nodes", &val) == 0);
TEST_CHECK(val == "true");
// Querying remember-nodes after never-forget-nodes=true returns true
// (both alias the same fuse_cfg.remember_nodes)
TEST_CHECK(cfg.get("remember-nodes", &val) == 0);
TEST_CHECK(val == "true");
// Empty string treated as true
TEST_CHECK(cfg.set("noforget", "") == 0);
TEST_CHECK(cfg.get("noforget", &val) == 0);
TEST_CHECK(val == "true");
// remember-nodes also accepts empty string as true
fuse_cfg.remember_nodes = false;
TEST_CHECK(cfg.set("remember-nodes", "") == 0);
TEST_CHECK(cfg.get("remember-nodes", &val) == 0);
TEST_CHECK(val == "true");
// Invalid string rejected by remember-nodes
TEST_CHECK(cfg.set("remember-nodes", "notabool") == -EINVAL);
fuse_cfg.remember_nodes = old_remember_nodes;
}
void void
test_config_readonly_before_init() test_config_readonly_before_init()
{ {
@ -3683,6 +3738,7 @@ TEST_LIST =
{"config_set_underscore_normalization",test_config_set_underscore_normalization}, {"config_set_underscore_normalization",test_config_set_underscore_normalization},
{"config_get_underscore_normalization",test_config_get_underscore_normalization}, {"config_get_underscore_normalization",test_config_get_underscore_normalization},
{"config_set_kv_form",test_config_set_kv_form}, {"config_set_kv_form",test_config_set_kv_form},
{"config_remember_nodes_bool_numeric",test_config_remember_nodes_bool_numeric},
{"config_readonly_before_init",test_config_readonly_before_init}, {"config_readonly_before_init",test_config_readonly_before_init},
{"config_readonly_after_init",test_config_readonly_after_init}, {"config_readonly_after_init",test_config_readonly_after_init},
{"config_mutable_after_init",test_config_mutable_after_init}, {"config_mutable_after_init",test_config_mutable_after_init},

View file

@ -27,7 +27,8 @@ Because the high-level interface is path based, it is not possible to
delegate looking up by inode to the filesystem. delegate looking up by inode to the filesystem.
To work around this, currently a "noforget" option is provided, which To work around this, currently a "noforget" option is provided, which
makes the library remember nodes forever. This will make the NFS makes the library remember nodes until they are unlinked or otherwise
server happy, but also results in an ever growing memory footprint for removed. This will make the NFS server happy, but also results in a
the filesystem. For this reason if the filesystem is large (or the larger memory footprint for the filesystem. For this reason if the
memory is small), then this option is not recommended. filesystem is large (or the memory is small), then this option is not
recommended.

View file

@ -365,13 +365,12 @@ void fuse_populate_maintenance_thread(struct fuse *fuse);
/** /**
* Iterate over cache removing stale entries * Legacy cache cleanup entry point.
* use in conjunction with "-oremember"
* *
* NOTE: This is already done for the standard sessions * NOTE: This is already done for the standard sessions
* *
* @param fuse struct fuse pointer for fuse instance * @param fuse struct fuse pointer for fuse instance
* @return the number of seconds until the next cleanup * @return 0
*/ */
int fuse_clean_cache(struct fuse *fuse); int fuse_clean_cache(struct fuse *fuse);

View file

@ -25,7 +25,7 @@ struct fuse_cfg_t
s64 umask = FUSE_CFG_INVALID_UMASK; s64 umask = FUSE_CFG_INVALID_UMASK;
bool valid_umask() const; bool valid_umask() const;
s64 remember_nodes = 0; bool remember_nodes = false;
bool debug = false; bool debug = false;
std::shared_ptr<FILE> log_file() const; std::shared_ptr<FILE> log_file() const;

View file

@ -102,13 +102,6 @@ struct list_head
struct list_head *prev; struct list_head *prev;
}; };
typedef struct remembered_node_t remembered_node_t;
struct remembered_node_t
{
node_t *node;
time_t time;
};
typedef struct nodeid_gen_t nodeid_gen_t; typedef struct nodeid_gen_t nodeid_gen_t;
struct nodeid_gen_t struct nodeid_gen_t
{ {
@ -127,7 +120,6 @@ struct fuse
fuse_operations ops; fuse_operations ops;
struct lock_queue_element *lockq; struct lock_queue_element *lockq;
kvec_t(remembered_node_t) remembered_nodes;
}; };
#define TREELOCK_WRITE -1 #define TREELOCK_WRITE -1
@ -192,20 +184,6 @@ get_node(const uint64_t nodeid)
return NULL; return NULL;
} }
static
void
remove_remembered_node(node_t *node_)
{
for(size_t i = 0; i < kv_size(f.remembered_nodes); i++)
{
if(kv_A(f.remembered_nodes,i).node != node_)
continue;
kv_delete(f.remembered_nodes,i);
break;
}
}
static static
uint32_t uint32_t
stat_crc32b(const struct stat *st_) stat_crc32b(const struct stat *st_)
@ -230,31 +208,6 @@ stat_crc32b(const struct stat *st_)
return (crc ? crc : UINT32_MAX); return (crc ? crc : UINT32_MAX);
} }
#ifndef CLOCK_MONOTONIC
# define CLOCK_MONOTONIC CLOCK_REALTIME
#endif
static
time_t
current_time()
{
int rv;
struct timespec now;
static clockid_t clockid = CLOCK_MONOTONIC;
rv = clock_gettime(clockid,&now);
if((rv == -1) && (errno == EINVAL))
{
clockid = CLOCK_REALTIME;
rv = clock_gettime(clockid,&now);
}
if(rv == -1)
now.tv_sec = time(NULL);
return now.tv_sec;
}
static static
void void
free_node(node_t *node_) free_node(node_t *node_)
@ -560,10 +513,10 @@ hash_name(node_t *node,
static static
inline inline
int bool
remember_nodes() remember_nodes()
{ {
return (fuse_cfg.remember_nodes > 0); return fuse_cfg.remember_nodes;
} }
static static
@ -572,8 +525,6 @@ delete_node(node_t *node)
{ {
assert(node->treelock == 0); assert(node->treelock == 0);
unhash_name(node); unhash_name(node);
if(remember_nodes())
remove_remembered_node(node);
unhash_id(node); unhash_id(node);
node_free(node); node_free(node);
} }
@ -626,10 +577,45 @@ inc_nlookup(node_t *node)
node->nlookup++; node->nlookup++;
} }
static
void
remember_node(node_t *node)
{
if(node->remembered)
return;
inc_nlookup(node);
node->remembered = true;
}
static
void
unref_node_if_unused(node_t *node)
{
if((node->nlookup == 0) &&
(node->open_count == 0) &&
(node->treelock == 0))
unref_node(node);
}
static
void
unremember_node(node_t *node)
{
if(!node->remembered)
return;
assert(node->nlookup > 0);
node->remembered = false;
node->nlookup--;
unref_node_if_unused(node);
}
static static
node_t* node_t*
find_node(uint64_t parent, find_node(uint64_t parent,
const char *name) const char *name,
bool remember)
{ {
node_t *node; node_t *node;
mutex_lockguard(f.lock); mutex_lockguard(f.lock);
@ -652,8 +638,8 @@ find_node(uint64_t parent,
goto out_err; goto out_err;
node->nodeid = generate_nodeid(&f.nodeid_gen); node->nodeid = generate_nodeid(&f.nodeid_gen);
if(fuse_cfg.remember_nodes) if(remember && remember_nodes())
inc_nlookup(node); remember_node(node);
if(hash_name(node,parent,name) == -1) if(hash_name(node,parent,name) == -1)
{ {
@ -663,10 +649,6 @@ find_node(uint64_t parent,
} }
hash_id(node); hash_id(node);
} }
else if((node->nlookup == 1) && remember_nodes())
{
remove_remembered_node(node);
}
inc_nlookup(node); inc_nlookup(node);
out_err: out_err:
@ -725,6 +707,8 @@ unlock_path(uint64_t nodeid,
{ {
assert(wnode->treelock == TREELOCK_WRITE); assert(wnode->treelock == TREELOCK_WRITE);
wnode->treelock = 0; wnode->treelock = 0;
/* unlink_node() can drop the retained lookup while the node is locked. */
unref_node_if_unused(wnode);
} }
node = get_node(nodeid); node = get_node(nodeid);
@ -1147,31 +1131,15 @@ forget_node(const uint64_t nodeid,
assert(node->nlookup >= nlookup); assert(node->nlookup >= nlookup);
node->nlookup -= nlookup; node->nlookup -= nlookup;
if(node->nlookup == 0) unref_node_if_unused(node);
{
unref_node(node);
}
else if((node->nlookup == 1) && remember_nodes())
{
remembered_node_t fn;
fn.node = node;
fn.time = current_time();
kv_push(remembered_node_t,f.remembered_nodes,fn);
}
} }
static static
void void
unlink_node(node_t *node) unlink_node(node_t *node)
{ {
if(remember_nodes())
{
assert(node->nlookup > 1);
node->nlookup--;
}
unhash_name(node); unhash_name(node);
unremember_node(node);
} }
static static
@ -1205,7 +1173,7 @@ rename_node(uint64_t olddir,
if(node == NULL) if(node == NULL)
goto out; goto out;
if(newnode != NULL) if((newnode != NULL) && (newnode != node))
unlink_node(newnode); unlink_node(newnode);
unhash_name(node); unhash_name(node);
@ -1260,11 +1228,12 @@ static
int int
set_path_info(uint64_t nodeid, set_path_info(uint64_t nodeid,
const char *name, const char *name,
struct fuse_entry_param *e) struct fuse_entry_param *e,
bool remember = true)
{ {
node_t *node; node_t *node;
node = find_node(nodeid,name); node = find_node(nodeid,name,remember);
if(node == NULL) if(node == NULL)
return -ENOMEM; return -ENOMEM;
@ -1296,7 +1265,8 @@ lookup_path(fuse_req_t *req_,
const char *name, const char *name,
const char *fusepath, const char *fusepath,
struct fuse_entry_param *e, struct fuse_entry_param *e,
fuse_file_info_t *fi) fuse_file_info_t *fi,
bool remember = true)
{ {
int rv; int rv;
@ -1311,7 +1281,7 @@ lookup_path(fuse_req_t *req_,
if(rv) if(rv)
return rv; return rv;
return set_path_info(nodeid,name,e); return set_path_info(nodeid,name,e,remember);
} }
static static
@ -2043,6 +2013,7 @@ fuse_do_release(fuse_req_ctx_t *req_ctx_,
{ {
assert(node->open_count > 0); assert(node->open_count > 0);
node->open_count--; node->open_count--;
unref_node_if_unused(node);
} }
} }
} }
@ -2070,12 +2041,12 @@ fuse_lib_create(fuse_req_t *req_,
else else
name = ((char*)arg + sizeof(struct fuse_open_in)); name = ((char*)arg + sizeof(struct fuse_open_in));
// opportunistically allocate a node so we have a new nodeid for // Opportunistically allocate a provisional node so passthrough create has
// later in the actual create. Added for use with passthrough. // a nodeid. It is remembered only after create fully succeeds.
{ {
node_t *node; node_t *node;
node = find_node(hdr_->nodeid,name); node = find_node(hdr_->nodeid,name,false);
if(node == NULL) if(node == NULL)
{ {
fuse_reply_err(req_,ENOMEM); fuse_reply_err(req_,ENOMEM);
@ -2095,7 +2066,7 @@ fuse_lib_create(fuse_req_t *req_,
&ffi); &ffi);
if(!err) if(!err)
{ {
err = lookup_path(req_,hdr_->nodeid,name,fusepath,&e,&ffi); err = lookup_path(req_,hdr_->nodeid,name,fusepath,&e,&ffi,false);
if(err) if(err)
{ {
f.ops.release(&req_->ctx,&ffi); f.ops.release(&req_->ctx,&ffi);
@ -2114,7 +2085,11 @@ fuse_lib_create(fuse_req_t *req_,
mutex_lock(f.lock); mutex_lock(f.lock);
node_t *node = get_node(e.ino); node_t *node = get_node(e.ino);
if(node) if(node)
node->open_count++; {
if(remember_nodes())
remember_node(node);
node->open_count++;
}
mutex_unlock(f.lock); mutex_unlock(f.lock);
if(fuse_reply_create(req_,&e,&ffi) == -ENOENT) if(fuse_reply_create(req_,&e,&ffi) == -ENOENT)
@ -3184,122 +3159,6 @@ fuse_lib_fallocate(fuse_req_t *req_,
fuse_reply_err(req_,err); fuse_reply_err(req_,err);
} }
static
int
remembered_node_cmp(const void *a_,
const void *b_)
{
const remembered_node_t *a = (const remembered_node_t*)a_;
const remembered_node_t *b = (const remembered_node_t*)b_;
if(a->time < b->time)
return -1;
if(a->time > b->time)
return 1;
return 0;
}
static
void
remembered_nodes_sort()
{
mutex_lock(f.lock);
qsort(&kv_first(f.remembered_nodes),
kv_size(f.remembered_nodes),
sizeof(remembered_node_t),
remembered_node_cmp);
mutex_unlock(f.lock);
}
#define MAX_PRUNE 100
#define MAX_CHECK 1000
int
fuse_prune_some_remembered_nodes(int *offset_)
{
time_t now;
int pruned;
int checked;
mutex_lock(f.lock);
pruned = 0;
checked = 0;
now = current_time();
while(*offset_ < (int)kv_size(f.remembered_nodes))
{
time_t age;
remembered_node_t *fn = &kv_A(f.remembered_nodes,*offset_);
if(pruned >= MAX_PRUNE)
break;
if(checked >= MAX_CHECK)
break;
checked++;
age = (now - fn->time);
if(fuse_cfg.remember_nodes > age)
break;
assert(fn->node->nlookup == 1);
/* Don't forget active directories */
if(fn->node->refctr > 1)
{
(*offset_)++;
continue;
}
fn->node->nlookup = 0;
unref_node(fn->node);
kv_delete(f.remembered_nodes,*offset_);
pruned++;
}
mutex_unlock(f.lock);
if((pruned < MAX_PRUNE) && (checked < MAX_CHECK))
*offset_ = -1;
return pruned;
}
#undef MAX_PRUNE
#undef MAX_CHECK
static
void
sleep_100ms(void)
{
const struct timespec ms100 = {0,100 * 1000000};
nanosleep(&ms100,NULL);
}
void
fuse_prune_remembered_nodes()
{
int offset;
int pruned;
offset = 0;
pruned = 0;
for(;;)
{
pruned += fuse_prune_some_remembered_nodes(&offset);
if(offset >= 0)
{
sleep_100ms();
continue;
}
break;
}
if(pruned > 0)
remembered_nodes_sort();
}
static struct fuse_lowlevel_ops fuse_path_ops = static struct fuse_lowlevel_ops fuse_path_ops =
{ {
.access = fuse_lib_access, .access = fuse_lib_access,
@ -3532,12 +3391,6 @@ fuse_gc1()
void void
fuse_populate_maintenance_thread(struct fuse *f_) fuse_populate_maintenance_thread(struct fuse *f_)
{ {
MaintenanceThread::push_job([=](u64 count_)
{
if(remember_nodes())
fuse_prune_remembered_nodes();
});
MaintenanceThread::push_job([](u64 count_) MaintenanceThread::push_job([](u64 count_)
{ {
if((count_ % 15) == 0) if((count_ % 15) == 0)
@ -3582,8 +3435,6 @@ fuse_new(int fd_,
mutex_init(f.lock); mutex_init(f.lock);
kv_init(f.remembered_nodes);
root = node_alloc(); root = node_alloc();
if(root == NULL) if(root == NULL)
{ {
@ -3636,7 +3487,6 @@ fuse_destroy(struct fuse *)
free(f.name_table.array); free(f.name_table.array);
mutex_destroy(f.lock); mutex_destroy(f.lock);
fuse_session_destroy(f.se); fuse_session_destroy(f.se);
kv_destroy(f.remembered_nodes);
} }
int int

View file

@ -13,11 +13,18 @@ struct node_t
uint64_t nlookup; uint64_t nlookup;
uint32_t refctr; uint32_t refctr;
uint32_t open_count; uint32_t open_count : 31;
uint32_t remembered : 1;
uint32_t stat_crc32b; uint32_t stat_crc32b;
int32_t treelock; int32_t treelock;
}; };
#if defined(__LP64__) || defined(_LP64)
static_assert(sizeof(node_t) == 64, "node_t expected to be 64 bytes on 64-bit");
#else
static_assert(sizeof(node_t) == 48, "node_t expected to be 48 bytes on 32-bit");
#endif
node_t *node_alloc(); node_t *node_alloc();
void node_free(node_t*); void node_free(node_t*);
void node_gc(); void node_gc();