]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
fuse: invalidate page cache after DIO and async DIO writes
authorCheng Ding <cding@ddn.com>
Mon, 20 Apr 2026 08:39:34 +0000 (16:39 +0800)
committerMiklos Szeredi <mszeredi@redhat.com>
Mon, 15 Jun 2026 12:06:20 +0000 (14:06 +0200)
This fixe does page cache invalidation after DIO and async DIO writes for
both O_DIRECT and FOPEN_DIRECT_IO cases.

Commit b359af8275a9 ("fuse: Invalidate the page cache after FOPEN_DIRECT_IO
write") fixed xfstests generic/209 for DIO writes in the FOPEN_DIRECT_IO
path. DIO writes without FOPEN_DIRECT_IO are already handled by
generic_file_direct_write().
However, async DIO writes (xfstests generic/451) remain unhandled.

After this fix:
- Async write with FUSE_ASYNC_DIO:
    invalidate in fuse_aio_invalidate_worker()

- Otherwise (Sync or async write without FUSE_ASYNC_DIO):
    - With FOPEN_DIRECT_IO:
        invalidate in fuse_direct_write_iter()
    - Without FOPEN_DIRECT_IO:
        invalidate in generic_file_direct_write()

Workqueue is required for async write invalidation to prevent deadlock:
calling it directly in the I/O end routine (which is in fuse worker thread
context) can block on a folio lock held by a buffered I/O thread waiting
for the same fuse worker thread.

Co-developed-by: Jingbo Xu <jefflexu@linux.alibaba.com>
Signed-off-by: Jingbo Xu <jefflexu@linux.alibaba.com>
Signed-off-by: Cheng Ding <cding@ddn.com>
Reviewed-by: Jingbo Xu <jefflexu@linux.alibaba.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/file.c
fs/fuse/fuse_i.h
fs/internal.h
include/linux/fs/super.h

index 9160836664b0ac0e35fa82bf8c2a6415fa7ecb4c..cb8da4c06d17e0729e33906f572360275ae53085 100644 (file)
@@ -639,6 +639,19 @@ static ssize_t fuse_get_res_by_io(struct fuse_io_priv *io)
        return io->bytes < 0 ? io->size : io->bytes;
 }
 
+static void fuse_aio_invalidate_worker(struct work_struct *work)
+{
+       struct fuse_io_priv *io = container_of(work, struct fuse_io_priv, work);
+       struct address_space *mapping = io->iocb->ki_filp->f_mapping;
+       ssize_t res = fuse_get_res_by_io(io);
+       pgoff_t start = io->offset >> PAGE_SHIFT;
+       pgoff_t end = (io->offset + res - 1) >> PAGE_SHIFT;
+
+       invalidate_inode_pages2_range(mapping, start, end);
+       io->iocb->ki_complete(io->iocb, res);
+       kref_put(&io->refcnt, fuse_io_release);
+}
+
 /*
  * In case of short read, the caller sets 'pos' to the position of
  * actual end of fuse request in IO request. Otherwise, if bytes_requested
@@ -671,10 +684,11 @@ static void fuse_aio_complete(struct fuse_io_priv *io, int err, ssize_t pos)
        spin_unlock(&io->lock);
 
        if (!left && !io->blocking) {
+               struct inode *inode = file_inode(io->iocb->ki_filp);
+               struct address_space *mapping = io->iocb->ki_filp->f_mapping;
                ssize_t res = fuse_get_res_by_io(io);
 
                if (res >= 0) {
-                       struct inode *inode = file_inode(io->iocb->ki_filp);
                        struct fuse_conn *fc = get_fuse_conn(inode);
                        struct fuse_inode *fi = get_fuse_inode(inode);
 
@@ -683,6 +697,17 @@ static void fuse_aio_complete(struct fuse_io_priv *io, int err, ssize_t pos)
                        spin_unlock(&fi->lock);
                }
 
+               if (io->write && res > 0 && mapping->nrpages) {
+                       /*
+                        * As in generic_file_direct_write(), invalidate after the
+                        * write, to invalidate read-ahead cache that may have competed
+                        * with the write.
+                        */
+                       INIT_WORK(&io->work, fuse_aio_invalidate_worker);
+                       queue_work(inode->i_sb->s_dio_done_wq, &io->work);
+                       return;
+               }
+
                io->iocb->ki_complete(io->iocb, res);
        }
 
