All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] fuse: Forward vfs_fadvise to backing_file in passthrough mode
@ 2026-09-09 23:23 Shai Barack
  2026-09-10 12:40 ` Amir Goldstein
  0 siblings, 1 reply; 2+ messages in thread
From: Shai Barack @ 2026-09-09 23:23 UTC (permalink / raw)
  To: shayba
  Cc: Miklos Szeredi, Amir Goldstein, Bernd Schubert, Sandeep Dhavale,
	Akilesh Kailash, Suren Baghdasaryan, linux-fsdevel, linux-kernel

When an application invokes posix_fadvise(..., POSIX_FADV_DONTNEED) or
other fadvise advice on a FUSE passthrough file descriptor, the VFS
currently only executes generic_fadvise() on the upper FUSE inode
mapping.

Because FUSE passthrough does not forward fadvise operations to the
underlying backing file, pages residing in the lower filesystem's
pagecache remain resident in memory.

During media-heavy workloads (such as image thumbnail generation or
streaming), single-access file data accumulates in the inactive pagecache
of the lower filesystem. Under memory pressure, the kernel retains these
stale media pages and instead evicts active executable pages from
running processes, leading to severe direct reclaim latency spikes.

Architecturally, Linux VFS supports filesystem-specific fadvise
delegation via the .fadvise hook in struct file_operations. Implement
fuse_file_fadvise() in fs/fuse/file.c and register it in
fuse_file_operations. When passthrough is active on a fuse_file,
fuse_file_fadvise() forwards the vfs_fadvise() call to the lower
backing_file under the opened credentials, allowing POSIX_FADV_DONTNEED
to cleanly invalidate the physical backing filesystem pagecache.

Signed-off-by: Shai Barack <shayba@google.com>
Cc: Miklos Szeredi <miklos@szeredi.hu>
Cc: Amir Goldstein <amir73il@gmail.com>
Cc: Bernd Schubert <bschubert@ddn.com>
Cc: Sandeep Dhavale <dhavale@google.com>
Cc: Akilesh Kailash <akailash@google.com>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: linux-fsdevel@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
---
 fs/fuse/file.c        | 11 +++++++++++
 fs/fuse/fuse_i.h      |  7 +++++++
 fs/fuse/passthrough.c | 16 ++++++++++++++++
 3 files changed, 34 insertions(+)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index a4d736945..81709ab82 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -3537,6 +3537,16 @@ static ssize_t fuse_copy_file_range(struct file *src_file, loff_t src_off,
 	return ret;
 }
 
+static int fuse_file_fadvise(struct file *file, loff_t offset, loff_t len, int advice)
+{
+	struct fuse_file *ff = file->private_data;
+
+	if (fuse_file_passthrough(ff))
+		return fuse_passthrough_fadvise(ff, offset, len, advice);
+
+	return generic_fadvise(file, offset, len, advice);
+}
+
 static const struct file_operations fuse_file_operations = {
 	.llseek		= fuse_file_llseek,
 	.read_iter	= fuse_file_read_iter,
@@ -3557,6 +3567,7 @@ static const struct file_operations fuse_file_operations = {
 	.fallocate	= fuse_file_fallocate,
 	.copy_file_range = fuse_copy_file_range,
 	.fop_flags	= FOP_DONTCACHE,
+	.fadvise	= fuse_file_fadvise,
 };
 
 static const struct address_space_operations fuse_file_aops  = {
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 939553b34..50073e070 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1545,6 +1545,12 @@ static inline struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
 static inline void fuse_backing_put(struct fuse_backing *fb)
 {
 }
+
+static inline int fuse_passthrough_fadvise(struct fuse_file *ff, loff_t offset,
+					   loff_t len, int advice)
+{
+	return -EOPNOTSUPP;
+}
 #endif
 
 void fuse_backing_files_init(struct fuse_conn *fc);
@@ -1575,6 +1581,7 @@ ssize_t fuse_passthrough_splice_write(struct pipe_inode_info *pipe,
 				      struct file *out, loff_t *ppos,
 				      size_t len, unsigned int flags);
 ssize_t fuse_passthrough_mmap(struct file *file, struct vm_area_struct *vma);
+int fuse_passthrough_fadvise(struct fuse_file *ff, loff_t offset, loff_t len, int advice);
 
 /* backing.c */
 
diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
index 059e57969..8062110dd 100644
--- a/fs/fuse/passthrough.c
+++ b/fs/fuse/passthrough.c
@@ -382,3 +382,19 @@ void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb)
 	put_cred(ff->cred);
 	ff->cred = NULL;
 }
+
+int fuse_passthrough_fadvise(struct fuse_file *ff, loff_t offset, loff_t len, int advice)
+{
+	struct file *backing_file = fuse_file_passthrough(ff);
+	const struct cred *old_cred;
+	int ret;
+
+	if (!backing_file)
+		return -EINVAL;
+
+	old_cred = override_creds(ff->cred);
+	ret = vfs_fadvise(backing_file, offset, len, advice);
+	revert_creds(old_cred);
+
+	return ret;
+}
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH] fuse: Forward vfs_fadvise to backing_file in passthrough mode
  2026-09-09 23:23 [PATCH] fuse: Forward vfs_fadvise to backing_file in passthrough mode Shai Barack
@ 2026-09-10 12:40 ` Amir Goldstein
  0 siblings, 0 replies; 2+ messages in thread
