mirror of
https://github.com/trapexit/mergerfs.git
synced 2026-10-19 16:16:48 +00:00
Remove timed node removal
This commit is contained in:
committed by
trapexit
parent
8117998205
commit
f1034a3ad6
@@ -15,6 +15,7 @@ function or have been replaced. **They should not be used.**
|
||||
* **hard_remove**: Does nothing.
|
||||
* **kernel_cache**: Use [cache.files=full](cache.md).
|
||||
* **negative_entry**: Use [cache.negative-entry](cache.md).
|
||||
* **remember**: Use `never-forget-nodes`.
|
||||
* **noforget**: Use `never-forget-nodes`.
|
||||
* **nonempty**: Does nothing.
|
||||
* **splice_move**: Does nothing.
|
||||
|
||||
@@ -213,12 +213,11 @@ config file.
|
||||
* **passthrough.max-stack-depth=INT**: Set to `1` another filesystem
|
||||
can be stacked on mergerfs. Set to `2` to have mergerfs stacked over
|
||||
another filesystem. (default: 1)
|
||||
* **remember-nodes=INT**: The number of seconds to keep the internal
|
||||
representation of a file once the OS tells mergerfs it is no longer
|
||||
needed. Really only needed for [exporting mergerfs via
|
||||
NFS](../remote_filesystems.md) (default: 0)
|
||||
* **never-forget-nodes=BOOL**: Sets `remember-nodes` to
|
||||
infinity. Formerly `noforget`. (default: false)
|
||||
* **never-forget-nodes=BOOL**: Keep the internal representation of
|
||||
a file after the OS tells mergerfs it is no longer needed, until the
|
||||
node is unlinked, removed, or replaced. Really only needed for
|
||||
[exporting mergerfs via NFS](../remote_filesystems.md). Formerly
|
||||
`noforget`. (default: false)
|
||||
* **allow-idmap=BOOL**: Enables idmap (identity mapping). (default:
|
||||
true)
|
||||
* **debug**: Enable debugging information. At the moment limited to a
|
||||
|
||||
@@ -57,7 +57,7 @@ NFS export settings:
|
||||
* `softreval`: NFS client will serve up cached data after `retrans`
|
||||
attempts to revalidate the data. Helps with intermittent network
|
||||
issues.
|
||||
* `timeo=150`: Timeout till retrying request.
|
||||
* `timeo=150`: Timeout till retrying request.
|
||||
* `retrans=3`: Number of retrying a request.
|
||||
* `rsize=131072`: Read size.
|
||||
* `wsize=131072`: Write size.
|
||||
@@ -76,12 +76,11 @@ but if you run into problems it may be worth trying Samba/SMB.
|
||||
|
||||
**mergerfs settings:**
|
||||
|
||||
* `never-forget-nodes=true` or `remember-nodes=SECONDS`
|
||||
* `never-forget-nodes=true`
|
||||
* `inodecalc=path-hash`
|
||||
* `lazy-umount-mountpoint=false`
|
||||
|
||||
`never-forget-nodes=true` (or `remember-nodes` with a large value such
|
||||
as `86400` for a full day) is needed because NFS uses the
|
||||
`never-forget-nodes=true` is needed because NFS uses the
|
||||
`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
|
||||
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
|
||||
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
|
||||
nodes around forever (or however long is set with `remember-nodes`) is
|
||||
not ideal but at the moment the only way to manage the situation.
|
||||
nodes around until they are unlinked, removed, or replaced is not ideal
|
||||
but at the moment the only way to manage the situation.
|
||||
|
||||
`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
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
depending on [fuse-msg-size](config/fuse-msg-size.md)
|
||||
* buffers allocated temporarily for reading directories
|
||||
* FUSE nodes
|
||||
* never-forget-nodes=true nodes
|
||||
* never-forget-nodes=true retained nodes, until unlinked or removed
|
||||
|
||||
|
||||
## Management
|
||||
|
||||
+12
-11
@@ -135,10 +135,6 @@ Config::Config()
|
||||
_max_background(fuse_cfg.max_background),
|
||||
_mount(mountpoint),
|
||||
_mountpoint(mountpoint),
|
||||
_never_forget_nodes(),
|
||||
_noforget(),
|
||||
_remember(fuse_cfg.remember_nodes),
|
||||
_remember_nodes(fuse_cfg.remember_nodes),
|
||||
_srcmounts(branches),
|
||||
_threads(fuse_cfg.read_thread_count),
|
||||
_uid(fuse_cfg.uid),
|
||||
@@ -147,13 +143,22 @@ Config::Config()
|
||||
|
||||
_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 =
|
||||
branches_mount_timeout.ro =
|
||||
branches_mount_timeout_fail.ro =
|
||||
cache_symlinks.ro =
|
||||
cache_writeback.ro =
|
||||
_congestion_threshold.ro =
|
||||
direct_io_allow_mmap.ro =
|
||||
export_support.ro =
|
||||
fsname.ro =
|
||||
@@ -161,9 +166,6 @@ Config::Config()
|
||||
handle_killpriv.ro =
|
||||
handle_killpriv_v2.ro =
|
||||
kernel_permissions_check.ro =
|
||||
_max_background.ro =
|
||||
_mount.ro =
|
||||
_mountpoint.ro =
|
||||
nullrw.ro =
|
||||
pid.ro =
|
||||
pin_threads.ro =
|
||||
@@ -172,8 +174,6 @@ Config::Config()
|
||||
process_thread_queue_depth.ro =
|
||||
read_thread_count.ro =
|
||||
scheduling_priority.ro =
|
||||
_srcmounts.ro =
|
||||
_version.ro =
|
||||
true;
|
||||
_congestion_threshold.display =
|
||||
_gid.display =
|
||||
@@ -181,6 +181,7 @@ Config::Config()
|
||||
_mount.display =
|
||||
_noforget.display =
|
||||
_remember.display =
|
||||
_remember_nodes.display =
|
||||
_srcmounts.display =
|
||||
_threads.display =
|
||||
_uid.display =
|
||||
|
||||
+2
-2
@@ -176,8 +176,8 @@ private:
|
||||
TFSRef<fs::path> _mountpoint;
|
||||
CfgNoforget _never_forget_nodes;
|
||||
CfgNoforget _noforget;
|
||||
TFSRef<s64> _remember;
|
||||
TFSRef<s64> _remember_nodes;
|
||||
CfgNoforget _remember;
|
||||
CfgNoforget _remember_nodes;
|
||||
SrcMounts _srcmounts;
|
||||
TFSRef<int> _threads;
|
||||
TFSRef<s64> _uid;
|
||||
|
||||
+15
-9
@@ -32,18 +32,24 @@ public:
|
||||
{
|
||||
int rv;
|
||||
bool b = false;
|
||||
s64 i = 0;
|
||||
|
||||
rv = str::from(s_,&b);
|
||||
if((b == true) || s_.empty())
|
||||
if(s_.empty())
|
||||
{
|
||||
fuse_cfg.remember_nodes = -1;
|
||||
return 0;
|
||||
b = true;
|
||||
}
|
||||
else if((rv = str::from(s_,&i)) == 0)
|
||||
{
|
||||
b = (i != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
rv = str::from(s_,&b);
|
||||
if(rv)
|
||||
return rv;
|
||||
}
|
||||
|
||||
if(rv)
|
||||
return rv;
|
||||
|
||||
fuse_cfg.remember_nodes = 0;
|
||||
fuse_cfg.remember_nodes = b;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -51,7 +57,7 @@ public:
|
||||
std::string
|
||||
to_string() const
|
||||
{
|
||||
if(fuse_cfg.remember_nodes == -1)
|
||||
if(fuse_cfg.remember_nodes)
|
||||
return "true";
|
||||
return "false";
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ Legend:
|
||||
| Option | Integration tests | Unit tests |
|
||||
|---|---|---|
|
||||
| `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 |
|
||||
| `noforget` | No node expiry over extended period | Infinite retention mode mapping |
|
||||
| `remember-nodes` | NFS-like lookup stability until unlink/remove/rename replacement | Boolean and numeric compatibility parsing |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
||||
@@ -2730,6 +2730,61 @@ test_config_set_kv_form()
|
||||
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
|
||||
test_config_readonly_before_init()
|
||||
{
|
||||
@@ -3683,6 +3738,7 @@ TEST_LIST =
|
||||
{"config_set_underscore_normalization",test_config_set_underscore_normalization},
|
||||
{"config_get_underscore_normalization",test_config_get_underscore_normalization},
|
||||
{"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_after_init",test_config_readonly_after_init},
|
||||
{"config_mutable_after_init",test_config_mutable_after_init},
|
||||
|
||||
@@ -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.
|
||||
|
||||
To work around this, currently a "noforget" option is provided, which
|
||||
makes the library remember nodes forever. This will make the NFS
|
||||
server happy, but also results in an ever growing memory footprint for
|
||||
the filesystem. For this reason if the filesystem is large (or the
|
||||
memory is small), then this option is not recommended.
|
||||
makes the library remember nodes until they are unlinked or otherwise
|
||||
removed. This will make the NFS server happy, but also results in a
|
||||
larger memory footprint for the filesystem. For this reason if the
|
||||
filesystem is large (or the memory is small), then this option is not
|
||||
recommended.
|
||||
|
||||
@@ -365,13 +365,12 @@ void fuse_populate_maintenance_thread(struct fuse *fuse);
|
||||
|
||||
|
||||
/**
|
||||
* Iterate over cache removing stale entries
|
||||
* use in conjunction with "-oremember"
|
||||
* Legacy cache cleanup entry point.
|
||||
*
|
||||
* NOTE: This is already done for the standard sessions
|
||||
*
|
||||
* @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);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ struct fuse_cfg_t
|
||||
s64 umask = FUSE_CFG_INVALID_UMASK;
|
||||
bool valid_umask() const;
|
||||
|
||||
s64 remember_nodes = 0;
|
||||
bool remember_nodes = false;
|
||||
|
||||
bool debug = false;
|
||||
std::shared_ptr<FILE> log_file() const;
|
||||
|
||||
+61
-211
@@ -102,13 +102,6 @@ struct list_head
|
||||
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;
|
||||
struct nodeid_gen_t
|
||||
{
|
||||
@@ -127,7 +120,6 @@ struct fuse
|
||||
fuse_operations ops;
|
||||
struct lock_queue_element *lockq;
|
||||
|
||||
kvec_t(remembered_node_t) remembered_nodes;
|
||||
};
|
||||
|
||||
#define TREELOCK_WRITE -1
|
||||
@@ -192,20 +184,6 @@ get_node(const uint64_t nodeid)
|
||||
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
|
||||
uint32_t
|
||||
stat_crc32b(const struct stat *st_)
|
||||
@@ -230,31 +208,6 @@ stat_crc32b(const struct stat *st_)
|
||||
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
|
||||
void
|
||||
free_node(node_t *node_)
|
||||
@@ -560,10 +513,10 @@ hash_name(node_t *node,
|
||||
|
||||
static
|
||||
inline
|
||||
int
|
||||
bool
|
||||
remember_nodes()
|
||||
{
|
||||
return (fuse_cfg.remember_nodes > 0);
|
||||
return fuse_cfg.remember_nodes;
|
||||
}
|
||||
|
||||
static
|
||||
@@ -572,8 +525,6 @@ delete_node(node_t *node)
|
||||
{
|
||||
assert(node->treelock == 0);
|
||||
unhash_name(node);
|
||||
if(remember_nodes())
|
||||
remove_remembered_node(node);
|
||||
unhash_id(node);
|
||||
node_free(node);
|
||||
}
|
||||
@@ -626,10 +577,45 @@ inc_nlookup(node_t *node)
|
||||
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
|
||||
node_t*
|
||||
find_node(uint64_t parent,
|
||||
const char *name)
|
||||
const char *name,
|
||||
bool remember)
|
||||
{
|
||||
node_t *node;
|
||||
mutex_lockguard(f.lock);
|
||||
@@ -652,8 +638,8 @@ find_node(uint64_t parent,
|
||||
goto out_err;
|
||||
|
||||
node->nodeid = generate_nodeid(&f.nodeid_gen);
|
||||
if(fuse_cfg.remember_nodes)
|
||||
inc_nlookup(node);
|
||||
if(remember && remember_nodes())
|
||||
remember_node(node);
|
||||
|
||||
if(hash_name(node,parent,name) == -1)
|
||||
{
|
||||
@@ -663,10 +649,6 @@ find_node(uint64_t parent,
|
||||
}
|
||||
hash_id(node);
|
||||
}
|
||||
else if((node->nlookup == 1) && remember_nodes())
|
||||
{
|
||||
remove_remembered_node(node);
|
||||
}
|
||||
inc_nlookup(node);
|
||||
|
||||
out_err:
|
||||
@@ -725,6 +707,8 @@ unlock_path(uint64_t nodeid,
|
||||
{
|
||||
assert(wnode->treelock == TREELOCK_WRITE);
|
||||
wnode->treelock = 0;
|
||||
/* unlink_node() can drop the retained lookup while the node is locked. */
|
||||
unref_node_if_unused(wnode);
|
||||
}
|
||||
|
||||
node = get_node(nodeid);
|
||||
@@ -1147,31 +1131,15 @@ forget_node(const uint64_t nodeid,
|
||||
assert(node->nlookup >= nlookup);
|
||||
node->nlookup -= nlookup;
|
||||
|
||||
if(node->nlookup == 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
unref_node_if_unused(node);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
unlink_node(node_t *node)
|
||||
{
|
||||
if(remember_nodes())
|
||||
{
|
||||
assert(node->nlookup > 1);
|
||||
node->nlookup--;
|
||||
}
|
||||
unhash_name(node);
|
||||
unremember_node(node);
|
||||
}
|
||||
|
||||
static
|
||||
@@ -1205,7 +1173,7 @@ rename_node(uint64_t olddir,
|
||||
if(node == NULL)
|
||||
goto out;
|
||||
|
||||
if(newnode != NULL)
|
||||
if((newnode != NULL) && (newnode != node))
|
||||
unlink_node(newnode);
|
||||
|
||||
unhash_name(node);
|
||||
@@ -1260,11 +1228,12 @@ static
|
||||
int
|
||||
set_path_info(uint64_t nodeid,
|
||||
const char *name,
|
||||
struct fuse_entry_param *e)
|
||||
struct fuse_entry_param *e,
|
||||
bool remember = true)
|
||||
{
|
||||
node_t *node;
|
||||
|
||||
node = find_node(nodeid,name);
|
||||
node = find_node(nodeid,name,remember);
|
||||
if(node == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -1296,7 +1265,8 @@ lookup_path(fuse_req_t *req_,
|
||||
const char *name,
|
||||
const char *fusepath,
|
||||
struct fuse_entry_param *e,
|
||||
fuse_file_info_t *fi)
|
||||
fuse_file_info_t *fi,
|
||||
bool remember = true)
|
||||
{
|
||||
int rv;
|
||||
|
||||
@@ -1311,7 +1281,7 @@ lookup_path(fuse_req_t *req_,
|
||||
if(rv)
|
||||
return rv;
|
||||
|
||||
return set_path_info(nodeid,name,e);
|
||||
return set_path_info(nodeid,name,e,remember);
|
||||
}
|
||||
|
||||
static
|
||||
@@ -2043,6 +2013,7 @@ fuse_do_release(fuse_req_ctx_t *req_ctx_,
|
||||
{
|
||||
assert(node->open_count > 0);
|
||||
node->open_count--;
|
||||
unref_node_if_unused(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2070,12 +2041,12 @@ fuse_lib_create(fuse_req_t *req_,
|
||||
else
|
||||
name = ((char*)arg + sizeof(struct fuse_open_in));
|
||||
|
||||
// opportunistically allocate a node so we have a new nodeid for
|
||||
// later in the actual create. Added for use with passthrough.
|
||||
// Opportunistically allocate a provisional node so passthrough create has
|
||||
// a nodeid. It is remembered only after create fully succeeds.
|
||||
{
|
||||
node_t *node;
|
||||
|
||||
node = find_node(hdr_->nodeid,name);
|
||||
node = find_node(hdr_->nodeid,name,false);
|
||||
if(node == NULL)
|
||||
{
|
||||
fuse_reply_err(req_,ENOMEM);
|
||||
@@ -2095,7 +2066,7 @@ fuse_lib_create(fuse_req_t *req_,
|
||||
&ffi);
|
||||
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)
|
||||
{
|
||||
f.ops.release(&req_->ctx,&ffi);
|
||||
@@ -2114,7 +2085,11 @@ fuse_lib_create(fuse_req_t *req_,
|
||||
mutex_lock(f.lock);
|
||||
node_t *node = get_node(e.ino);
|
||||
if(node)
|
||||
node->open_count++;
|
||||
{
|
||||
if(remember_nodes())
|
||||
remember_node(node);
|
||||
node->open_count++;
|
||||
}
|
||||
mutex_unlock(f.lock);
|
||||
|
||||
if(fuse_reply_create(req_,&e,&ffi) == -ENOENT)
|
||||
@@ -3184,122 +3159,6 @@ fuse_lib_fallocate(fuse_req_t *req_,
|
||||
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 =
|
||||
{
|
||||
.access = fuse_lib_access,
|
||||
@@ -3532,12 +3391,6 @@ fuse_gc1()
|
||||
void
|
||||
fuse_populate_maintenance_thread(struct fuse *f_)
|
||||
{
|
||||
MaintenanceThread::push_job([=](u64 count_)
|
||||
{
|
||||
if(remember_nodes())
|
||||
fuse_prune_remembered_nodes();
|
||||
});
|
||||
|
||||
MaintenanceThread::push_job([](u64 count_)
|
||||
{
|
||||
if((count_ % 15) == 0)
|
||||
@@ -3582,8 +3435,6 @@ fuse_new(int fd_,
|
||||
|
||||
mutex_init(f.lock);
|
||||
|
||||
kv_init(f.remembered_nodes);
|
||||
|
||||
root = node_alloc();
|
||||
if(root == NULL)
|
||||
{
|
||||
@@ -3636,7 +3487,6 @@ fuse_destroy(struct fuse *)
|
||||
free(f.name_table.array);
|
||||
mutex_destroy(f.lock);
|
||||
fuse_session_destroy(f.se);
|
||||
kv_destroy(f.remembered_nodes);
|
||||
}
|
||||
|
||||
int
|
||||
|
||||
@@ -13,11 +13,18 @@ struct node_t
|
||||
uint64_t nlookup;
|
||||
|
||||
uint32_t refctr;
|
||||
uint32_t open_count;
|
||||
uint32_t open_count : 31;
|
||||
uint32_t remembered : 1;
|
||||
uint32_t stat_crc32b;
|
||||
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();
|
||||
void node_free(node_t*);
|
||||
void node_gc();
|
||||
|
||||
Reference in New Issue
Block a user