Skip to content

Commit 0f6439f

Browse files
joannekoongMiklos Szeredi
authored andcommitted
fuse: add kernel-enforced timeout option for requests
There are situations where fuse servers can become unresponsive or stuck, for example if the server is deadlocked. Currently, there's no good way to detect if a server is stuck and needs to be killed manually. This commit adds an option for enforcing a timeout (in seconds) for requests where if the timeout elapses without the server responding to the request, the connection will be automatically aborted. Please note that these timeouts are not 100% precise. For example, the request may take roughly an extra FUSE_TIMEOUT_TIMER_FREQ seconds beyond the requested timeout due to internal implementation, in order to mitigate overhead. [SzM: Bump the API version number] Signed-off-by: Joanne Koong <joannelkoong@gmail.com> Reviewed-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
1 parent eef36cf commit 0f6439f

File tree

7 files changed

+179
-4
lines changed

7 files changed

+179
-4
lines changed

fs/fuse/dev.c

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,103 @@ MODULE_ALIAS("devname:fuse");
3232

3333
static struct kmem_cache *fuse_req_cachep;
3434

35+
/* Frequency (in seconds) of request timeout checks, if opted into */
36+
#define FUSE_TIMEOUT_TIMER_FREQ 15
37+
38+
const unsigned long fuse_timeout_timer_freq =
39+
secs_to_jiffies(FUSE_TIMEOUT_TIMER_FREQ);
40+
41+
bool fuse_request_expired(struct fuse_conn *fc, struct list_head *list)
42+
{
43+
struct fuse_req *req;
44+
45+
req = list_first_entry_or_null(list, struct fuse_req, list);
46+
if (!req)
47+
return false;
48+
return time_is_before_jiffies(req->create_time + fc->timeout.req_timeout);
49+
}
50+
51+
bool fuse_fpq_processing_expired(struct fuse_conn *fc, struct list_head *processing)
52+
{
53+
int i;
54+
55+
for (i = 0; i < FUSE_PQ_HASH_SIZE; i++)
56+
if (fuse_request_expired(fc, &processing[i]))
57+
return true;
58+
59+
return false;
60+
}
61+
62+
/*
63+
* Check if any requests aren't being completed by the time the request timeout
64+
* elapses. To do so, we:
65+
* - check the fiq pending list
66+
* - check the bg queue
67+
* - check the fpq io and processing lists
68+
*
69+
* To make this fast, we only check against the head request on each list since
70+
* these are generally queued in order of creation time (eg newer requests get
71+
* queued to the tail). We might miss a few edge cases (eg requests transitioning
72+
* between lists, re-sent requests at the head of the pending list having a
73+
* later creation time than other requests on that list, etc.) but that is fine
74+
* since if the request never gets fulfilled, it will eventually be caught.
75+
*/
76+
void fuse_check_timeout(struct work_struct *work)
77+
{
78+
struct delayed_work *dwork = to_delayed_work(work);
79+
struct fuse_conn *fc = container_of(dwork, struct fuse_conn,
80+
timeout.work);
81+
struct fuse_iqueue *fiq = &fc->iq;
82+
struct fuse_dev *fud;
83+
struct fuse_pqueue *fpq;
84+
bool expired = false;
85+
86+
if (!atomic_read(&fc->num_waiting))
87+
goto out;
88+
89+
spin_lock(&fiq->lock);
90+
expired = fuse_request_expired(fc, &fiq->pending);
91+
spin_unlock(&fiq->lock);
92+
if (expired)
93+
goto abort_conn;
94+
95+
spin_lock(&fc->bg_lock);
96+
expired = fuse_request_expired(fc, &fc->bg_queue);
97+
spin_unlock(&fc->bg_lock);
98+
if (expired)
99+
goto abort_conn;
100+
101+
spin_lock(&fc->lock);
102+
if (!fc->connected) {
103+
spin_unlock(&fc->lock);
104+
return;
105+
}
106+
list_for_each_entry(fud, &fc->devices, entry) {
107+
fpq = &fud->pq;
108+
spin_lock(&fpq->lock);
109+
if (fuse_request_expired(fc, &fpq->io) ||
110+
fuse_fpq_processing_expired(fc, fpq->processing)) {
111+
spin_unlock(&fpq->lock);
112+
spin_unlock(&fc->lock);
113+
goto abort_conn;
114+
}
115+
116+
spin_unlock(&fpq->lock);
117+
}
118+
spin_unlock(&fc->lock);
119+
120+
if (fuse_uring_request_expired(fc))
121+
goto abort_conn;
122+
123+
out:
124+
queue_delayed_work(system_wq, &fc->timeout.work,
125+
fuse_timeout_timer_freq);
126+
return;
127+
128+
abort_conn:
129+
fuse_abort_conn(fc);
130+
}
131+
35132
static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req)
36133
{
37134
INIT_LIST_HEAD(&req->list);
@@ -40,6 +137,7 @@ static void fuse_request_init(struct fuse_mount *fm, struct fuse_req *req)
40137
refcount_set(&req->count, 1);
41138
__set_bit(FR_PENDING, &req->flags);
42139
req->fm = fm;
140+
req->create_time = jiffies;
43141
}
44142

