Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/client/dfuse/dfuse.h
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,12 @@ ival_drop_inode(struct dfuse_inode_entry *inode);
int
ival_update_inode(struct dfuse_inode_entry *inode, double timeout);

/* Queue an on-demand dentry invalidation (parent/name) to be issued from the invalidation thread.
* ie_drop, if non-NULL, is a reference that is released after the invalidation has been issued.
*/
int
dfuse_mark_inval_entry(fuse_ino_t parent, const char *name, struct dfuse_inode_entry *ie_drop);

int
ival_init(struct dfuse_info *dfuse_info);

Expand Down
99 changes: 98 additions & 1 deletion src/client/dfuse/inval.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* (C) Copyright 2016-2024 Intel Corporation.
* (C) Copyright 2026 Hewlett Packard Enterprise Development LP
* (C) Copyright 2025 Google LLC
*
* SPDX-License-Identifier: BSD-2-Clause-Patent
Expand Down Expand Up @@ -91,10 +92,24 @@ struct dfuse_time_entry {
/* Core data structure, maintains a list of struct dfuse_time_entry lists */
struct dfuse_ival {
d_list_t time_entry_list;
struct dfuse_info *dfuse_info;
struct fuse_session *session;
bool session_dead;
};

/* A single on-demand dentry invalidation request. Request handlers running on the fixed worker
* pool enqueue these rather than calling fuse_lowlevel_notify_inval_entry() directly, as that
* blocks acquiring the parent's kernel i_rwsem, which may be held by a client waiting on the same
* worker pool - deadlocking it. The dedicated invalidation thread drains the queue instead.
*/
struct dfuse_inval_item {
d_list_t link;
fuse_ino_t parent;
char name[NAME_MAX + 1];
/* Optional inode reference to drop once the invalidation has completed, or NULL */
struct dfuse_inode_entry *ie_drop;
};

/* The core data from struct dfuse_inode_entry. No additional inode references are held on inodes
* because of there place on invalidate lists, rather inodes are removed from any list on close.
* Therefore once a decision is made to evict an inode then a copy of the data is needed as once
Expand All @@ -119,6 +134,9 @@ static pthread_t ival_thread;
static sem_t ival_sem;
static struct dfuse_ival ival_data;

/* On-demand invalidation queue, protected by ival_lock and drained by the invalidation thread */
static d_list_t ival_queue;

/* Eviction loop, run periodically in it's own thread
*
* Returns true if there is more work to do. If false then *sleep_time is set in seconds.
Expand Down Expand Up @@ -200,6 +218,69 @@ ival_loop(int *sleep_time)
return (idx == EVICT_COUNT);
}

/* Queue a dentry invalidation for the invalidation thread. Takes ownership of ie_drop, which is
* released after the invalidation has been issued. On failure ie_drop is not touched.
*/
int
dfuse_mark_inval_entry(fuse_ino_t parent, const char *name, struct dfuse_inode_entry *ie_drop)
{
struct dfuse_inval_item *item;

D_ALLOC_PTR(item);
if (item == NULL)
return ENOMEM;

item->parent = parent;
item->ie_drop = ie_drop;
strncpy(item->name, name, NAME_MAX);
item->name[NAME_MAX] = '\0';

D_MUTEX_LOCK(&ival_lock);
d_list_add_tail(&item->link, &ival_queue);
D_MUTEX_UNLOCK(&ival_lock);

sem_post(&ival_sem);

return 0;
}

/* Drain the on-demand invalidation queue. Runs on the invalidation thread so the blocking
* fuse_lowlevel_notify_inval_entry() call is issued off the worker pool that services /dev/fuse.
* Once shutdown has begun (ival_stop) items are freed without issuing notifies, as the worker pool
* is gone by then and a notify could block the thread indefinitely.
*/
static void
ival_drain_queue(void)
{
struct dfuse_inval_item *item;
d_list_t drain;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might use outside while loop:
D_MUTEX_LOCK
d_list_splice_init(&ival_queue, &tmp_queue);
D_MUTEX_UNLOCK

and in loop pop with tmp_queue, this could avoid call mutex lock for each item? to avoid some potential contention locks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. i will modify

D_INIT_LIST_HEAD(&drain);

/* Move the whole queue out under a single lock, then process without holding it. */
D_MUTEX_LOCK(&ival_lock);
d_list_splice_init(&ival_queue, &drain);
D_MUTEX_UNLOCK(&ival_lock);

while ((item = d_list_pop_entry(&drain, struct dfuse_inval_item, link)) != NULL) {
if (!ival_stop && !ival_data.session_dead) {
int rc;

rc = fuse_lowlevel_notify_inval_entry(ival_data.session, item->parent,
item->name,
strnlen(item->name, NAME_MAX));
if (rc && rc != -ENOENT && rc != -EBADF)
DHS_ERROR(&ival_data, -rc, "notify_inval_entry() failed");
if (rc == -EBADF)
ival_data.session_dead = true;
}

if (item->ie_drop)
dfuse_inode_decref(ival_data.dfuse_info, item->ie_drop);
D_FREE(item);
}
}

/* Main loop for eviction thread. Spins until ready for exit waking after one second and iterates
* over all newly expired dentries.
*/
Expand All @@ -218,15 +299,19 @@ ival_thread_fn(void *arg)

rc = sem_timedwait(&ival_sem, &ts);
if (rc == 0) {
if (ival_stop)
if (ival_stop) {
ival_drain_queue();
return NULL;
}
} else {
rc = errno;

if (errno != ETIMEDOUT)
DS_ERROR(rc, "sem_wait");
}

ival_drain_queue();

while (ival_loop(&sleep_time))
;
if (sleep_time < 2)
Expand Down Expand Up @@ -266,7 +351,9 @@ ival_init(struct dfuse_info *dfuse_info)

DFUSE_TRA_UP(&ival_data, dfuse_info, "invalidator");

ival_data.dfuse_info = dfuse_info;
D_INIT_LIST_HEAD(&ival_data.time_entry_list);
D_INIT_LIST_HEAD(&ival_queue);

rc = sem_init(&ival_sem, 0, 0);
if (rc != 0)
Expand Down Expand Up @@ -320,6 +407,16 @@ void
ival_fini()
{
struct dfuse_time_entry *dte, *dtep;
struct dfuse_inval_item *item;

/* Free any invalidation requests that were never issued. Any inode references held by
* queued items are released here.
*/
while ((item = d_list_pop_entry(&ival_queue, struct dfuse_inval_item, link)) != NULL) {
if (item->ie_drop)
dfuse_inode_decref(ival_data.dfuse_info, item->ie_drop);
D_FREE(item);
}

/* Walk the list, oldest first */
d_list_for_each_entry_safe(dte, dtep, &ival_data.time_entry_list, dte_list) {
Expand Down
9 changes: 4 additions & 5 deletions src/client/dfuse/ops/lookup.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* (C) Copyright 2016-2024 Intel Corporation.
* (C) Copyright 2025 Hewlett Packard Enterprise Development LP
* (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP
*
* SPDX-License-Identifier: BSD-2-Clause-Patent
*/
Expand Down Expand Up @@ -163,10 +163,9 @@ dfuse_reply_entry(struct dfuse_info *dfuse_info, struct dfuse_inode_entry *ie,
if (wipe_parent == 0)
return;

rc = fuse_lowlevel_notify_inval_entry(dfuse_info->di_session, wipe_parent, wipe_name,
strnlen(wipe_name, NAME_MAX));
if (rc && rc != -ENOENT)
DS_ERROR(-rc, "inval_entry() failed");
rc = dfuse_mark_inval_entry(wipe_parent, wipe_name, NULL);
if (rc)
DS_ERROR(rc, "dfuse_mark_inval_entry() failed");

return;
out_err:
Expand Down
11 changes: 5 additions & 6 deletions src/client/dfuse/ops/open.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,11 @@ dfuse_cb_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
dfuse_inode_decref(dfuse_info, oh->doh_parent_dir);
}
if (ie) {
rc = fuse_lowlevel_notify_inval_entry(dfuse_info->di_session, ie->ie_parent,
ie->ie_name, strnlen(ie->ie_name, NAME_MAX));

if (rc != 0 && rc != -ENOENT)
DHS_ERROR(ie, -rc, "inval_entry() error");
dfuse_inode_decref(dfuse_info, ie);
rc = dfuse_mark_inval_entry(ie->ie_parent, ie->ie_name, ie);
if (rc) {
DHS_ERROR(ie, rc, "dfuse_mark_inval_entry() failed");
dfuse_inode_decref(dfuse_info, ie);
}
}
dfuse_oh_free(dfuse_info, oh);
}
12 changes: 6 additions & 6 deletions src/client/dfuse/ops/opendir.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* (C) Copyright 2016-2024 Intel Corporation.
* (C) Copyright 2026 Hewlett Packard Enterprise Development LP
*
* SPDX-License-Identifier: BSD-2-Clause-Patent
*/
Expand Down Expand Up @@ -83,12 +84,11 @@ dfuse_cb_releasedir(fuse_req_t req, struct dfuse_inode_entry *ino, struct fuse_f
if (ie) {
int rc;

rc = fuse_lowlevel_notify_inval_entry(dfuse_info->di_session, ie->ie_parent,
ie->ie_name, strnlen(ie->ie_name, NAME_MAX));

if (rc != 0 && rc != -ENOENT)
DHS_ERROR(ie, -rc, "inval_entry() error");
dfuse_inode_decref(dfuse_info, ie);
rc = dfuse_mark_inval_entry(ie->ie_parent, ie->ie_name, ie);
if (rc) {
DHS_ERROR(ie, rc, "dfuse_mark_inval_entry() failed");
dfuse_inode_decref(dfuse_info, ie);
}
}
dfuse_oh_free(dfuse_info, oh);
};
9 changes: 4 additions & 5 deletions src/client/dfuse/ops/rename.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* (C) Copyright 2016-2023 Intel Corporation.
* (C) Copyright 2026 Hewlett Packard Enterprise Development LP
*
* SPDX-License-Identifier: BSD-2-Clause-Patent
*/
Expand Down Expand Up @@ -34,11 +35,9 @@ dfuse_oid_moved(struct dfuse_info *dfuse_info, daos_obj_id_t *oid, struct dfuse_
(strncmp(ie->ie_name, name, NAME_MAX) != 0)) {
DFUSE_TRA_DEBUG(ie, "Invalidating old name");

rc = fuse_lowlevel_notify_inval_entry(dfuse_info->di_session, ie->ie_parent,
ie->ie_name, strnlen(ie->ie_name, NAME_MAX));

if (rc && rc != -ENOENT)
DFUSE_TRA_ERROR(ie, "inval_entry() returned: %d (%s)", rc, strerror(-rc));
rc = dfuse_mark_inval_entry(ie->ie_parent, ie->ie_name, NULL);
if (rc)
DFUSE_TRA_ERROR(ie, "dfuse_mark_inval_entry() failed: %d", rc);
}

/* Update the inode entry data */
Expand Down
22 changes: 9 additions & 13 deletions src/client/dfuse/ops/setxattr.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* (C) Copyright 2019-2022 Intel Corporation.
* (C) Copyright 2026 Hewlett Packard Enterprise Development LP
*
* SPDX-License-Identifier: BSD-2-Clause-Patent
*/
Expand Down Expand Up @@ -47,21 +48,16 @@ dfuse_cb_setxattr(fuse_req_t req, struct dfuse_inode_entry *inode,
rc = dfs_setxattr(inode->ie_dfs->dfs_ns, inode->ie_obj, name, value, size, flags);
if (rc == 0) {
/* Optionally remove the dentry to force a new lookup on access.
* If the xattr is to set a UNS entry point, and dentry_dir
* caching is enabled then invalidate the dentry here, to force
* a lookup which will check the xattr and return the linked
* container. The fuse header says this potentially deadlocks
* however it does appear to work, and calling this after the
* reply will introduce a race condition that future lookups
* will be skipped.
* If the xattr is to set a UNS entry point, and dentry_dir caching is enabled then
* invalidate the dentry to force a lookup which will check the xattr and return the
* linked container. The invalidation is queued to the invalidation thread rather
* than issued here as fuse_lowlevel_notify_inval_entry() blocks on kernel inode
* locks that may be held by a client waiting on this worker pool, deadlocking it.
*/
if (duns_attr && inode->ie_dfs->dfc_dentry_dir_timeout > 0) {
struct dfuse_info *dfuse_info = fuse_req_userdata(req);

rc = fuse_lowlevel_notify_inval_entry(dfuse_info->di_session,
inode->ie_parent, inode->ie_name,
strnlen(inode->ie_name, NAME_MAX));
DFUSE_TRA_INFO(inode, "inval_entry() rc is %d", rc);
rc = dfuse_mark_inval_entry(inode->ie_parent, inode->ie_name, NULL);
if (rc)
DHS_ERROR(inode, rc, "dfuse_mark_inval_entry() failed");
}
DFUSE_REPLY_ZERO(inode, req);
return;
Expand Down
Loading