From: Amir Goldstein @ 2026-09-10 12:40 UTC (permalink / raw)
  To: Shai Barack
  Cc: Miklos Szeredi, Bernd Schubert, Sandeep Dhavale, Akilesh Kailash,
	Suren Baghdasaryan, linux-fsdevel, linux-kernel, fuse-devel

On Thu, Sep 10, 2026 at 1:23 AM Shai Barack <shayba@google.com> wrote:
>
> When an application invokes posix_fadvise(..., POSIX_FADV_DONTNEED) or
> other fadvise advice on a FUSE passthrough file descriptor, the VFS
> currently only executes generic_fadvise() on the upper FUSE inode
> mapping.
>
> Because FUSE passthrough does not forward fadvise operations to the
> underlying backing file, pages residing in the lower filesystem's
> pagecache remain resident in memory.
>
> During media-heavy workloads (such as image thumbnail generation or
> streaming), single-access file data accumulates in the inactive pagecache
> of the lower filesystem. Under memory pressure, the kernel retains these
> stale media pages and instead evicts active executable pages from
> running processes, leading to severe direct reclaim latency spikes.
>
> Architecturally, Linux VFS supports filesystem-specific fadvise
> delegation via the .fadvise hook in struct file_operations. Implement
> fuse_file_fadvise() in fs/fuse/file.c and register it in
> fuse_file_operations. When passthrough is active on a fuse_file,
> fuse_file_fadvise() forwards the vfs_fadvise() call to the lower
> backing_file under the opened credentials, allowing POSIX_FADV_DONTNEED
> to cleanly invalidate the physical backing filesystem pagecache.
>
> Signed-off-by: Shai Barack <shayba@google.com>
> Cc: Miklos Szeredi <miklos@szeredi.hu>
> Cc: Amir Goldstein <amir73il@gmail.com>
> Cc: Bernd Schubert <bschubert@ddn.com>
> Cc: Sandeep Dhavale <dhavale@google.com>
> Cc: Akilesh Kailash <akailash@google.com>
> Cc: Suren Baghdasaryan <surenb@google.com>
> Cc: linux-fsdevel@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> ---

Hi Shai,

This makes sense.

Please CC fuse-devel with future posting.

See some nits below...

