Re: [PATCH v3 1/2] fuse: preserve FOPEN_PARALLEL_DIRECT_WRITES for passthrough opens
From: Amir Goldstein
Date: Fri Jun 19 2026 - 04:13:55 EST
On Wed, Jun 17, 2026 at 1:13 AM Russ Fellows <russ.fellows@xxxxxxxxx> wrote:
>
> fuse_file_io_open() clears FOPEN_PARALLEL_DIRECT_WRITES for any open
> that lacks FOPEN_DIRECT_IO. That rule is too strict for passthrough
> opens, which already bypass the page cache through the backing file
> and do not need FOPEN_DIRECT_IO to guarantee direct-I/O semantics.
> Clearing the flag before the passthrough write path sees it prevents
> the kernel from ever taking the shared-lock path for passthrough writes.
>
> Introduce FOPEN_IOMODE helper macros to classify the effective I/O mode
> of an open file:
>
> FOPEN_IOMODE_IS_CACHED(oflags) - page-cache I/O (neither flag)
> FOPEN_IOMODE_IS_DIRECT(oflags) - DIRECT_IO is set
> FOPEN_IOMODE_IS_PASSTHROUGH(oflags) - PASSTHROUGH only (no DIRECT_IO)
>
> Use FOPEN_IOMODE_IS_CACHED to express the PARALLEL_DIRECT_WRITES guard
> cleanly: the flag is only suppressed for cached iomode, not for passthrough
> or direct-IO mode.
>
> This is a prerequisite for passthrough write parallelism; without it the
> shared-lock path in the subsequent patch never activates.
>
> Signed-off-by: Russ Fellows <russ.fellows@xxxxxxxxx>
> ---
> fs/fuse/iomode.c | 30 +++++++++++++++++++++++++++---
> 1 file changed, 27 insertions(+), 3 deletions(-)
>
> diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c
> index c99e285f3..46e60b04c 100644
> --- a/fs/fuse/iomode.c
> +++ b/fs/fuse/iomode.c
> @@ -165,6 +165,23 @@ static void fuse_file_uncached_io_release(struct fuse_file *ff,
> (FOPEN_PASSTHROUGH | FOPEN_DIRECT_IO | FOPEN_PARALLEL_DIRECT_WRITES | \
> FOPEN_NOFLUSH)
>
> +/*
> + * Helpers to classify the effective I/O mode of an open file.
> + *
> + * FOPEN_IOMODE() - extract the mode-relevant bits
> + * FOPEN_IOMODE_IS_CACHED - neither DIRECT_IO nor PASSTHROUGH: page-cache I/O
> + * FOPEN_IOMODE_IS_DIRECT - DIRECT_IO is set (may also have PASSTHROUGH)
> + * FOPEN_IOMODE_IS_PASSTHROUGH - PASSTHROUGH only, without DIRECT_IO
> + */
> +#define FOPEN_IOMODE(oflags) \
> + ((oflags) & (FOPEN_DIRECT_IO | FOPEN_PASSTHROUGH))
> +#define FOPEN_IOMODE_IS_CACHED(oflags) \
> + (FOPEN_IOMODE(oflags) == 0)
> +#define FOPEN_IOMODE_IS_DIRECT(oflags) \
> + (FOPEN_IOMODE(oflags) & FOPEN_DIRECT_IO)
> +#define FOPEN_IOMODE_IS_PASSTHROUGH(oflags) \
> + (FOPEN_IOMODE(oflags) == FOPEN_PASSTHROUGH)
> +
Sorry, mad bad I gave you wrong code samples.
The open iomode is not about how the io is performed direct/passthrough
but it is about the state ff->iomode, so the FOPEN_IOMODE_IS_DIRECT()
is misleading and it does not make the patch simpler which was the intention.
Better loose all except FOPEN_IOMODE_CACHED() to avoid ambiguity.
> static int fuse_file_passthrough_open(struct inode *inode, struct file *file)
> {
> struct fuse_file *ff = file->private_data;
> @@ -216,9 +233,11 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
> goto fail;
>
> /*
> - * FOPEN_PARALLEL_DIRECT_WRITES requires FOPEN_DIRECT_IO.
> + * FOPEN_PARALLEL_DIRECT_WRITES is not supported with cached iomode.
> + * Passthrough and direct-IO both bypass the page cache, so both are
> + * eligible to enable parallel direct writes.
> */
> - if (!(ff->open_flags & FOPEN_DIRECT_IO))
> + if (FOPEN_IOMODE_IS_CACHED(ff->open_flags))
> ff->open_flags &= ~FOPEN_PARALLEL_DIRECT_WRITES;
>
> /*
> @@ -228,8 +247,13 @@ int fuse_file_io_open(struct file *file, struct inode *inode)
> * Note that if user opens a file open with O_DIRECT, but server did
> * not specify FOPEN_DIRECT_IO, a later fcntl() could remove O_DIRECT,
> * so we put the inode in caching mode to prevent parallel dio.
Merge this "Note" into the comment above about FOPEN_PARALLEL_DIRECT_WRITES.
It is out of context here.
> + *
> + * Pure direct-IO (DIRECT_IO without PASSTHROUGH) needs no page-cache
> + * iomode machinery — return early. When DIRECT_IO and PASSTHROUGH are
> + * both set, write_iter uses the direct-IO path but the backing file
> + * still needs to be opened below.
Loose this text, code is self explanatory
> */
> - if ((ff->open_flags & FOPEN_DIRECT_IO) &&
> + if (FOPEN_IOMODE_IS_DIRECT(ff->open_flags) &&
> !(ff->open_flags & FOPEN_PASSTHROUGH))
> return 0;
Sigh, this function is a mess, see my untested patch below to make it simpler
Thanks,
Amir.
--- a/fs/fuse/iomode.c
+++ b/fs/fuse/iomode.c
@@ -192,12 +192,18 @@ static int fuse_file_passthrough_open(struct
inode *inode, struct file *file)
return err;
}
+/* Fuse uses page cache if no passthrough nor direct_io open specified */
+#define FOPEN_IOMODE(oflags) \
+ ((oflags) & (FOPEN_DIRECT_IO | FOPEN_PASSTHROUGH))
+#define FOPEN_IOMODE_CACHED(oflags) \
+ (FOPEN_IOMODE(oflags) == 0)
+
/* Request access to submit new io to inode via open file */
int fuse_file_io_open(struct file *file, struct inode *inode)
{
struct fuse_file *ff = file->private_data;
struct fuse_inode *fi = get_fuse_inode(inode);
- int err;
+ int err = 0;
/*
* io modes are not relevant with DAX and with server that does not
@@ -216,25 +222,20 @@ int fuse_file_io_open(struct file *file, struct
inode *inode)
/*
* FOPEN_PARALLEL_DIRECT_WRITES requires FOPEN_DIRECT_IO.
+ * Note that if user opens a file open with O_DIRECT, but server did
+ * not specify FOPEN_DIRECT_IO, a later fcntl() could remove O_DIRECT,
+ * so we put the inode in caching mode to prevent parallel dio.
*/
- if (!(ff->open_flags & FOPEN_DIRECT_IO))
+ if (FOPEN_IOMODE_CACHED(ff->open_flags))
ff->open_flags &= ~FOPEN_PARALLEL_DIRECT_WRITES;
/*
* First passthrough file open denies caching inode io mode.
* First caching file open enters caching inode io mode.
- *
- * Note that if user opens a file open with O_DIRECT, but server did
- * not specify FOPEN_DIRECT_IO, a later fcntl() could remove O_DIRECT,
- * so we put the inode in caching mode to prevent parallel dio.
*/
- if ((ff->open_flags & FOPEN_DIRECT_IO) &&
- !(ff->open_flags & FOPEN_PASSTHROUGH))
- return 0;
-
if (ff->open_flags & FOPEN_PASSTHROUGH)
err = fuse_file_passthrough_open(inode, file);
- else
+ else if (FOPEN_IOMODE_CACHED(ff->open_flags))
err = fuse_file_cached_io_open(inode, ff);
if (err)
goto fail;