diff --git a/src/client/dfuse/dfuse.h b/src/client/dfuse/dfuse.h index 45e9ab47d5f..ab14572fa1f 100644 --- a/src/client/dfuse/dfuse.h +++ b/src/client/dfuse/dfuse.h @@ -1189,12 +1189,6 @@ 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); diff --git a/src/client/dfuse/inval.c b/src/client/dfuse/inval.c index 505c6ecc862..3ddcc052d86 100644 --- a/src/client/dfuse/inval.c +++ b/src/client/dfuse/inval.c @@ -1,6 +1,5 @@ /** * (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 @@ -92,24 +91,10 @@ 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 @@ -134,9 +119,6 @@ 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. @@ -218,69 +200,6 @@ 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; - - 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. */ @@ -299,10 +218,8 @@ ival_thread_fn(void *arg) rc = sem_timedwait(&ival_sem, &ts); if (rc == 0) { - if (ival_stop) { - ival_drain_queue(); + if (ival_stop) return NULL; - } } else { rc = errno; @@ -310,8 +227,6 @@ ival_thread_fn(void *arg) DS_ERROR(rc, "sem_wait"); } - ival_drain_queue(); - while (ival_loop(&sleep_time)) ; if (sleep_time < 2) @@ -351,9 +266,7 @@ 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) @@ -407,16 +320,6 @@ 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) { diff --git a/src/client/dfuse/ops/lookup.c b/src/client/dfuse/ops/lookup.c index 606008849c6..1f1f25201b9 100644 --- a/src/client/dfuse/ops/lookup.c +++ b/src/client/dfuse/ops/lookup.c @@ -1,6 +1,6 @@ /** * (C) Copyright 2016-2024 Intel Corporation. - * (C) Copyright 2025-2026 Hewlett Packard Enterprise Development LP + * (C) Copyright 2025 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -163,9 +163,10 @@ dfuse_reply_entry(struct dfuse_info *dfuse_info, struct dfuse_inode_entry *ie, if (wipe_parent == 0) return; - rc = dfuse_mark_inval_entry(wipe_parent, wipe_name, NULL); - if (rc) - DS_ERROR(rc, "dfuse_mark_inval_entry() failed"); + 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"); return; out_err: diff --git a/src/client/dfuse/ops/open.c b/src/client/dfuse/ops/open.c index 580bef13a8a..5a5f7d3494e 100644 --- a/src/client/dfuse/ops/open.c +++ b/src/client/dfuse/ops/open.c @@ -242,11 +242,12 @@ 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 = 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); - } + 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); } dfuse_oh_free(dfuse_info, oh); } diff --git a/src/client/dfuse/ops/opendir.c b/src/client/dfuse/ops/opendir.c index dc80b04664d..60d1e6ab4d5 100644 --- a/src/client/dfuse/ops/opendir.c +++ b/src/client/dfuse/ops/opendir.c @@ -1,6 +1,5 @@ /** * (C) Copyright 2016-2024 Intel Corporation. - * (C) Copyright 2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -84,11 +83,12 @@ dfuse_cb_releasedir(fuse_req_t req, struct dfuse_inode_entry *ino, struct fuse_f if (ie) { int rc; - 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); - } + 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); } dfuse_oh_free(dfuse_info, oh); }; diff --git a/src/client/dfuse/ops/rename.c b/src/client/dfuse/ops/rename.c index 96fef8e2b8f..fcf95b82466 100644 --- a/src/client/dfuse/ops/rename.c +++ b/src/client/dfuse/ops/rename.c @@ -1,6 +1,5 @@ /** * (C) Copyright 2016-2023 Intel Corporation. - * (C) Copyright 2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -35,9 +34,11 @@ 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 = dfuse_mark_inval_entry(ie->ie_parent, ie->ie_name, NULL); - if (rc) - DFUSE_TRA_ERROR(ie, "dfuse_mark_inval_entry() failed: %d", 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 && rc != -ENOENT) + DFUSE_TRA_ERROR(ie, "inval_entry() returned: %d (%s)", rc, strerror(-rc)); } /* Update the inode entry data */ diff --git a/src/client/dfuse/ops/setxattr.c b/src/client/dfuse/ops/setxattr.c index 7682e9af1d4..e50c010b377 100644 --- a/src/client/dfuse/ops/setxattr.c +++ b/src/client/dfuse/ops/setxattr.c @@ -1,6 +1,5 @@ /** * (C) Copyright 2019-2022 Intel Corporation. - * (C) Copyright 2026 Hewlett Packard Enterprise Development LP * * SPDX-License-Identifier: BSD-2-Clause-Patent */ @@ -48,16 +47,21 @@ 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 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 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 (duns_attr && inode->ie_dfs->dfc_dentry_dir_timeout > 0) { - rc = dfuse_mark_inval_entry(inode->ie_parent, inode->ie_name, NULL); - if (rc) - DHS_ERROR(inode, rc, "dfuse_mark_inval_entry() failed"); + 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); } DFUSE_REPLY_ZERO(inode, req); return;