Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
3bcf5ee
fuse: rename to fuse_dev_end_requests and make non-static
bsbernd Jan 20, 2025
1666101
fuse: Move fuse_get_dev to header file
bsbernd Jan 20, 2025
6b6c217
fuse: Move request bits
bsbernd Jan 20, 2025
d31f04e
fuse: make args->in_args[0] to be always the header
bsbernd Jan 20, 2025
35bc672
fuse: {io-uring} Handle SQEs - register commands
bsbernd Jan 20, 2025
c6641bd
fuse: Make fuse_copy non static
bsbernd Jan 20, 2025
27743ea
fuse: Add fuse-io-uring handling into fuse_copy
bsbernd Jan 20, 2025
efcaee3
fuse: {io-uring} Make hash-list req unique finding functions non-static
bsbernd Jan 20, 2025
6d7c5e7
fuse: Add io-uring sqe commit and fetch support
bsbernd Jan 20, 2025
f8f69d2
fuse: {io-uring} Handle teardown of ring entries
bsbernd Jan 20, 2025
aaf0f3b
fuse: cleanup request queuing towards virtiofs
May 29, 2024
fa04c7d
fuse: {io-uring} Make fuse_dev_queue_{interrupt,forget} non-static
bsbernd Jan 20, 2025
d9c10de
fuse: Allow to queue fg requests through io-uring
bsbernd Jan 20, 2025
018641a
RED-ONLY: fuse: {io-uring red} define IO_URING_F_TASK_DEAD
bsbernd Apr 17, 2025
6da52b7
fuse: Allow to queue bg requests through io-uring
bsbernd Jan 20, 2025
144266f
fuse: {io-uring} Prevent mount point hang on fuse-server termination
bsbernd Jan 20, 2025
dc40b25
fuse: block request allocation until io-uring init is complete
bsbernd Jan 20, 2025
5174768
fuse: enable fuse-over-io-uring
bsbernd Jan 20, 2025
ac738c1
fuse: prevent disabling io-uring on active connections
bsbernd Jan 23, 2025
7cf1540
fuse: fix uring race condition for null dereference of fc
joannekoong Mar 18, 2025
3a7a4e7
fuse: fix possible deadlock if rings are never initialized
luis-henrix Mar 6, 2025
bbe1041
fuse: {io-uring} Fix a possible req cancellation race
bsbernd Mar 25, 2025
54d9a86
fuse: removed unused function fuse_uring_create() from header
luis-henrix Feb 7, 2025
65e7afa
fuse: Allocate only namelen buf memory in fuse_notify_
bsbernd Dec 16, 2024
4f1ba11
fuse: use FUSE_ROOT_ID in fuse_get_root_inode()
Feb 28, 2024
fabde09
fuse: check attributes staleness on fuse_iget()
cntianci Nov 18, 2024
d77db6e
fuse: fuse: Use readdir buffer of max_pages size
szmi Apr 8, 2025
1b4cae7
Revert "virtiofs: use pages instead of pointer for kernel direct IO"
bsbernd Apr 17, 2025
95a52d5
fuse: Fix missing FOLL_PIN for direct-io
Aug 29, 2023
b023958
fuse: enable dynamic configuration of fuse max pages limit (FUSE_MAX_…
joannekoong Sep 23, 2024
47b2694
fuse: Make the fuse_send_one request counter atomic
bsbernd Apr 2, 2025
4415892
[RFC] fuse: Set request unique on allocation
bsbernd Apr 2, 2025
6e77e0e
fuse: add simple request tracepoints
josefbacik Jul 3, 2024
9efaa8d
fuse: {io-uring} Avoid _send code dup
bsbernd Apr 2, 2025
4a7f142
fuse: fine-grained request ftraces
bsbernd Apr 2, 2025
573e7ab
fuse: Increase FUSE_NAME_MAX to PATH_MAX
bsbernd Dec 16, 2024
52da885
fuse: add support for explicit export disabling
lostjeffle Feb 26, 2024
f400249
fuse: create helper function if DIO write needs exclusive lock
bsbernd Aug 22, 2023
110fb13
fuse: factor out helper fuse_truncate_update_attr()
amir73il Feb 1, 2024
cbabbdd
fuse: allocate ff->release_args only if release is needed
amir73il Feb 1, 2024
8dcafbe
fuse: break up fuse_open_common()
amir73il Feb 2, 2024
c1531c7
fuse: prepare for failing open response
amir73il Feb 1, 2024
0a64c3c
fuse: introduce inode io modes
amir73il Feb 1, 2024
a709f7b
fuse: add fuse_dio_lock/unlock helper functions
bsbernd Dec 23, 2023
afe3358
fuse: allow parallel dio writes with FUSE_DIRECT_IO_ALLOW_MMAP
amir73il Feb 9, 2024
ba1236c
fuse: add fast path for fuse_range_is_writeback
yangyun50 Aug 14, 2024
bbddfd7
fuse: use exclusive lock when FUSE_I_CACHE_IO_MODE is set
yangyun50 Sep 14, 2024
3f71501
fuse: Increase the default max pages limit to 8182
bsbernd Apr 8, 2025
ea01f94
fuse: {uring} Pin the user buffer
bsbernd Jan 8, 2025
5f0264c
fuse: {io-uring] Avoid complete-in-task if pinned pages are used
bsbernd Jan 17, 2025
2a889c7
fuse: Add memory barrier in fuse_uring_destruct
bsbernd Apr 22, 2025
763c96d
fuse: Use fuser-server provided read-ahead for CAP_SYS_ADMIN
bsbernd May 7, 2025
78dec11
fuse: fix race between concurrent setattrs from multiple nodes
gwu-ddn May 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fuse: {io-uring} Handle teardown of ring entries
On teardown struct file_operations::uring_cmd requests
need to be completed by calling io_uring_cmd_done().
Not completing all ring entries would result in busy io-uring
tasks giving warning messages in intervals and unreleased
struct file.

Additionally the fuse connection and with that the ring can
only get released when all io-uring commands are completed.

Completion is done with ring entries that are
a) in waiting state for new fuse requests - io_uring_cmd_done
is needed

