Re: [PATCH v7 1/6] fuse: decouple fuse_ring creation from ent registration
Joanne Koong <[email protected]>
| Newsgroups | dev.linux.lists.fuse-devel |
|---|---|
| Message-ID | <CAJnrk1bocn3zSUe23E4F6oTNyosmF8kdVdFB-2hX2qbSqx-LDA@mail.gmail.com> |
On Wed, Aug 19, 2026 at 4:35 AM Miklos Szeredi <[email protected]> wrote: > > On Fri, 14 Aug 2026 at 21:00, Joanne Koong <[email protected]> wrote: > > > > Currently, the connection's fuse_ring is created lazily on the first > > FUSE_IO_URING_CMD_REGISTER command. A server registers entries from one > > thread per queue (one per CPU) and those threads issue their first > > REGISTER command concurrently. They then race to create the single > > per-connection fuse_ring, which required open-coded handling in > > fuse_uring_create() to detect and protect against concurrent creations. > > > > Decouple fuse_ring creation from ent registration and move it to > > FUSE_INIT reply processing after a server has negotiated and set > > FUSE_OVER_IO_URING. The ring is published before the connection is > > marked initialized. fuse_uring_register() no longer creates the ring and > > it instead uses the ring set up at init time. > > I tested this with loraw (a "raw" loopback tester that doesn't use > libfuse) and it fails with > > root@kvm:~# ./loraw -u /mnt/fuse > loraw: loraw.c:1010: lo_start_uring: Assertion `!cqe->res' failed. > > cqe->res is -22 (EINVAL). > > Attaching the reproducer. To compile: > > cp $(KERNEL_TREE)/include/uapi/linux/fuse.h fuse_kernel.h > gcc loraw.c -oloraw -luring > Thanks for attaching the repro. This is happening because this patch uses the FUSE_OVER_IO_URING init reply as a signal that the ring should be created, but I missed that the FUSE_OVER_IO_URING reply is *optional*. Prior to this patch, there's two scenarios: a) server sets FUSE_OVER_IO_URING reply at init time - requests will automatically block until fuse-io-uring is completely set up b) server does not set FUSE_OVER_IO_URING but later sends uring register request - requests will continue along /dev/fuse path until fuse-io-uring is completely set up Libfuse sets FUSE_OVER_IO_URING in the reply, but the loraw.c server does not. I think the best way to fix this is to have the ring creation happen when the kernel receives the first io-uring command instead of at FUSE_INIT or at FUSE_IO_URING_CMD_REGISTER ent creation time, given that FUSE_IO_URING_ADD_QUEUE needs the ring to exist: Subject: [PATCH] fuse: create fuse_ring on the first io-uring command Commit 6330b1f61ed1 ("fuse: decouple fuse_ring creation from ent registration") moved fuse_ring creation to FUSE_INIT reply processing, gated on the server setting FUSE_OVER_IO_URING in its reply flags. However, that flag is optional. Libfuse sets it but servers not going through libfuse may not. Create the ring on the first io-uring command instead, independent of what the server negotiated. Ring creation stays decoupled from ent registration, which FUSE_IO_URING_CMD_ADD_QUEUE depends on since it needs the ring to exist before any entry is registered. Fixes: 6330b1f61ed1 ("fuse: decouple fuse_ring creation from ent registration") Reported-by: Miklos Szeredi <[email protected]> Signed-off-by: Joanne Koong <[email protected]> --- fs/fuse/dev.c | 2 +- fs/fuse/dev_uring.c | 18 +++++++++++------- fs/fuse/dev_uring_i.h | 5 ----- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 4fec31fc0b84..a665d76292c9 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -77,7 +77,7 @@ void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *pa fch->max_pages = param->max_pages; if (param->io_uring_enabled) - fuse_uring_conn_init(fch); + fch->io_uring = 1; } /* Pairs with smp_load_acquire() readers of fch->initialized */ diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index e22a48c9a678..23c26099d159 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -303,6 +303,7 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch) { struct fuse_ring *ring; size_t nr_queues = num_possible_cpus(); + struct fuse_ring *res = NULL; size_t max_payload_size; ring = kzalloc_obj(*ring, GFP_KERNEL_ACCOUNT); @@ -322,6 +323,12 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch) spin_unlock(&fch->lock); goto out_err; } + if (fch->ring) { + /* race, another thread created the ring in the meantime */ + spin_unlock(&fch->lock); + res = fch->ring; + goto out_err; + } init_waitqueue_head(&ring->stop_waitq); @@ -336,13 +343,7 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch) out_err: kfree(ring->queues); kfree(ring); - return NULL; -} - -void fuse_uring_conn_init(struct fuse_chan *fch) -{ - if (fuse_uring_create(fch)) - fch->io_uring = 1; + return res; } static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, @@ -1685,6 +1686,9 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) if (!smp_load_acquire(&fch->initialized)) return -EAGAIN; + if (!smp_load_acquire(&fch->ring) && !fuse_uring_create(fch)) + return -ENOMEM; + switch (cmd_op) { case FUSE_IO_URING_CMD_REGISTER: err = fuse_uring_register(cmd, issue_flags, fch); diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index 263d0f8b9714..3233b07430d2 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -184,7 +184,6 @@ struct fuse_ring { bool ready; }; -void fuse_uring_conn_init(struct fuse_chan *fch); void fuse_uring_stop_queues(struct fuse_ring *ring); void fuse_uring_abort_end_requests(struct fuse_ring *ring); int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags); @@ -224,10 +223,6 @@ static inline bool fuse_uring_ready(struct fuse_chan *fch) #else /* CONFIG_FUSE_IO_URING */ -static inline void fuse_uring_conn_init(struct fuse_chan *fch) -{ -} - static inline void fuse_uring_abort(struct fuse_chan *fch) { } -- 2.52.0 If you'd prefer an inline replacement for the original commit instead of a fixup patch on top of the tree, please let me know and I'd be happy to send that over. Whatever would be easiest for you. Thanks, Joanne