45143
static struct fuse_req *fuse_request_alloc(struct fuse_mount *fm, gfp_t flags)
@@ -2291,6 +2389,9 @@ void fuse_abort_conn(struct fuse_conn *fc)
22912389
LIST_HEAD(to_end);
22922390
unsigned int i;
22932391

2392+
if (fc->timeout.req_timeout)
2393+
cancel_delayed_work(&fc->timeout.work);
2394+
22942395
/* Background queuing checks fc->connected under bg_lock */
22952396
spin_lock(&fc->bg_lock);
22962397
fc->connected = 0;

fs/fuse/dev_uring.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,33 @@ void fuse_uring_abort_end_requests(struct fuse_ring *ring)
140140
}
141141
}
142142

143+
bool fuse_uring_request_expired(struct fuse_conn *fc)
144+
{
145+
struct fuse_ring *ring = fc->ring;
146+
struct fuse_ring_queue *queue;
147+
int qid;
148+
149+
if (!ring)
150+
return false;
151+
152+
for (qid = 0; qid < ring->nr_queues; qid++) {
153+
queue = READ_ONCE(ring->queues[qid]);
154+
if (!queue)
155+
continue;
156+
157+
spin_lock(&queue->lock);
158+
if (fuse_request_expired(fc, &queue->fuse_req_queue) ||
159+
fuse_request_expired(fc, &queue->fuse_req_bg_queue) ||
160+
fuse_fpq_processing_expired(fc, queue->fpq.processing)) {
161+
spin_unlock(&queue->lock);
162+
return true;
163+
}
164+
spin_unlock(&queue->lock);
165+
}
166+
167+
return false;
168+
}
169+
143170
void fuse_uring_destruct(struct fuse_conn *fc)
144171
{
145172
struct fuse_ring *ring = fc->ring;

fs/fuse/dev_uring_i.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags);
143143
void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req);
144144
bool fuse_uring_queue_bq_req(struct fuse_req *req);
145145
bool fuse_uring_remove_pending_req(struct fuse_req *req);
146+
bool fuse_uring_request_expired(struct fuse_conn *fc);
146147

147148
static inline void fuse_uring_abort(struct fuse_conn *fc)
148149
{
@@ -200,6 +201,11 @@ static inline bool fuse_uring_remove_pending_req(struct fuse_req *req)
200201
return false;
201202
}
202203

204+
static inline bool fuse_uring_request_expired(struct fuse_conn *fc)
205+
{
206+
return false;
207+
}
208+
203209
#endif /* CONFIG_FUSE_IO_URING */
204210

205211
#endif /* _FS_FUSE_DEV_URING_I_H */