b) already in userspace - io_uring_cmd_done through teardown
is not needed, the request can just get released. If fuse server
is still active and commits such a ring entry, fuse_uring_cmd()
already checks if the connection is active and then complete the
io-uring itself with -ENOTCONN. I.e. special handling is not
needed.

This scheme is basically represented by the ring entry state
FRRS_WAIT and FRRS_USERSPACE.

Entries in state:
- FRRS_INIT: No action needed, do not contribute to
  ring->queue_refs yet
- All other states: Are currently processed by other tasks,
  async teardown is needed and it has to wait for the two
  states above. It could be also solved without an async
  teardown task, but would require additional if conditions
  in hot code paths. Also in my personal opinion the code
  looks cleaner with async teardown.

Signed-off-by: Bernd Schubert <[email protected]>
Reviewed-by: Pavel Begunkov <[email protected]> # io_uring
Reviewed-by: Luis Henriques <[email protected]>
Signed-off-by: Miklos Szeredi <[email protected]>
(cherry picked from commit 4a9bfb9)
  • Loading branch information
bsbernd committed Apr 17, 2025
commit f8f69d2f6dd20a9d74ee1531c66ac519079a1a25
9 changes: 9 additions & 0 deletions fs/fuse/dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
See the file COPYING.
*/

#include "dev_uring_i.h"
#include "fuse_i.h"
#include "fuse_dev_i.h"

Expand Down Expand Up @@ -2165,6 +2166,12 @@ void fuse_abort_conn(struct fuse_conn *fc)
spin_unlock(&fc->lock);

fuse_dev_end_requests(&to_end);

/*
* fc->lock must not be taken to avoid conflicts with io-uring
* locks
*/
fuse_uring_abort(fc);
} else {
spin_unlock(&fc->lock);
}
Expand All @@ -2176,6 +2183,8 @@ void fuse_wait_aborted(struct fuse_conn *fc)
/* matches implicit memory barrier in fuse_drop_waiting() */
smp_mb();
wait_event(fc->blocked_waitq, atomic_read(&fc->num_waiting) == 0);

fuse_uring_wait_stopped_queues(fc);
}

int fuse_dev_release(struct inode *inode, struct file *file)
Expand Down
207 changes: 207 additions & 0 deletions fs/fuse/dev_uring.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,37 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req,
fuse_request_end(req);
}

/* Abort all list queued request on the given ring queue */
static void fuse_uring_abort_end_queue_requests(struct fuse_ring_queue *queue)
{
struct fuse_req *req;
LIST_HEAD(req_list);

spin_lock(&queue->lock);
list_for_each_entry(req, &queue->fuse_req_queue, list)
clear_bit(FR_PENDING, &req->flags);
list_splice_init(&queue->fuse_req_queue, &req_list);
spin_unlock(&queue->lock);

/* must not hold queue lock to avoid order issues with fi->lock */
fuse_dev_end_requests(&req_list);
}

