* [PATCH 0/3] FUSE passthrough fixes
@ 2024-04-07 15:57 Amir Goldstein
2024-04-07 15:57 ` [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write Amir Goldstein
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Amir Goldstein @ 2024-04-07 15:57 UTC (permalink / raw)
To: Miklos Szeredi; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel
Miklos,
While going over the code to prepare for getattr() passthrough I
experienced a WTF moment that resulted in the two fix patches.
Patch 3/3 is included for reference and to give Sweet Tea a starting
point for getattr() passthrough.
What puzzled me is that some ff->iomode state bugs were so blunt that
I needed to figure out how I did not see any WARN_ON in my tests of rc1.
There are different reasons for different types of bugs.
1. For concurrent dio writes without any passthrough open,
fuse_file_cached_io_start() was supposed to hit
WARN_ON(ff->iomode == IOM_UNCACHED) if there is already a dio write
in-flight.
My conclusion is that the set of fstests that run on passthrough_hp,
on my small test VM do not excercise concurrent dio writes.
2. For dio write, where the file was opened passthrough, every write
was going to hit WARN_ON(ff->iomode == IOM_UNCACHED) and also
fuse_file_cached_io_end() was going to set ff->iomode == IOM_NONE
and leak the fuse_backing object.
However, the bug fixed by patch 2/3 made sure that parallel dio write
would always fallback to exclusive dio if file was open with a backing
file.
Testing:
I ran fstests with passthrough_hp with options:
1) FOPEN_PASSTHROUGH
2) FOPEN_DIRECT_IO | FOPEN_PARALLEL_DIRECT_WRITES
3) FOPEN_PASSTHROUGH | FOPEN_DIRECT_IO | FOPEN_PARALLEL_DIRECT_WRITES
Did not observe any regressions (not any improvments) from rc1.
Ran some multi threads aiodio tests with just patch 2/3 and the
assertions in fuse_evict_inode() from patch 3/3.
First two configs did not hit any assertion.
The passthrough+direct_io+parallel_direct_writes config always
hits the assertion in fuse_file_cached_io_start() and always hits
the leaked fuse_backing assertion in fuse_evict_inode().
Bernd do you have different tests to cover concurrent dio writes in
your setup? Any ideas on how to improve the fstests test coverage?
Thanks,
Amir.
Amir Goldstein (3):
fuse: fix wrong ff->iomode state changes from parallel dio write
fuse: fix parallel dio write on file open in passthrough mode
fuse: prepare for long lived reference on backing file
fs/fuse/file.c | 18 +++++++-----
fs/fuse/fuse_i.h | 10 +++++--
fs/fuse/inode.c | 7 +++++
fs/fuse/iomode.c | 73 +++++++++++++++++++++++++++++++++---------------
4 files changed, 76 insertions(+), 32 deletions(-)
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write 2024-04-07 15:57 [PATCH 0/3] FUSE passthrough fixes Amir Goldstein @ 2024-04-07 15:57 ` Amir Goldstein 2024-04-09 13:33 ` Miklos Szeredi 2024-04-07 15:57 ` [PATCH 2/3] fuse: fix parallel dio write on file open in passthrough mode Amir Goldstein 2024-04-07 15:57 ` [PATCH 3/3] fuse: prepare for long lived reference on backing file Amir Goldstein 2 siblings, 1 reply; 10+ messages in thread From: Amir Goldstein @ 2024-04-07 15:57 UTC (permalink / raw) To: Miklos Szeredi; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel There is a confusion with fuse_file_uncached_io_{start,end} interface. These helpers do two things when called from passthrough open()/release(): 1. Take/drop negative refcount of fi->iocachectr (inode uncached io mode) 2. State change ff->iomode IOM_NONE <-> IOM_UNCACHED (file uncached open) The calls from parallel dio write path need to take a reference on fi->iocachectr, but they should not be changing ff->iomode state, because in this case, the fi->iocachectr reference does not stick around until file release(). Factor out helpers fuse_inode_uncached_io_{start,end}, to be used from parallel dio write path and rename fuse_file_*cached_io_{start,end} helpers to fuse_file_*cached_io_{open,release} to clarify the difference. Add a check of ff->iomode in mmap(), so that fuse_file_cached_io_open() is called only on first mmap of direct_io file. Fixes: 205c1d802683 ("fuse: allow parallel dio writes with FUSE_DIRECT_IO_ALLOW_MMAP") Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- fs/fuse/file.c | 18 +++++++++------ fs/fuse/fuse_i.h | 7 +++--- fs/fuse/inode.c | 1 + fs/fuse/iomode.c | 59 ++++++++++++++++++++++++++++++++---------------- 4 files changed, 56 insertions(+), 29 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index a56e7bffd000..fcf20b304093 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1362,7 +1362,7 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, bool *exclusive) { struct inode *inode = file_inode(iocb->ki_filp); - struct fuse_file *ff = iocb->ki_filp->private_data; + struct fuse_inode *fi = get_fuse_inode(inode); *exclusive = fuse_dio_wr_exclusive_lock(iocb, from); if (*exclusive) { @@ -1377,7 +1377,7 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, * have raced, so check it again. */ if (fuse_io_past_eof(iocb, from) || - fuse_file_uncached_io_start(inode, ff, NULL) != 0) { + fuse_inode_uncached_io_start(fi, NULL) != 0) { inode_unlock_shared(inode); inode_lock(inode); *exclusive = true; @@ -1388,13 +1388,13 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, static void fuse_dio_unlock(struct kiocb *iocb, bool exclusive) { struct inode *inode = file_inode(iocb->ki_filp); - struct fuse_file *ff = iocb->ki_filp->private_data; + struct fuse_inode *fi = get_fuse_inode(inode); if (exclusive) { inode_unlock(inode); } else { /* Allow opens in caching mode after last parallel dio end */ - fuse_file_uncached_io_end(inode, ff); + fuse_inode_uncached_io_end(fi); inode_unlock_shared(inode); } } @@ -2574,10 +2574,14 @@ static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma) * First mmap of direct_io file enters caching inode io mode. * Also waits for parallel dio writers to go into serial mode * (exclusive instead of shared lock). + * After first mmap, the inode stays in caching io mode until + * the direct_io file release. */ - rc = fuse_file_cached_io_start(inode, ff); - if (rc) - return rc; + if (READ_ONCE(ff->iomode) != IOM_CACHED) { + rc = fuse_file_cached_io_open(inode, ff); + if (rc) + return rc; + } } if ((vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE)) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index b24084b60864..f23919610313 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1394,9 +1394,10 @@ int fuse_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry, struct fileattr *fa); /* iomode.c */ -int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff); -int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff, struct fuse_backing *fb); -void fuse_file_uncached_io_end(struct inode *inode, struct fuse_file *ff); +int fuse_file_cached_io_open(struct inode *inode, struct fuse_file *ff); +int fuse_inode_uncached_io_start(struct fuse_inode *fi, + struct fuse_backing *fb); +void fuse_inode_uncached_io_end(struct fuse_inode *fi); int fuse_file_io_open(struct file *file, struct inode *inode); void fuse_file_io_release(struct fuse_file *ff, struct inode *inode); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 3a5d88878335..99e44ea7d875 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -175,6 +175,7 @@ static void fuse_evict_inode(struct inode *inode) } } if (S_ISREG(inode->i_mode) && !fuse_is_bad(inode)) { + WARN_ON(fi->iocachectr != 0); WARN_ON(!list_empty(&fi->write_files)); WARN_ON(!list_empty(&fi->queued_writes)); } diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c index c653ddcf0578..1519f895f0a9 100644 --- a/fs/fuse/iomode.c +++ b/fs/fuse/iomode.c @@ -21,12 +21,13 @@ static inline bool fuse_is_io_cache_wait(struct fuse_inode *fi) } /* - * Start cached io mode. + * Called on cached file open() and on first mmap() of direct_io file. + * Takes cached_io inode mode reference to be dropped on file release. * * Blocks new parallel dio writes and waits for the in-progress parallel dio * writes to complete. */ -int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff) +int fuse_file_cached_io_open(struct inode *inode, struct fuse_file *ff) { struct fuse_inode *fi = get_fuse_inode(inode); @@ -56,8 +57,7 @@ int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff) return -ETXTBSY; } - WARN_ON(ff->iomode == IOM_UNCACHED); - if (ff->iomode == IOM_NONE) { + if (!WARN_ON(ff->iomode != IOM_NONE)) { ff->iomode = IOM_CACHED; if (fi->iocachectr == 0) set_bit(FUSE_I_CACHE_IO_MODE, &fi->state); @@ -67,10 +67,9 @@ int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff) return 0; } -static void fuse_file_cached_io_end(struct inode *inode, struct fuse_file *ff) +static void fuse_file_cached_io_release(struct fuse_file *ff, + struct fuse_inode *fi) { - struct fuse_inode *fi = get_fuse_inode(inode); - spin_lock(&fi->lock); WARN_ON(fi->iocachectr <= 0); WARN_ON(ff->iomode != IOM_CACHED); @@ -82,9 +81,8 @@ static void fuse_file_cached_io_end(struct inode *inode, struct fuse_file *ff) } /* Start strictly uncached io mode where cache access is not allowed */ -int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff, struct fuse_backing *fb) +int fuse_inode_uncached_io_start(struct fuse_inode *fi, struct fuse_backing *fb) { - struct fuse_inode *fi = get_fuse_inode(inode); struct fuse_backing *oldfb; int err = 0; @@ -99,9 +97,7 @@ int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff, struc err = -ETXTBSY; goto unlock; } - WARN_ON(ff->iomode != IOM_NONE); fi->iocachectr--; - ff->iomode = IOM_UNCACHED; /* fuse inode holds a single refcount of backing file */ if (!oldfb) { @@ -115,15 +111,29 @@ int fuse_file_uncached_io_start(struct inode *inode, struct fuse_file *ff, struc return err; } -void fuse_file_uncached_io_end(struct inode *inode, struct fuse_file *ff) +/* Takes uncached_io inode mode reference to be dropped on file release */ +static int fuse_file_uncached_io_open(struct inode *inode, + struct fuse_file *ff, + struct fuse_backing *fb) { struct fuse_inode *fi = get_fuse_inode(inode); + int err; + + err = fuse_inode_uncached_io_start(fi, fb); + if (err) + return err; + + WARN_ON(ff->iomode != IOM_NONE); + ff->iomode = IOM_UNCACHED; + return 0; +} + +void fuse_inode_uncached_io_end(struct fuse_inode *fi) +{ struct fuse_backing *oldfb = NULL; spin_lock(&fi->lock); WARN_ON(fi->iocachectr >= 0); - WARN_ON(ff->iomode != IOM_UNCACHED); - ff->iomode = IOM_NONE; fi->iocachectr++; if (!fi->iocachectr) { wake_up(&fi->direct_io_waitq); @@ -134,6 +144,15 @@ void fuse_file_uncached_io_end(struct inode *inode, struct fuse_file *ff) fuse_backing_put(oldfb); } +/* Drop uncached_io reference from passthrough open */ +static void fuse_file_uncached_io_release(struct fuse_file *ff, + struct fuse_inode *fi) +{ + WARN_ON(ff->iomode != IOM_UNCACHED); + ff->iomode = IOM_NONE; + fuse_inode_uncached_io_end(fi); +} + /* * Open flags that are allowed in combination with FOPEN_PASSTHROUGH. * A combination of FOPEN_PASSTHROUGH and FOPEN_DIRECT_IO means that read/write @@ -163,7 +182,7 @@ static int fuse_file_passthrough_open(struct inode *inode, struct file *file) return PTR_ERR(fb); /* First passthrough file open denies caching inode io mode */ - err = fuse_file_uncached_io_start(inode, ff, fb); + err = fuse_file_uncached_io_open(inode, ff, fb); if (!err) return 0; @@ -216,7 +235,7 @@ int fuse_file_io_open(struct file *file, struct inode *inode) if (ff->open_flags & FOPEN_PASSTHROUGH) err = fuse_file_passthrough_open(inode, file); else - err = fuse_file_cached_io_start(inode, ff); + err = fuse_file_cached_io_open(inode, ff); if (err) goto fail; @@ -236,8 +255,10 @@ int fuse_file_io_open(struct file *file, struct inode *inode) /* No more pending io and no new io possible to inode via open/mmapped file */ void fuse_file_io_release(struct fuse_file *ff, struct inode *inode) { + struct fuse_inode *fi = get_fuse_inode(inode); + /* - * Last parallel dio close allows caching inode io mode. + * Last passthrough file close allows caching inode io mode. * Last caching file close exits caching inode io mode. */ switch (ff->iomode) { @@ -245,10 +266,10 @@ void fuse_file_io_release(struct fuse_file *ff, struct inode *inode) /* Nothing to do */ break; case IOM_UNCACHED: - fuse_file_uncached_io_end(inode, ff); + fuse_file_uncached_io_release(ff, fi); break; case IOM_CACHED: - fuse_file_cached_io_end(inode, ff); + fuse_file_cached_io_release(ff, fi); break; } } -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write 2024-04-07 15:57 ` [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write Amir Goldstein @ 2024-04-09 13:33 ` Miklos Szeredi 2024-04-09 15:10 ` Amir Goldstein 0 siblings, 1 reply; 10+ messages in thread From: Miklos Szeredi @ 2024-04-09 13:33 UTC (permalink / raw) To: Amir Goldstein; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel On Sun, 7 Apr 2024 at 17:58, Amir Goldstein <amir73il@gmail.com> wrote: > > There is a confusion with fuse_file_uncached_io_{start,end} interface. > These helpers do two things when called from passthrough open()/release(): > 1. Take/drop negative refcount of fi->iocachectr (inode uncached io mode) > 2. State change ff->iomode IOM_NONE <-> IOM_UNCACHED (file uncached open) > > The calls from parallel dio write path need to take a reference on > fi->iocachectr, but they should not be changing ff->iomode state, > because in this case, the fi->iocachectr reference does not stick around > until file release(). Okay. > > Factor out helpers fuse_inode_uncached_io_{start,end}, to be used from > parallel dio write path and rename fuse_file_*cached_io_{start,end} > helpers to fuse_file_*cached_io_{open,release} to clarify the difference. > > Add a check of ff->iomode in mmap(), so that fuse_file_cached_io_open() > is called only on first mmap of direct_io file. Is this supposed to be an optimization? AFAICS it's wrong, because it moves the check outside of any relevant locks. > @@ -56,8 +57,7 @@ int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff) > return -ETXTBSY; > } > > - WARN_ON(ff->iomode == IOM_UNCACHED); > - if (ff->iomode == IOM_NONE) { > + if (!WARN_ON(ff->iomode != IOM_NONE)) { This double negation is ugly. Just let the compiler optimize away the second comparison. Thanks, Miklos ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write 2024-04-09 13:33 ` Miklos Szeredi @ 2024-04-09 15:10 ` Amir Goldstein 2024-04-09 15:32 ` Miklos Szeredi 0 siblings, 1 reply; 10+ messages in thread From: Amir Goldstein @ 2024-04-09 15:10 UTC (permalink / raw) To: Miklos Szeredi; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel On Tue, Apr 9, 2024 at 4:33 PM Miklos Szeredi <miklos@szeredi.hu> wrote: > > On Sun, 7 Apr 2024 at 17:58, Amir Goldstein <amir73il@gmail.com> wrote: > > > > There is a confusion with fuse_file_uncached_io_{start,end} interface. > > These helpers do two things when called from passthrough open()/release(): > > 1. Take/drop negative refcount of fi->iocachectr (inode uncached io mode) > > 2. State change ff->iomode IOM_NONE <-> IOM_UNCACHED (file uncached open) > > > > The calls from parallel dio write path need to take a reference on > > fi->iocachectr, but they should not be changing ff->iomode state, > > because in this case, the fi->iocachectr reference does not stick around > > until file release(). > > Okay. > > > > > Factor out helpers fuse_inode_uncached_io_{start,end}, to be used from > > parallel dio write path and rename fuse_file_*cached_io_{start,end} > > helpers to fuse_file_*cached_io_{open,release} to clarify the difference. > > > > Add a check of ff->iomode in mmap(), so that fuse_file_cached_io_open() > > is called only on first mmap of direct_io file. > > Is this supposed to be an optimization? No. The reason I did this is because I wanted to differentiate the refcount semantics (start/end) from the state semantics (open/release) and to make it clearer that there is only one state change and refcount increment on the first mmap(). > AFAICS it's wrong, because it > moves the check outside of any relevant locks. > Aren't concurrent mmap serialized on some lock? Anyway, I think that the only "bug" that this can trigger is the WARN_ON(ff->iomode != IOM_NONE) so if we .... > > > @@ -56,8 +57,7 @@ int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff) > > return -ETXTBSY; > > } > > > > - WARN_ON(ff->iomode == IOM_UNCACHED); > > - if (ff->iomode == IOM_NONE) { > > + if (!WARN_ON(ff->iomode != IOM_NONE)) { > > This double negation is ugly. Just let the compiler optimize away the > second comparison. ...drop this change, we should be good. If you agree, do you need me to re-post? Thanks, Amir. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write 2024-04-09 15:10 ` Amir Goldstein @ 2024-04-09 15:32 ` Miklos Szeredi 2024-04-09 16:18 ` Amir Goldstein 0 siblings, 1 reply; 10+ messages in thread From: Miklos Szeredi @ 2024-04-09 15:32 UTC (permalink / raw) To: Amir Goldstein; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel On Tue, 9 Apr 2024 at 17:10, Amir Goldstein <amir73il@gmail.com> wrote: > > On Tue, Apr 9, 2024 at 4:33 PM Miklos Szeredi <miklos@szeredi.hu> wrote: > > > > On Sun, 7 Apr 2024 at 17:58, Amir Goldstein <amir73il@gmail.com> wrote: > > > > > > There is a confusion with fuse_file_uncached_io_{start,end} interface. > > > These helpers do two things when called from passthrough open()/release(): > > > 1. Take/drop negative refcount of fi->iocachectr (inode uncached io mode) > > > 2. State change ff->iomode IOM_NONE <-> IOM_UNCACHED (file uncached open) > > > > > > The calls from parallel dio write path need to take a reference on > > > fi->iocachectr, but they should not be changing ff->iomode state, > > > because in this case, the fi->iocachectr reference does not stick around > > > until file release(). > > > > Okay. > > > > > > > > Factor out helpers fuse_inode_uncached_io_{start,end}, to be used from > > > parallel dio write path and rename fuse_file_*cached_io_{start,end} > > > helpers to fuse_file_*cached_io_{open,release} to clarify the difference. > > > > > > Add a check of ff->iomode in mmap(), so that fuse_file_cached_io_open() > > > is called only on first mmap of direct_io file. > > > > Is this supposed to be an optimization? > > No. > The reason I did this is because I wanted to differentiate > the refcount semantics (start/end) > from the state semantics (open/release) > and to make it clearer that there is only one state change > and refcount increment on the first mmap(). > > > AFAICS it's wrong, because it > > moves the check outside of any relevant locks. > > > > Aren't concurrent mmap serialized on some lock? Only on current->mm, which doesn't serialize mmaps of the same file in different processes. > > Anyway, I think that the only "bug" that this can trigger is the > WARN_ON(ff->iomode != IOM_NONE) > so if we .... > > > > > > @@ -56,8 +57,7 @@ int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff) > > > return -ETXTBSY; > > > } > > > > > > - WARN_ON(ff->iomode == IOM_UNCACHED); > > > - if (ff->iomode == IOM_NONE) { > > > + if (!WARN_ON(ff->iomode != IOM_NONE)) { > > > > This double negation is ugly. Just let the compiler optimize away the > > second comparison. > > ...drop this change, we should be good. > > If you agree, do you need me to re-post? Okay, but then what's the point of the unlocked check? Thanks, Miklos ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write 2024-04-09 15:32 ` Miklos Szeredi @ 2024-04-09 16:18 ` Amir Goldstein 2024-04-13 6:50 ` Amir Goldstein 0 siblings, 1 reply; 10+ messages in thread From: Amir Goldstein @ 2024-04-09 16:18 UTC (permalink / raw) To: Miklos Szeredi; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel On Tue, Apr 9, 2024 at 6:32 PM Miklos Szeredi <miklos@szeredi.hu> wrote: > > On Tue, 9 Apr 2024 at 17:10, Amir Goldstein <amir73il@gmail.com> wrote: > > > > On Tue, Apr 9, 2024 at 4:33 PM Miklos Szeredi <miklos@szeredi.hu> wrote: > > > > > > On Sun, 7 Apr 2024 at 17:58, Amir Goldstein <amir73il@gmail.com> wrote: > > > > > > > > There is a confusion with fuse_file_uncached_io_{start,end} interface. > > > > These helpers do two things when called from passthrough open()/release(): > > > > 1. Take/drop negative refcount of fi->iocachectr (inode uncached io mode) > > > > 2. State change ff->iomode IOM_NONE <-> IOM_UNCACHED (file uncached open) > > > > > > > > The calls from parallel dio write path need to take a reference on > > > > fi->iocachectr, but they should not be changing ff->iomode state, > > > > because in this case, the fi->iocachectr reference does not stick around > > > > until file release(). > > > > > > Okay. > > > > > > > > > > > Factor out helpers fuse_inode_uncached_io_{start,end}, to be used from > > > > parallel dio write path and rename fuse_file_*cached_io_{start,end} > > > > helpers to fuse_file_*cached_io_{open,release} to clarify the difference. > > > > > > > > Add a check of ff->iomode in mmap(), so that fuse_file_cached_io_open() > > > > is called only on first mmap of direct_io file. > > > > > > Is this supposed to be an optimization? > > > > No. > > The reason I did this is because I wanted to differentiate > > the refcount semantics (start/end) > > from the state semantics (open/release) > > and to make it clearer that there is only one state change > > and refcount increment on the first mmap(). > > > > > AFAICS it's wrong, because it > > > moves the check outside of any relevant locks. > > > > > > > Aren't concurrent mmap serialized on some lock? > > Only on current->mm, which doesn't serialize mmaps of the same file in > different processes. > > > > > Anyway, I think that the only "bug" that this can trigger is the > > WARN_ON(ff->iomode != IOM_NONE) > > so if we .... > > > > > > > > > @@ -56,8 +57,7 @@ int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff) > > > > return -ETXTBSY; > > > > } > > > > > > > > - WARN_ON(ff->iomode == IOM_UNCACHED); > > > > - if (ff->iomode == IOM_NONE) { > > > > + if (!WARN_ON(ff->iomode != IOM_NONE)) { > > > > > > This double negation is ugly. Just let the compiler optimize away the > > > second comparison. > > > > ...drop this change, we should be good. > > > > If you agree, do you need me to re-post? > > Okay, but then what's the point of the unlocked check? As I wrote, I just did it to emphasize the open-once semantics. If you do not like the unlocked check, feel free to remove it. Thanks, Amir. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write 2024-04-09 16:18 ` Amir Goldstein @ 2024-04-13 6:50 ` Amir Goldstein 2024-04-15 8:14 ` Miklos Szeredi 0 siblings, 1 reply; 10+ messages in thread From: Amir Goldstein @ 2024-04-13 6:50 UTC (permalink / raw) To: Miklos Szeredi; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel On Tue, Apr 9, 2024 at 7:18 PM Amir Goldstein <amir73il@gmail.com> wrote: > > On Tue, Apr 9, 2024 at 6:32 PM Miklos Szeredi <miklos@szeredi.hu> wrote: > > > > On Tue, 9 Apr 2024 at 17:10, Amir Goldstein <amir73il@gmail.com> wrote: > > > > > > On Tue, Apr 9, 2024 at 4:33 PM Miklos Szeredi <miklos@szeredi.hu> wrote: > > > > > > > > On Sun, 7 Apr 2024 at 17:58, Amir Goldstein <amir73il@gmail.com> wrote: > > > > > > > > > > There is a confusion with fuse_file_uncached_io_{start,end} interface. > > > > > These helpers do two things when called from passthrough open()/release(): > > > > > 1. Take/drop negative refcount of fi->iocachectr (inode uncached io mode) > > > > > 2. State change ff->iomode IOM_NONE <-> IOM_UNCACHED (file uncached open) > > > > > > > > > > The calls from parallel dio write path need to take a reference on > > > > > fi->iocachectr, but they should not be changing ff->iomode state, > > > > > because in this case, the fi->iocachectr reference does not stick around > > > > > until file release(). > > > > > > > > Okay. > > > > > > > > > > > > > > Factor out helpers fuse_inode_uncached_io_{start,end}, to be used from > > > > > parallel dio write path and rename fuse_file_*cached_io_{start,end} > > > > > helpers to fuse_file_*cached_io_{open,release} to clarify the difference. > > > > > > > > > > Add a check of ff->iomode in mmap(), so that fuse_file_cached_io_open() > > > > > is called only on first mmap of direct_io file. > > > > > > > > Is this supposed to be an optimization? > > > > > > No. > > > The reason I did this is because I wanted to differentiate > > > the refcount semantics (start/end) > > > from the state semantics (open/release) > > > and to make it clearer that there is only one state change > > > and refcount increment on the first mmap(). > > > > > > > AFAICS it's wrong, because it > > > > moves the check outside of any relevant locks. > > > > > > > > > > Aren't concurrent mmap serialized on some lock? > > > > Only on current->mm, which doesn't serialize mmaps of the same file in > > different processes. > > > > > > > > Anyway, I think that the only "bug" that this can trigger is the > > > WARN_ON(ff->iomode != IOM_NONE) > > > so if we .... > > > > > > > > > > > > @@ -56,8 +57,7 @@ int fuse_file_cached_io_start(struct inode *inode, struct fuse_file *ff) > > > > > return -ETXTBSY; > > > > > } > > > > > > > > > > - WARN_ON(ff->iomode == IOM_UNCACHED); > > > > > - if (ff->iomode == IOM_NONE) { > > > > > + if (!WARN_ON(ff->iomode != IOM_NONE)) { > > > > > > > > This double negation is ugly. Just let the compiler optimize away the > > > > second comparison. > > > > > > ...drop this change, we should be good. > > > > > > If you agree, do you need me to re-post? > > > > Okay, but then what's the point of the unlocked check? > > As I wrote, I just did it to emphasize the open-once > semantics. > If you do not like the unlocked check, feel free to remove it. Miklos, I see that you removed the unlocked check in the fix staged for-next. Please also remove this from commit message: Add a check of ff->iomode in mmap(), so that fuse_file_cached_io_open() is called only on first mmap of direct_io file. Thanks, Amir. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write 2024-04-13 6:50 ` Amir Goldstein @ 2024-04-15 8:14 ` Miklos Szeredi 0 siblings, 0 replies; 10+ messages in thread From: Miklos Szeredi @ 2024-04-15 8:14 UTC (permalink / raw) To: Amir Goldstein; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel On Sat, 13 Apr 2024 at 08:50, Amir Goldstein <amir73il@gmail.com> wrote: > I see that you removed the unlocked check in the fix staged for-next. > Please also remove this from commit message: > > Add a check of ff->iomode in mmap(), so that fuse_file_cached_io_open() is > called only on first mmap of direct_io file. Done. Thanks, Miklos ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 2/3] fuse: fix parallel dio write on file open in passthrough mode 2024-04-07 15:57 [PATCH 0/3] FUSE passthrough fixes Amir Goldstein 2024-04-07 15:57 ` [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write Amir Goldstein @ 2024-04-07 15:57 ` Amir Goldstein 2024-04-07 15:57 ` [PATCH 3/3] fuse: prepare for long lived reference on backing file Amir Goldstein 2 siblings, 0 replies; 10+ messages in thread From: Amir Goldstein @ 2024-04-07 15:57 UTC (permalink / raw) To: Miklos Szeredi; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel Paralle dio write takes a negative refcount of fi->iocachectr and so does open of file in passthrough mode. The refcount of passthrough mode is associated with attach/detach of a fuse_backing object to fuse inode. For parallel dio write, the backing file is irrelevant, so the call to fuse_inode_uncached_io_start() passes a NULL fuse_backing object. Passing a NULL fuse_backing will result in false -EBUSY error if the file is already open in passthrough mode. Allow taking negative fi->iocachectr refcount with NULL fuse_backing, because it does not conflict with an already attached fuse_backing object. Fixes: 4a90451bbc7f ("fuse: implement open in passthrough mode") Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- fs/fuse/iomode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c index 1519f895f0a9..f9e30c4540af 100644 --- a/fs/fuse/iomode.c +++ b/fs/fuse/iomode.c @@ -89,7 +89,7 @@ int fuse_inode_uncached_io_start(struct fuse_inode *fi, struct fuse_backing *fb) spin_lock(&fi->lock); /* deny conflicting backing files on same fuse inode */ oldfb = fuse_inode_backing(fi); - if (oldfb && oldfb != fb) { + if (fb && oldfb && oldfb != fb) { err = -EBUSY; goto unlock; } @@ -100,7 +100,7 @@ int fuse_inode_uncached_io_start(struct fuse_inode *fi, struct fuse_backing *fb) fi->iocachectr--; /* fuse inode holds a single refcount of backing file */ - if (!oldfb) { + if (fb && !oldfb) { oldfb = fuse_inode_backing_set(fi, fb); WARN_ON_ONCE(oldfb != NULL); } else { -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 3/3] fuse: prepare for long lived reference on backing file 2024-04-07 15:57 [PATCH 0/3] FUSE passthrough fixes Amir Goldstein 2024-04-07 15:57 ` [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write Amir Goldstein 2024-04-07 15:57 ` [PATCH 2/3] fuse: fix parallel dio write on file open in passthrough mode Amir Goldstein @ 2024-04-07 15:57 ` Amir Goldstein 2 siblings, 0 replies; 10+ messages in thread From: Amir Goldstein @ 2024-04-07 15:57 UTC (permalink / raw) To: Miklos Szeredi; +Cc: Bernd Schubert, Sweet Tea Dorminy, linux-fsdevel Currently backing file is associated to fuse inode on the first passthrough open of the inode and detached on last passthourgh close. In preparation for attaching a backing file on lookup, allow attaching a long lived (single) reference on fuse inode backing file that will be dropped on fuse inode evict. Signed-off-by: Amir Goldstein <amir73il@gmail.com> --- fs/fuse/file.c | 2 +- fs/fuse/fuse_i.h | 5 ++++- fs/fuse/inode.c | 6 ++++++ fs/fuse/iomode.c | 14 +++++++++++--- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index fcf20b304093..347bae2b287f 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1377,7 +1377,7 @@ static void fuse_dio_lock(struct kiocb *iocb, struct iov_iter *from, * have raced, so check it again. */ if (fuse_io_past_eof(iocb, from) || - fuse_inode_uncached_io_start(fi, NULL) != 0) { + fuse_inode_uncached_io_start(fi, NULL, false) != 0) { inode_unlock_shared(inode); inode_lock(inode); *exclusive = true; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index f23919610313..2f340fd05e8a 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -209,6 +209,8 @@ enum { FUSE_I_BTIME, /* Wants or already has page cache IO */ FUSE_I_CACHE_IO_MODE, + /* Long lived backing file */ + FUSE_I_BACKING, }; struct fuse_conn; @@ -1396,7 +1398,8 @@ int fuse_fileattr_set(struct mnt_idmap *idmap, /* iomode.c */ int fuse_file_cached_io_open(struct inode *inode, struct fuse_file *ff); int fuse_inode_uncached_io_start(struct fuse_inode *fi, - struct fuse_backing *fb); + struct fuse_backing *fb, + bool keep_fb); void fuse_inode_uncached_io_end(struct fuse_inode *fi); int fuse_file_io_open(struct file *file, struct inode *inode); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 99e44ea7d875..989a84f6a825 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -175,6 +175,12 @@ static void fuse_evict_inode(struct inode *inode) } } if (S_ISREG(inode->i_mode) && !fuse_is_bad(inode)) { + /* fuse inode may have a long lived reference to backing file */ + if (fuse_inode_backing(fi)) { + WARN_ON(!test_bit(FUSE_I_BACKING, &fi->state)); + fuse_inode_uncached_io_end(fi); + } + WARN_ON(fi->iocachectr != 0); WARN_ON(!list_empty(&fi->write_files)); WARN_ON(!list_empty(&fi->queued_writes)); diff --git a/fs/fuse/iomode.c b/fs/fuse/iomode.c index f9e30c4540af..b1ff43668800 100644 --- a/fs/fuse/iomode.c +++ b/fs/fuse/iomode.c @@ -80,8 +80,14 @@ static void fuse_file_cached_io_release(struct fuse_file *ff, spin_unlock(&fi->lock); } -/* Start strictly uncached io mode where cache access is not allowed */ -int fuse_inode_uncached_io_start(struct fuse_inode *fi, struct fuse_backing *fb) +/* + * Start strictly uncached io mode where cache access is not allowed. + * + * With @keep_fb true, the backing file reference is expected to be dropped + * on inode evict. + */ +int fuse_inode_uncached_io_start(struct fuse_inode *fi, struct fuse_backing *fb, + bool keep_fb) { struct fuse_backing *oldfb; int err = 0; @@ -103,6 +109,8 @@ int fuse_inode_uncached_io_start(struct fuse_inode *fi, struct fuse_backing *fb) if (fb && !oldfb) { oldfb = fuse_inode_backing_set(fi, fb); WARN_ON_ONCE(oldfb != NULL); + if (keep_fb) + set_bit(FUSE_I_BACKING, &fi->state); } else { fuse_backing_put(fb); } @@ -119,7 +127,7 @@ static int fuse_file_uncached_io_open(struct inode *inode, struct fuse_inode *fi = get_fuse_inode(inode); int err; - err = fuse_inode_uncached_io_start(fi, fb); + err = fuse_inode_uncached_io_start(fi, fb, false); if (err) return err; -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
end of thread, other threads:[~2024-04-15 8:15 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2024-04-07 15:57 [PATCH 0/3] FUSE passthrough fixes Amir Goldstein 2024-04-07 15:57 ` [PATCH 1/3] fuse: fix wrong ff->iomode state changes from parallel dio write Amir Goldstein 2024-04-09 13:33 ` Miklos Szeredi 2024-04-09 15:10 ` Amir Goldstein 2024-04-09 15:32 ` Miklos Szeredi 2024-04-09 16:18 ` Amir Goldstein 2024-04-13 6:50 ` Amir Goldstein 2024-04-15 8:14 ` Miklos Szeredi 2024-04-07 15:57 ` [PATCH 2/3] fuse: fix parallel dio write on file open in passthrough mode Amir Goldstein 2024-04-07 15:57 ` [PATCH 3/3] fuse: prepare for long lived reference on backing file Amir Goldstein
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).