fs/fuse/fuse_dev_i.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,8 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq,
6363
void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req);
6464
bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock);
6565

66+
bool fuse_request_expired(struct fuse_conn *fc, struct list_head *list);
67+
bool fuse_fpq_processing_expired(struct fuse_conn *fc, struct list_head *processing);
68+
6669
#endif
6770

fs/fuse/fuse_i.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
/** Number of dentries for each connection in the control filesystem */
4545
#define FUSE_CTL_NUM_DENTRIES 5
4646

47+
/** Frequency (in jiffies) of request timeout checks, if opted into */
48+
extern const unsigned long fuse_timeout_timer_freq;
49+
4750
/** Maximum of max_pages received in init_out */
4851
extern unsigned int fuse_max_pages_limit;
4952

@@ -445,6 +448,8 @@ struct fuse_req {
445448
void *ring_entry;
446449
void *ring_queue;
447450
#endif
451+
/** When (in jiffies) the request was created */
452+
unsigned long create_time;
448453
};
449454

450455
struct fuse_iqueue;
@@ -941,6 +946,15 @@ struct fuse_conn {
941946
/** uring connection information*/
942947
struct fuse_ring *ring;
943948
#endif
949+
950+
/** Only used if the connection opts into request timeouts */
951+
struct {
952+
/* Worker for checking if any requests have timed out */
953+
struct delayed_work work;
954+
955+
/* Request timeout (in jiffies). 0 = no timeout */
956+
unsigned int req_timeout;
957+
} timeout;
944958
};
945959

946960
/*
@@ -1222,6 +1236,9 @@ void fuse_request_end(struct fuse_req *req);
12221236
void fuse_abort_conn(struct fuse_conn *fc);
12231237
void fuse_wait_aborted(struct fuse_conn *fc);
12241238

1239+
/* Check if any requests timed out */
1240+
void fuse_check_timeout(struct work_struct *work);
1241+
12251242
/**
12261243
* Invalidate inode attributes
12271244
*/

fs/fuse/inode.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
979979
fc->user_ns = get_user_ns(user_ns);
980980
fc->max_pages = FUSE_DEFAULT_MAX_PAGES_PER_REQ;
981981
fc->max_pages_limit = fuse_max_pages_limit;
982+
fc->timeout.req_timeout = 0;
982983

983984
if (IS_ENABLED(CONFIG_FUSE_PASSTHROUGH))
984985
fuse_backing_files_init(fc);
@@ -1007,6 +1008,8 @@ void fuse_conn_put(struct fuse_conn *fc)
10071008

10081009
if (IS_ENABLED(CONFIG_FUSE_DAX))
10091010
fuse_dax_conn_free(fc);
1011+
if (fc->timeout.req_timeout)
1012+
cancel_delayed_work_sync(&fc->timeout.work);
10101013
if (fiq->ops->release)
10111014
fiq->ops->release(fiq);
10121015
put_pid_ns(fc->pid_ns);
@@ -1257,6 +1260,14 @@ static void process_init_limits(struct fuse_conn *fc, struct fuse_init_out *arg)
12571260
spin_unlock(&fc->bg_lock);
12581261
}
12591262