void fuse_uring_abort_end_requests(struct fuse_ring *ring)
{
int qid;
struct fuse_ring_queue *queue;

for (qid = 0; qid < ring->nr_queues; qid++) {
queue = READ_ONCE(ring->queues[qid]);
if (!queue)
continue;

queue->stopped = true;
fuse_uring_abort_end_queue_requests(queue);
}
}

void fuse_uring_destruct(struct fuse_conn *fc)
{
struct fuse_ring *ring = fc->ring;
Expand Down Expand Up @@ -94,10 +125,13 @@ static struct fuse_ring *fuse_uring_create(struct fuse_conn *fc)
goto out_err;
}

init_waitqueue_head(&ring->stop_waitq);

fc->ring = ring;
ring->nr_queues = nr_queues;
ring->fc = fc;
ring->max_payload_sz = max_payload_size;
atomic_set(&ring->queue_refs, 0);

spin_unlock(&fc->lock);
return ring;
Expand Down Expand Up @@ -154,6 +188,175 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
return queue;
}

static void fuse_uring_stop_fuse_req_end(struct fuse_req *req)
{
clear_bit(FR_SENT, &req->flags);
req->out.h.error = -ECONNABORTED;
fuse_request_end(req);
}

/*
* Release a request/entry on connection tear down
*/
static void fuse_uring_entry_teardown(struct fuse_ring_ent *ent)
{
struct fuse_req *req;
struct io_uring_cmd *cmd;

struct fuse_ring_queue *queue = ent->queue;

spin_lock(&queue->lock);
cmd = ent->cmd;
ent->cmd = NULL;
req = ent->fuse_req;
ent->fuse_req = NULL;
if (req) {
/* remove entry from queue->fpq->processing */
list_del_init(&req->list);
}
spin_unlock(&queue->lock);

if (cmd)
io_uring_cmd_done(cmd, -ENOTCONN, 0, IO_URING_F_UNLOCKED);

if (req)
fuse_uring_stop_fuse_req_end(req);

list_del_init(&ent->list);
kfree(ent);
}

static void fuse_uring_stop_list_entries(struct list_head *head,
struct fuse_ring_queue *queue,
enum fuse_ring_req_state exp_state)
{
struct fuse_ring *ring = queue->ring;
struct fuse_ring_ent *ent, *next;
ssize_t queue_refs = SSIZE_MAX;
LIST_HEAD(to_teardown);

spin_lock(&queue->lock);
list_for_each_entry_safe(ent, next, head, list) {
if (ent->state != exp_state) {
pr_warn("entry teardown qid=%d state=%d expected=%d",
queue->qid, ent->state, exp_state);
continue;
}

list_move(&ent->list, &to_teardown);
}
spin_unlock(&queue->lock);

/* no queue lock to avoid lock order issues */
list_for_each_entry_safe(ent, next, &to_teardown, list) {
fuse_uring_entry_teardown(ent);
queue_refs = atomic_dec_return(&ring->queue_refs);
WARN_ON_ONCE(queue_refs < 0);
}
}

static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue)
{
fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue,
FRRS_USERSPACE);
fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue,
FRRS_AVAILABLE);
}

/*
* Log state debug info
*/
static void fuse_uring_log_ent_state(struct fuse_ring *ring)
{
int qid;
struct fuse_ring_ent *ent;

for (qid = 0; qid < ring->nr_queues; qid++) {
struct fuse_ring_queue *queue = ring->queues[qid];

if (!queue)
continue;

spin_lock(&queue->lock);
/*
* Log entries from the intermediate queue, the other queues
* should be empty
*/
list_for_each_entry(ent, &queue->ent_w_req_queue, list) {
pr_info(" ent-req-queue ring=%p qid=%d ent=%p state=%d\n",
ring, qid, ent, ent->state);
}
list_for_each_entry(ent, &queue->ent_commit_queue, list) {
pr_info(" ent-commit-queue ring=%p qid=%d ent=%p state=%d\n",
ring, qid, ent, ent->state);
}
spin_unlock(&queue->lock);
}
ring->stop_debug_log = 1;
}