>  fs/fuse/file.c        | 11 +++++++++++
>  fs/fuse/fuse_i.h      |  7 +++++++
>  fs/fuse/passthrough.c | 16 ++++++++++++++++
>  3 files changed, 34 insertions(+)
>
> diff --git a/fs/fuse/file.c b/fs/fuse/file.c
> index a4d736945..81709ab82 100644
> --- a/fs/fuse/file.c
> +++ b/fs/fuse/file.c
> @@ -3537,6 +3537,16 @@ static ssize_t fuse_copy_file_range(struct file *src_file, loff_t src_off,
>         return ret;
>  }
>
> +static int fuse_file_fadvise(struct file *file, loff_t offset, loff_t len, int advice)
> +{
> +       struct fuse_file *ff = file->private_data;
> +
> +       if (fuse_file_passthrough(ff))
> +               return fuse_passthrough_fadvise(ff, offset, len, advice);
> +
> +       return generic_fadvise(file, offset, len, advice);
> +}
> +
>  static const struct file_operations fuse_file_operations = {
>         .llseek         = fuse_file_llseek,
>         .read_iter      = fuse_file_read_iter,
> @@ -3557,6 +3567,7 @@ static const struct file_operations fuse_file_operations = {
>         .fallocate      = fuse_file_fallocate,
>         .copy_file_range = fuse_copy_file_range,
>         .fop_flags      = FOP_DONTCACHE,
> +       .fadvise        = fuse_file_fadvise,
>  };
>
>  static const struct address_space_operations fuse_file_aops  = {
> diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
> index 939553b34..50073e070 100644
> --- a/fs/fuse/fuse_i.h
> +++ b/fs/fuse/fuse_i.h
> @@ -1545,6 +1545,12 @@ static inline struct fuse_backing *fuse_backing_get(struct fuse_backing *fb)
>  static inline void fuse_backing_put(struct fuse_backing *fb)
>  {
>  }
> +
> +static inline int fuse_passthrough_fadvise(struct fuse_file *ff, loff_t offset,
> +                                          loff_t len, int advice)
> +{
> +       return -EOPNOTSUPP;
> +}

Not needed, the same as fuse_passthrough_mmap stub is not needed because
if (fuse_file_passthrough()) branch is optimized out.

>  #endif
>
>  void fuse_backing_files_init(struct fuse_conn *fc);
> @@ -1575,6 +1581,7 @@ ssize_t fuse_passthrough_splice_write(struct pipe_inode_info *pipe,
>                                       struct file *out, loff_t *ppos,
>                                       size_t len, unsigned int flags);
>  ssize_t fuse_passthrough_mmap(struct file *file, struct vm_area_struct *vma);
> +int fuse_passthrough_fadvise(struct fuse_file *ff, loff_t offset, loff_t len, int advice);
>
>  /* backing.c */
>
> diff --git a/fs/fuse/passthrough.c b/fs/fuse/passthrough.c
> index 059e57969..8062110dd 100644
> --- a/fs/fuse/passthrough.c
> +++ b/fs/fuse/passthrough.c
> @@ -382,3 +382,19 @@ void fuse_passthrough_release(struct fuse_file *ff, struct fuse_backing *fb)
>         put_cred(ff->cred);
>         ff->cred = NULL;
>  }
> +
> +int fuse_passthrough_fadvise(struct fuse_file *ff, loff_t offset, loff_t len, int advice)
> +{
> +       struct file *backing_file = fuse_file_passthrough(ff);
> +       const struct cred *old_cred;
> +       int ret;
> +
> +       if (!backing_file)
> +               return -EINVAL;

Not needed. already checked.

> +
> +       old_cred = override_creds(ff->cred);
> +       ret = vfs_fadvise(backing_file, offset, len, advice);
> +       revert_creds(old_cred);
> +
> +       return ret;
> +}

Please reorder this above fuse_passthrough_open() along with the
other passthrough ops. open and release are special.

</nits>

This got me thinking - do we have any other purely page cache operations
that should be executed on the passthrough file?

There is one pattern that stands out: fuse_fsync()/fuse_flush() in case
server does not implement those ops.

Currently, they are mostly a noop for fuse passthrough files, but it
could make sense to passthrough these two ops on the backing file
same as overlayfs does.

This has implications on the use case that you described.
If an application wants to synchronically evict page cache of large file
that was recently written, POSIX_FADV_DONTNEED alone is not enough.

Some would claim that passthtough of fallocate() to backing file in case
server does not implement FUSE_FALLCOATE also makes sense,
but that will be a visible change of behavior from current -EOPNOTSUPP,
so I think this is better to do with an opt-in "passthrough ops mask".

Thanks,
Amir.

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-09-10 12:41 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-09 23:23 [PATCH] fuse: Forward vfs_fadvise to backing_file in passthrough mode Shai Barack
2026-09-10 12:40 ` Amir Goldstein

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.