@@ -1745,15 +1770,6 @@ ssize_t fuse_direct_io(struct fuse_io_priv *io, struct iov_iter *iter,
        if (res > 0)
                *ppos = pos;
 
-       if (res > 0 && write && fopen_direct_io) {
-               /*
-                * As in generic_file_direct_write(), invalidate after the
-                * write, to invalidate read-ahead cache that may have competed
-                * with the write.
-                */
-               invalidate_inode_pages2_range(mapping, idx_from, idx_to);
-       }
-
        return res > 0 ? res : err;
 }
 EXPORT_SYMBOL_GPL(fuse_direct_io);
@@ -1792,6 +1808,8 @@ static ssize_t fuse_direct_read_iter(struct kiocb *iocb, struct iov_iter *to)
 static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
 {
        struct inode *inode = file_inode(iocb->ki_filp);
+       struct address_space *mapping = inode->i_mapping;
+       loff_t pos = iocb->ki_pos;
        ssize_t res;
        bool exclusive;
 
@@ -1808,6 +1826,16 @@ static ssize_t fuse_direct_write_iter(struct kiocb *iocb, struct iov_iter *from)
                                             FUSE_DIO_WRITE);
                        fuse_write_update_attr(inode, iocb->ki_pos, res);
                }
+               if (res > 0 && mapping->nrpages) {
+                       /*
+                        * As in generic_file_direct_write(), invalidate after
+                        * write, to invalidate read-ahead cache that may have
+                        * with the write.
+                        */
+                       invalidate_inode_pages2_range(mapping,
+                               pos >> PAGE_SHIFT,
+                               (pos + res - 1) >> PAGE_SHIFT);
+               }
        }
        fuse_dio_unlock(iocb, exclusive);
 
@@ -2714,6 +2742,7 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
        size_t count = iov_iter_count(iter), shortened = 0;
        loff_t offset = iocb->ki_pos;
        struct fuse_io_priv *io;
+       bool async = ff->fm->fc->async_dio;
 
        pos = offset;
        inode = file->f_mapping->host;
@@ -2722,6 +2751,12 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
        if ((iov_iter_rw(iter) == READ) && (offset >= i_size))
                return 0;
 
+       if ((iov_iter_rw(iter) == WRITE) && async && !inode->i_sb->s_dio_done_wq) {
+               ret = sb_init_dio_done_wq(inode->i_sb);
+               if (ret < 0)
+                       return ret;
+       }
+
        io = kmalloc_obj(struct fuse_io_priv);
        if (!io)
                return -ENOMEM;
@@ -2737,7 +2772,7 @@ fuse_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
         * By default, we want to optimize all I/Os with async request
         * submission to the client filesystem if supported.
         */
-       io->async = ff->fm->fc->async_dio;
+       io->async = async;
        io->iocb = iocb;
        io->blocking = is_sync_kiocb(iocb);
 
index 30129d798088f49bdea869bfedb6aecd9ccb7f1a..6455617b74a0c6741184dfb50385cbac13a67ff8 100644 (file)
@@ -337,6 +337,7 @@ union fuse_file_args {
 /** The request IO state (for asynchronous processing) */
 struct fuse_io_priv {
        struct kref refcnt;
+       struct work_struct work;
        int async;
        spinlock_t lock;
        unsigned reqs;
index d77578d66d4284d85626578b98b5db9bb6082bd4..355d93f92208617b3f2b2c5d32d181bc17ffd5a0 100644 (file)
@@ -138,7 +138,6 @@ extern bool super_trylock_shared(struct super_block *sb);
 struct super_block *user_get_super(dev_t, bool excl);
 void put_super(struct super_block *sb);
 extern bool mount_capable(struct fs_context *);
-int sb_init_dio_done_wq(struct super_block *sb);
 
 /*
  * Prepare superblock for changing its read-only state (i.e., either remount
index f21ffbb6dea5bcdd23494bd6fb907ae70a142b34..4056126781151eeb0df075163fa8334885cd399f 100644 (file)
@@ -235,4 +235,6 @@ int freeze_super(struct super_block *super, enum freeze_holder who,
 int thaw_super(struct super_block *super, enum freeze_holder who,
               const void *freeze_owner);
 
+int sb_init_dio_done_wq(struct super_block *sb);
+
 #endif /* _LINUX_FS_SUPER_H */