static void fuse_uring_async_stop_queues(struct work_struct *work)
{
int qid;
struct fuse_ring *ring =
container_of(work, struct fuse_ring, async_teardown_work.work);

/* XXX code dup */
for (qid = 0; qid < ring->nr_queues; qid++) {
struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);

if (!queue)
continue;

fuse_uring_teardown_entries(queue);
}

/*
* Some ring entries might be in the middle of IO operations,
* i.e. in process to get handled by file_operations::uring_cmd
* or on the way to userspace - we could handle that with conditions in
* run time code, but easier/cleaner to have an async tear down handler
* If there are still queue references left
*/
if (atomic_read(&ring->queue_refs) > 0) {
if (time_after(jiffies,
ring->teardown_time + FUSE_URING_TEARDOWN_TIMEOUT))
fuse_uring_log_ent_state(ring);

schedule_delayed_work(&ring->async_teardown_work,
FUSE_URING_TEARDOWN_INTERVAL);
} else {
wake_up_all(&ring->stop_waitq);
}
}

/*
* Stop the ring queues
*/
void fuse_uring_stop_queues(struct fuse_ring *ring)
{
int qid;

for (qid = 0; qid < ring->nr_queues; qid++) {
struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);

if (!queue)
continue;

fuse_uring_teardown_entries(queue);
}

if (atomic_read(&ring->queue_refs) > 0) {
ring->teardown_time = jiffies;
INIT_DELAYED_WORK(&ring->async_teardown_work,
fuse_uring_async_stop_queues);
schedule_delayed_work(&ring->async_teardown_work,
FUSE_URING_TEARDOWN_INTERVAL);
} else {
wake_up_all(&ring->stop_waitq);
}
}

/*
* Checks for errors and stores it into the request
*/
Expand Down Expand Up @@ -525,6 +728,9 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
return err;
fpq = &queue->fpq;

if (!READ_ONCE(fc->connected) || READ_ONCE(queue->stopped))
return err;

spin_lock(&queue->lock);
/* Find a request based on the unique ID of the fuse request
* This should get revised, as it needs a hash calculation and list
Expand Down Expand Up @@ -652,6 +858,7 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd,
ent->headers = iov[0].iov_base;
ent->payload = iov[1].iov_base;

atomic_inc(&ring->queue_refs);
return ent;
}

Expand Down
51 changes: 51 additions & 0 deletions fs/fuse/dev_uring_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

#ifdef CONFIG_FUSE_IO_URING

#define FUSE_URING_TEARDOWN_TIMEOUT (5 * HZ)
#define FUSE_URING_TEARDOWN_INTERVAL (HZ/20)

enum fuse_ring_req_state {
FRRS_INVALID = 0,

Expand Down Expand Up @@ -80,6 +83,8 @@ struct fuse_ring_queue {
struct list_head fuse_req_queue;

struct fuse_pqueue fpq;

bool stopped;
};

/**
Expand All @@ -97,12 +102,51 @@ struct fuse_ring {
size_t max_payload_sz;

struct fuse_ring_queue **queues;

/*
* Log ring entry states on stop when entries cannot be released
*/
unsigned int stop_debug_log : 1;

wait_queue_head_t stop_waitq;

/* async tear down */
struct delayed_work async_teardown_work;

/* log */
unsigned long teardown_time;

atomic_t queue_refs;
};

bool fuse_uring_enabled(void);
void fuse_uring_destruct(struct fuse_conn *fc);
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);

static inline void fuse_uring_abort(struct fuse_conn *fc)
{
struct fuse_ring *ring = fc->ring;

if (ring == NULL)
return;

if (atomic_read(&ring->queue_refs) > 0) {
fuse_uring_abort_end_requests(ring);
fuse_uring_stop_queues(ring);
}
}

static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc)
{
struct fuse_ring *ring = fc->ring;

if (ring)
wait_event(ring->stop_waitq,
atomic_read(&ring->queue_refs) == 0);
}

#else /* CONFIG_FUSE_IO_URING */

struct fuse_ring;
Expand All @@ -120,6 +164,13 @@ static inline bool fuse_uring_enabled(void)
return false;
}

static inline void fuse_uring_abort(struct fuse_conn *fc)
{
}

static inline void fuse_uring_wait_stopped_queues(struct fuse_conn *fc)
{
}
#endif /* CONFIG_FUSE_IO_URING */

#endif /* _FS_FUSE_DEV_URING_I_H */