fuse: move request timeout code to a new source file

This marks the first step in cleanly separating the transport layer from
the filesystem layer.

Add "dev.h", which will contain the interface definition for the transport
layer.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
This commit is contained in:
Miklos Szeredi 2026-03-10 16:45:20 +01:00
parent 31da059891
commit f14348575d
8 changed files with 173 additions and 145 deletions

View File

@ -11,7 +11,7 @@ obj-$(CONFIG_CUSE) += cuse.o
obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
fuse-y := trace.o # put trace.o first so we see ftrace errors sooner
fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o
fuse-y += dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o ioctl.o req_timeout.o
fuse-y += iomode.o
fuse-$(CONFIG_FUSE_DAX) += dax.o
fuse-$(CONFIG_FUSE_PASSTHROUGH) += passthrough.o backing.o

View File

@ -32,100 +32,6 @@ MODULE_ALIAS("devname:fuse");
static struct kmem_cache *fuse_req_cachep;
const unsigned long fuse_timeout_timer_freq =
secs_to_jiffies(FUSE_TIMEOUT_TIMER_FREQ);
bool fuse_request_expired(struct fuse_conn *fc, struct list_head *list)
{
struct fuse_req *req;
req = list_first_entry_or_null(list, struct fuse_req, list);
if (!req)
return false;
return time_is_before_jiffies(req->create_time + fc->timeout.req_timeout);
}
static bool fuse_fpq_processing_expired(struct fuse_conn *fc, struct list_head *processing)
{
int i;
for (i = 0; i < FUSE_PQ_HASH_SIZE; i++)
if (fuse_request_expired(fc, &processing[i]))
return true;
return false;
}
/*
* Check if any requests aren't being completed by the time the request timeout
* elapses. To do so, we:
* - check the fiq pending list
* - check the bg queue
* - check the fpq io and processing lists
*
* To make this fast, we only check against the head request on each list since
* these are generally queued in order of creation time (eg newer requests get
* queued to the tail). We might miss a few edge cases (eg requests transitioning
* between lists, re-sent requests at the head of the pending list having a
* later creation time than other requests on that list, etc.) but that is fine
* since if the request never gets fulfilled, it will eventually be caught.
*/
void fuse_check_timeout(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct fuse_conn *fc = container_of(dwork, struct fuse_conn,
timeout.work);
struct fuse_iqueue *fiq = &fc->iq;
struct fuse_dev *fud;
struct fuse_pqueue *fpq;
bool expired = false;
if (!atomic_read(&fc->num_waiting))
goto out;
spin_lock(&fiq->lock);
expired = fuse_request_expired(fc, &fiq->pending);
spin_unlock(&fiq->lock);
if (expired)
goto abort_conn;
spin_lock(&fc->bg_lock);
expired = fuse_request_expired(fc, &fc->bg_queue);
spin_unlock(&fc->bg_lock);
if (expired)
goto abort_conn;
spin_lock(&fc->lock);
if (!fc->connected) {
spin_unlock(&fc->lock);
return;
}
list_for_each_entry(fud, &fc->devices, entry) {
fpq = &fud->pq;
spin_lock(&fpq->lock);
if (fuse_request_expired(fc, &fpq->io) ||
fuse_fpq_processing_expired(fc, fpq->processing)) {
spin_unlock(&fpq->lock);
spin_unlock(&fc->lock);
goto abort_conn;
}
spin_unlock(&fpq->lock);
}
spin_unlock(&fc->lock);
if (fuse_uring_request_expired(fc))
goto abort_conn;
out:
queue_delayed_work(system_percpu_wq, &fc->timeout.work,
fuse_timeout_timer_freq);
return;
abort_conn:
fuse_abort_conn(fc);
}
static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req)
{
INIT_LIST_HEAD(&req->list);

10
fs/fuse/dev.h Normal file
View File

@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FS_FUSE_DEV_H
#define _FS_FUSE_DEV_H
struct fuse_conn;
void fuse_init_server_timeout(struct fuse_conn *fc, unsigned int timeout);
#endif /* _FS_FUSE_DEV_H */

View File

@ -48,12 +48,6 @@
/** Number of dentries for each connection in the control filesystem */
#define FUSE_CTL_NUM_DENTRIES 5
/* Frequency (in seconds) of request timeout checks, if opted into */
#define FUSE_TIMEOUT_TIMER_FREQ 15
/** Frequency (in jiffies) of request timeout checks, if opted into */
extern const unsigned long fuse_timeout_timer_freq;
/*
* Dentries invalidation workqueue period, in seconds. The value of this
* parameter shall be >= FUSE_DENTRY_INVAL_FREQ_MIN seconds, or 0 (zero), in
@ -63,16 +57,6 @@ extern unsigned inval_wq __read_mostly;
/** Maximum of max_pages received in init_out */
extern unsigned int fuse_max_pages_limit;
/*
* Default timeout (in seconds) for the server to reply to a request
* before the connection is aborted, if no timeout was specified on mount.
*/
extern unsigned int fuse_default_req_timeout;
/*
* Max timeout (in seconds) for the server to reply to a request before
* the connection is aborted.
*/
extern unsigned int fuse_max_req_timeout;
/** List of active connections */
extern struct list_head fuse_conn_list;
@ -1286,9 +1270,6 @@ void fuse_request_end(struct fuse_req *req);
void fuse_abort_conn(struct fuse_conn *fc);
void fuse_wait_aborted(struct fuse_conn *fc);
/* Check if any requests timed out */
void fuse_check_timeout(struct work_struct *work);
void fuse_dentry_tree_init(void);
void fuse_dentry_tree_cleanup(void);

View File

@ -6,6 +6,7 @@
See the file COPYING.
*/
#include "dev.h"
#include "fuse_i.h"
#include "fuse_dev_i.h"
#include "dev_uring_i.h"
@ -41,8 +42,6 @@ static int set_global_limit(const char *val, const struct kernel_param *kp);
unsigned int fuse_max_pages_limit = 256;
/* default is no timeout */
unsigned int fuse_default_req_timeout;
unsigned int fuse_max_req_timeout;
unsigned int max_user_bgreq;
module_param_call(max_user_bgreq, set_global_limit, param_get_uint,
@ -1311,34 +1310,6 @@ static void process_init_limits(struct fuse_conn *fc, struct fuse_init_out *arg)
spin_unlock(&fc->bg_lock);
}
static void set_request_timeout(struct fuse_conn *fc, unsigned int timeout)
{
fc->timeout.req_timeout = secs_to_jiffies(timeout);
INIT_DELAYED_WORK(&fc->timeout.work, fuse_check_timeout);
queue_delayed_work(system_percpu_wq, &fc->timeout.work,
fuse_timeout_timer_freq);
}
static void init_server_timeout(struct fuse_conn *fc, unsigned int timeout)
{
if (!timeout && !fuse_max_req_timeout && !fuse_default_req_timeout)
return;
if (!timeout)
timeout = fuse_default_req_timeout;
if (fuse_max_req_timeout) {
if (timeout)
timeout = min(fuse_max_req_timeout, timeout);
else
timeout = fuse_max_req_timeout;
}
timeout = max(FUSE_TIMEOUT_TIMER_FREQ, timeout);
set_request_timeout(fc, timeout);
}
struct fuse_init_args {
struct fuse_args args;
struct fuse_init_in in;
@ -1491,7 +1462,7 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
fc->no_flock = 1;
}
init_server_timeout(fc, timeout);
fuse_init_server_timeout(fc, timeout);
fm->sb->s_bdi->ra_pages =
min(fm->sb->s_bdi->ra_pages, ra_pages);

150
fs/fuse/req_timeout.c Normal file
View File

@ -0,0 +1,150 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "dev.h"
#include "sysctl.h"
#include "fuse_i.h"
#include "fuse_dev_i.h"
#include "dev_uring_i.h"
/* Frequency (in seconds) of request timeout checks, if opted into */
#define FUSE_TIMEOUT_TIMER_FREQ 15
/* Frequency (in jiffies) of request timeout checks, if opted into */
static const unsigned long fuse_timeout_timer_freq =
secs_to_jiffies(FUSE_TIMEOUT_TIMER_FREQ);
/*
* Default timeout (in seconds) for the server to reply to a request
* before the connection is aborted, if no timeout was specified on mount.
*
* Exported via sysctl
*/
unsigned int fuse_default_req_timeout;
/*
* Max timeout (in seconds) for the server to reply to a request before
* the connection is aborted.
*
* Exported via sysctl
*/
unsigned int fuse_max_req_timeout;
bool fuse_request_expired(struct fuse_conn *fc, struct list_head *list)
{
struct fuse_req *req;
req = list_first_entry_or_null(list, struct fuse_req, list);
if (!req)
return false;
return time_is_before_jiffies(req->create_time + fc->timeout.req_timeout);
}
static bool fuse_fpq_processing_expired(struct fuse_conn *fc, struct list_head *processing)
{
int i;
for (i = 0; i < FUSE_PQ_HASH_SIZE; i++)
if (fuse_request_expired(fc, &processing[i]))
return true;
return false;
}
/*
* Check if any requests aren't being completed by the time the request timeout
* elapses. To do so, we:
* - check the fiq pending list
* - check the bg queue
* - check the fpq io and processing lists
*
* To make this fast, we only check against the head request on each list since
* these are generally queued in order of creation time (eg newer requests get
* queued to the tail). We might miss a few edge cases (eg requests transitioning
* between lists, re-sent requests at the head of the pending list having a
* later creation time than other requests on that list, etc.) but that is fine
* since if the request never gets fulfilled, it will eventually be caught.
*/
static void fuse_check_timeout(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct fuse_conn *fc = container_of(dwork, struct fuse_conn,
timeout.work);
struct fuse_iqueue *fiq = &fc->iq;
struct fuse_dev *fud;
struct fuse_pqueue *fpq;
bool expired = false;
if (!atomic_read(&fc->num_waiting))
goto out;
spin_lock(&fiq->lock);
expired = fuse_request_expired(fc, &fiq->pending);
spin_unlock(&fiq->lock);
if (expired)
goto abort_conn;
spin_lock(&fc->bg_lock);
expired = fuse_request_expired(fc, &fc->bg_queue);
spin_unlock(&fc->bg_lock);
if (expired)
goto abort_conn;
spin_lock(&fc->lock);
if (!fc->connected) {
spin_unlock(&fc->lock);
return;
}
list_for_each_entry(fud, &fc->devices, entry) {
fpq = &fud->pq;
spin_lock(&fpq->lock);
if (fuse_request_expired(fc, &fpq->io) ||
fuse_fpq_processing_expired(fc, fpq->processing)) {
spin_unlock(&fpq->lock);
spin_unlock(&fc->lock);
goto abort_conn;
}
spin_unlock(&fpq->lock);
}
spin_unlock(&fc->lock);
if (fuse_uring_request_expired(fc))
goto abort_conn;
out:
queue_delayed_work(system_percpu_wq, &fc->timeout.work,
fuse_timeout_timer_freq);
return;
abort_conn:
fuse_abort_conn(fc);
}
static void set_request_timeout(struct fuse_conn *fc, unsigned int timeout)
{
fc->timeout.req_timeout = secs_to_jiffies(timeout);
INIT_DELAYED_WORK(&fc->timeout.work, fuse_check_timeout);
queue_delayed_work(system_percpu_wq, &fc->timeout.work,
fuse_timeout_timer_freq);
}
void fuse_init_server_timeout(struct fuse_conn *fc, unsigned int timeout)
{
if (!timeout && !fuse_max_req_timeout && !fuse_default_req_timeout)
return;
if (!timeout)
timeout = fuse_default_req_timeout;
if (fuse_max_req_timeout) {
if (timeout)
timeout = min(fuse_max_req_timeout, timeout);
else
timeout = fuse_max_req_timeout;
}
timeout = max(FUSE_TIMEOUT_TIMER_FREQ, timeout);
set_request_timeout(fc, timeout);
}

View File

@ -6,6 +6,7 @@
*/
#include <linux/sysctl.h>
#include "sysctl.h"
#include "fuse_i.h"
static struct ctl_table_header *fuse_table_header;

9
fs/fuse/sysctl.h Normal file
View File

@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FS_FUSE_SYSCTL_H
#define _FS_FUSE_SYSCTL_H
extern unsigned int fuse_default_req_timeout;
extern unsigned int fuse_max_req_timeout;
#endif /* _FS_FUSE_SYSCTL_H */