[Devel] [PATCH RHEL9 COMMIT] fs/fuse: revamp fuse_invalidate_files()
Konstantin Khorenko
khorenko at virtuozzo.com
Mon Sep 15 17:05:12 MSK 2025
The commit is pushed to "branch-rh9-5.14.0-427.77.1.vz9.86.x-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh9-5.14.0-427.77.1.vz9.86.6
------>
commit ada7c4d7b6569f5bd9f6229000a4e3574b57867c
Author: Liu Kui <kui.liu at virtuozzo.com>
Date: Mon Sep 15 21:02:00 2025 +0800
fs/fuse: revamp fuse_invalidate_files()
Currently we have to scan serveral queues to find all requests carrying
locked pages from the page cache and then unlock them. This approach
is very bug prone when a request is moved from one queue to another,
during which we have to check the fuse_file state to make sure the request
is either aborted early or added to the a queue that won't be missed by
the function fuse_invalidate_files(), ugly and messy.
Well there is a much simpler approach, we can just maintain a dedicated
list just for revocation. Only read requests coming from page cache are
added to the list and stay in the list until they are ended. Then we
can simply go through the list to find all requests and revoke them
from fuse_invalidate_files(), it can also make the normal path mostly
revocation agnostic and those special code can be removed.
Related to #VSTOR-101450
Signed-off-by: Liu Kui <kui.liu at virtuozzo.com>
======
Patchset description:
fs/fuse: Revamp fuse_invalidate_files()
Current implementation requires traversing various queues to find
requests that need to be killed. This approach is problematic because:
1. Requests can move between different queues during their lifecycle
2. Special care must be taken when moving requests between queues to
avoid missing requests that need to be killed.
3. The current implementation is complex, bug-prone, and difficult to
maintain
This patch series implements a cleaner solution by introducing a dedicated
queue to track all killable requests. Each request is enqueued when created
and dequeued only when destroyed, ensuring that no requests are missed
during kill operations.
Key changes:
- Add a new killable requests queue to track all requests that can be
killed
- Simplify kill operations by iterating only through the dedicated queue
- Remove complex queue traversal logic and race condition handling
- Improve code maintainability and reduce bug potential
Implements #VSTOR-101450
https://virtuozzo.atlassian.net/browse/VSTOR-101450
Feature: vStorage
---
fs/fuse/file.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
fs/fuse/fuse_i.h | 10 ++++++++--
fs/fuse/inode.c | 13 ++++++++++++-
3 files changed, 70 insertions(+), 8 deletions(-)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 074e6ee11eb91..c8e1056370ad7 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -100,6 +100,8 @@ struct fuse_file *fuse_file_alloc(struct fuse_mount *fm)
refcount_set(&ff->count, 1);
RB_CLEAR_NODE(&ff->polled_node);
init_waitqueue_head(&ff->poll_wait);
+ spin_lock_init(&ff->lock);
+ INIT_LIST_HEAD(&ff->revoke_list);
ff->kh = atomic64_inc_return(&fm->fc->khctr);
spin_lock(&fm->fc->lock);
@@ -1117,6 +1119,7 @@ static int fuse_do_readpage(struct file *file, struct page *page,
bool *killed_p)
{
struct inode *inode = page->mapping->host;
+ struct fuse_file *ff = file->private_data;
struct fuse_mount *fm = get_fuse_mount(inode);
loff_t pos = page_offset(page);
struct fuse_page_desc desc = { .length = PAGE_SIZE };
@@ -1137,9 +1140,13 @@ static int fuse_do_readpage(struct file *file, struct page *page,
* page.
*/
fuse_wait_on_page_writeback(inode, page->index);
-
- if (fuse_file_fail_immediately(file))
+ spin_lock(&ff->lock);
+ if (unlikely(__fuse_file_fail_immediately(ff))) {
+ spin_unlock(&ff->lock);
return -EIO;
+ }
+ list_add(&ia.revoke_entry, &ff->revoke_list);
+ spin_unlock(&ff->lock);
attr_ver = fuse_get_attr_version(fm->fc);
@@ -1153,6 +1160,11 @@ static int fuse_do_readpage(struct file *file, struct page *page,
*killed_p = ia.ap.args.killed;
if (ia.ap.args.killed)
res = -EIO;
+
+ spin_lock(&ff->lock);
+ list_del(&ia.revoke_entry);
+ spin_unlock(&ff->lock);
+
if (res < 0)
return res;
/*
@@ -1201,16 +1213,33 @@ void fuse_release_ff(struct inode *inode, struct fuse_file *ff)
}
EXPORT_SYMBOL_GPL(fuse_release_ff);
+void fuse_revoke_readpages(struct fuse_file *ff)
+{
+ struct fuse_io_args *ia;
+ int i;
+
+ spin_lock(&ff->lock);
+ /* revoke all pending read issued from page cache */
+ list_for_each_entry(ia, &ff->revoke_list, revoke_entry) {
+ ia->ap.args.killed = 1;
+ for (i = 0; i < ia->ap.num_pages; i++)
+ unlock_page(ia->ap.pages[i]);
+ }
+ spin_unlock(&ff->lock);
+}
+EXPORT_SYMBOL_GPL(fuse_revoke_readpages);
+
static void fuse_readpages_end(struct fuse_mount *fm, struct fuse_args *args,
int err)
{
int i;
- int killed = args->killed;
+ bool killed = args->killed;
struct fuse_io_args *ia = container_of(args, typeof(*ia), ap.args);
struct fuse_args_pages *ap = &ia->ap;
size_t count = ia->read.in.size;
size_t num_read = args->out_args[0].size;
struct inode *inode = args->io_inode;
+ struct fuse_file *ff = ia->ff;
if (unlikely(killed))
err = -EIO;
@@ -1235,9 +1264,13 @@ static void fuse_readpages_end(struct fuse_mount *fm, struct fuse_args *args,
}
fuse_invalidate_atime(inode);
- if (ia->ff)
- fuse_release_ff(inode, ia->ff);
+ if (ff) {
+ spin_lock(&ff->lock);
+ list_del(&ia->revoke_entry);
+ spin_unlock(&ff->lock);
+ fuse_release_ff(inode, ff);
+ }
fuse_io_free(ia);
}
@@ -1268,6 +1301,17 @@ static void fuse_send_readpages(struct fuse_io_args *ia, struct file *file)
if (fm->fc->async_read) {
ia->ff = fuse_file_get(ff);
ap->args.end = fuse_readpages_end;
+
+ spin_lock(&ff->lock);
+ if (unlikely(__fuse_file_fail_immediately(ff))) {
+ spin_unlock(&ff->lock);
+ INIT_LIST_HEAD(&ia->revoke_entry);
+ err = -EIO;
+ goto out;
+ }
+ list_add(&ia->revoke_entry, &ff->revoke_list);
+ spin_unlock(&ff->lock);
+
err = fuse_simple_background(fm, &ap->args, GFP_KERNEL);
if (!err)
return;
@@ -1275,6 +1319,7 @@ static void fuse_send_readpages(struct fuse_io_args *ia, struct file *file)
res = fuse_simple_request(fm, &ap->args);
err = res < 0 ? res : 0;
}
+out:
fuse_readpages_end(fm, &ap->args, err);
}
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 02c80cb9873a1..f77e8dfadaa30 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -267,6 +267,12 @@ struct fuse_file {
bool flock:1;
unsigned long ff_state;
+
+ /* Lock to protect the revoke_list*/
+ spinlock_t lock;
+
+ /** List of requests that may be killed **/
+ struct list_head revoke_list;
};
/** FUSE file states (ff_state) */
@@ -1191,6 +1197,7 @@ struct fuse_io_args {
struct fuse_args_pages ap;
struct fuse_io_priv *io;
struct fuse_file *ff;
+ struct list_head revoke_entry;
};
void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos,
@@ -1544,12 +1551,11 @@ struct fuse_file *__fuse_write_file_get(struct fuse_conn *fc,
struct fuse_inode *fi);
struct fuse_file *fuse_file_get(struct fuse_file *ff);
void fuse_release_ff(struct inode *inode, struct fuse_file *ff);
-
void fuse_kill_requests(struct fuse_conn *fc, struct inode *inode,
struct list_head *req_list);
-
struct fuse_req *fuse_dev_find_request(int fd, u64 unique);
+void fuse_revoke_readpages(struct fuse_file *ff);
/* readdir.c */
int fuse_readdir(struct file *file, struct dir_context *ctx);
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 4d140c2d0f5c3..0afc5d4cec8d3 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -507,6 +507,7 @@ void fuse_kill_requests(struct fuse_conn *fc, struct inode *inode,
}
EXPORT_SYMBOL_GPL(fuse_kill_requests);
+#if 0
static void fuse_kill_routing(struct fuse_rtable *rt, struct fuse_conn *fc, struct inode *inode)
{
if (rt->type == FUSE_ROUTING_CPU) {
@@ -531,6 +532,7 @@ static void fuse_kill_routing(struct fuse_rtable *rt, struct fuse_conn *fc, stru
}
}
}
+#endif
int fuse_invalidate_files(struct fuse_conn *fc, u64 nodeid)
{
@@ -555,7 +557,9 @@ int fuse_invalidate_files(struct fuse_conn *fc, u64 nodeid)
spin_lock(&fi->lock);
set_bit(FUSE_I_INVAL_FILES, &fi->state);
list_for_each_entry(ff, &fi->rw_files, rw_entry) {
+ spin_lock(&ff->lock);
set_bit(FUSE_S_FAIL_IMMEDIATELY, &ff->ff_state);
+ spin_unlock(&ff->lock);
}
spin_unlock(&fi->lock);
@@ -567,6 +571,7 @@ int fuse_invalidate_files(struct fuse_conn *fc, u64 nodeid)
err = filemap_write_and_wait(inode->i_mapping);
if (!err || err == -EIO) { /* AS_EIO might trigger -EIO */
+#if 0
struct fuse_dev *fud;
spin_lock(&fc->lock);
@@ -604,8 +609,14 @@ int fuse_invalidate_files(struct fuse_conn *fc, u64 nodeid)
spin_unlock(&fpq->lock);
}
- wake_up(&fi->page_waitq); /* readpage[s] can wait on fuse wb */
spin_unlock(&fc->lock);
+#endif
+ spin_lock(&fi->lock);
+ list_for_each_entry(ff, &fi->rw_files, rw_entry)
+ fuse_revoke_readpages(ff);
+ spin_unlock(&fi->lock);
+
+ wake_up(&fi->page_waitq); /* readpage[s] can wait on fuse wb */
err = invalidate_inode_pages2(inode->i_mapping);
}
More information about the Devel
mailing list