1263+
static void set_request_timeout(struct fuse_conn *fc, unsigned int timeout)
1264+
{
1265+
fc->timeout.req_timeout = secs_to_jiffies(timeout);
1266+
INIT_DELAYED_WORK(&fc->timeout.work, fuse_check_timeout);
1267+
queue_delayed_work(system_wq, &fc->timeout.work,
1268+
fuse_timeout_timer_freq);
1269+
}
1270+
12601271
struct fuse_init_args {
12611272
struct fuse_args args;
12621273
struct fuse_init_in in;
@@ -1392,6 +1403,9 @@ static void process_init_reply(struct fuse_mount *fm, struct fuse_args *args,
13921403
}
13931404
if (flags & FUSE_OVER_IO_URING && fuse_uring_enabled())
13941405
fc->io_uring = 1;
1406+
1407+
if ((flags & FUSE_REQUEST_TIMEOUT) && arg->request_timeout)
1408+
set_request_timeout(fc, arg->request_timeout);
13951409
} else {
13961410
ra_pages = fc->max_read / PAGE_SIZE;
13971411
fc->no_lock = 1;
@@ -1439,7 +1453,8 @@ void fuse_send_init(struct fuse_mount *fm)
14391453
FUSE_HANDLE_KILLPRIV_V2 | FUSE_SETXATTR_EXT | FUSE_INIT_EXT |
14401454
FUSE_SECURITY_CTX | FUSE_CREATE_SUPP_GROUP |
14411455
FUSE_HAS_EXPIRE_ONLY | FUSE_DIRECT_IO_ALLOW_MMAP |
1442-
FUSE_NO_EXPORT_SUPPORT | FUSE_HAS_RESEND | FUSE_ALLOW_IDMAP;
1456+
FUSE_NO_EXPORT_SUPPORT | FUSE_HAS_RESEND | FUSE_ALLOW_IDMAP |
1457+
FUSE_REQUEST_TIMEOUT;
14431458
#ifdef CONFIG_FUSE_DAX
14441459
if (fm->fc->dax)
14451460
flags |= FUSE_MAP_ALIGNMENT;

include/uapi/linux/fuse.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@
229229
* - FUSE_URING_IN_OUT_HEADER_SZ
230230
* - FUSE_URING_OP_IN_OUT_SZ
231231
* - enum fuse_uring_cmd
232+
*
233+
* 7.43
234+
* - add FUSE_REQUEST_TIMEOUT
232235
*/
233236

234237
#ifndef _LINUX_FUSE_H
@@ -264,7 +267,7 @@
264267
#define FUSE_KERNEL_VERSION 7
265268

266269
/** Minor version number of this interface */
267-
#define FUSE_KERNEL_MINOR_VERSION 42
270+
#define FUSE_KERNEL_MINOR_VERSION 43
268271

269272
/** The node ID of the root inode */
270273
#define FUSE_ROOT_ID 1
@@ -435,6 +438,8 @@ struct fuse_file_lock {
435438
* of the request ID indicates resend requests
436439
* FUSE_ALLOW_IDMAP: allow creation of idmapped mounts
437440
* FUSE_OVER_IO_URING: Indicate that client supports io-uring
441+
* FUSE_REQUEST_TIMEOUT: kernel supports timing out requests.
442+
* init_out.request_timeout contains the timeout (in secs)
438443
*/
439444
#define FUSE_ASYNC_READ (1 << 0)
440445
#define FUSE_POSIX_LOCKS (1 << 1)
@@ -477,11 +482,11 @@ struct fuse_file_lock {
477482
#define FUSE_PASSTHROUGH (1ULL << 37)
478483
#define FUSE_NO_EXPORT_SUPPORT (1ULL << 38)
479484
#define FUSE_HAS_RESEND (1ULL << 39)
480-
481485
/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
482486
#define FUSE_DIRECT_IO_RELAX FUSE_DIRECT_IO_ALLOW_MMAP
483487
#define FUSE_ALLOW_IDMAP (1ULL << 40)
484488
#define FUSE_OVER_IO_URING (1ULL << 41)
489+
#define FUSE_REQUEST_TIMEOUT (1ULL << 42)
485490

486491
/**
487492
* CUSE INIT request/reply flags
@@ -909,7 +914,8 @@ struct fuse_init_out {
909914
uint16_t map_alignment;
910915
uint32_t flags2;
911916
uint32_t max_stack_depth;
912-
uint32_t unused[6];
917+
uint16_t request_timeout;
918+
uint16_t unused[11];
913919
};
914920

915921
#define CUSE_INIT_INFO_MAX 4096

0 commit comments

Comments
 (0)