fuse: suspend blockers

Sergey Senozhatsky posted 1 patch 6 months, 2 weeks ago
fuse: suspend blockers
Posted by Sergey Senozhatsky 6 months, 2 weeks ago
Hi,

We are seeing a number of cases when blocked fuse requests prevent
the system from suspending, which is a little important on laptops.
Usually something like this:

[ 36.281038] Freezing user space processes
[ 56.284961] Freezing user space processes failed after 20.003 seconds (1 tasks refusing to freeze, wq_busy=0):
[ 56.285069] task:secagentd state:D stack:0 pid:1792 ppid:1711 flags:0x00004006
[ 56.285084] Call Trace:
[ 56.285091] <TASK>
[ 56.285111] schedule+0x612/0x2230
[ 56.285136] fuse_get_req+0x108/0x2d0
[ 56.285179] fuse_simple_request+0x40/0x630
[ 56.285203] fuse_getxattr+0x15d/0x1c0
[...]

Which looks like wait_event_killable_exclusive() in fuse_get_req().
And we were wondering if we could do something about it.  For example,
perhaps, something like:

---

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index f182a4ca1bb32..587cea3a0407d 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -241,7 +241,7 @@ static struct fuse_req *fuse_get_req(struct fuse_conn *fc, bool for_background)
 
        if (fuse_block_alloc(fc, for_background)) {
                err = -EINTR;
-               if (wait_event_killable_exclusive(fc->blocked_waitq,
+               if (wait_event_freezable_killable_exclusive(fc->blocked_waitq,
                                !fuse_block_alloc(fc, for_background)))
                        goto out;
        }
diff --git a/include/linux/wait.h b/include/linux/wait.h
index 5b65f720261a9..1c8fdf1e02785 100644
--- a/include/linux/wait.h
+++ b/include/linux/wait.h
@@ -628,6 +628,19 @@ do {                                                                               \
        __ret;                                                                  \
 })
 
+#define __wait_event_freezable_killable_exclusive(wq, condition)               \
+       ___wait_event(wq, condition, TASK_KILLABLE, 1, 0,                       \
+                     freezable_schedule())
+
+#define wait_event_freezable_killable_exclusive(wq, condition)                 \
+({                                                                             \
+       int __ret = 0;                                                          \
+       might_sleep();                                                          \
+       if (!(condition))                                                       \
+               __ret = __wait_event_freezable_killable_exclusive(wq,           \
+                                                                 condition);   \
+       __ret;                                                                  \
+})
 
 #define __wait_event_freezable_exclusive(wq, condition)                                \
        ___wait_event(wq, condition, TASK_INTERRUPTIBLE, 1, 0,                  \

---

Would this be a terrible idea?
Re: fuse: suspend blockers
Posted by Miklos Szeredi 6 months, 2 weeks ago
On Fri, 6 Jun 2025 at 07:57, Sergey Senozhatsky
<senozhatsky@chromium.org> wrote:
>
> Hi,
>
> We are seeing a number of cases when blocked fuse requests prevent
> the system from suspending, which is a little important on laptops.
> Usually something like this:
>
> [ 36.281038] Freezing user space processes
> [ 56.284961] Freezing user space processes failed after 20.003 seconds (1 tasks refusing to freeze, wq_busy=0):
> [ 56.285069] task:secagentd state:D stack:0 pid:1792 ppid:1711 flags:0x00004006
> [ 56.285084] Call Trace:
> [ 56.285091] <TASK>
> [ 56.285111] schedule+0x612/0x2230
> [ 56.285136] fuse_get_req+0x108/0x2d0
> [ 56.285179] fuse_simple_request+0x40/0x630
> [ 56.285203] fuse_getxattr+0x15d/0x1c0
> [...]
>
> Which looks like wait_event_killable_exclusive() in fuse_get_req().
> And we were wondering if we could do something about it.  For example,
> perhaps, something like:
>
> ---
>
> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> index f182a4ca1bb32..587cea3a0407d 100644
> --- a/fs/fuse/dev.c
> +++ b/fs/fuse/dev.c
> @@ -241,7 +241,7 @@ static struct fuse_req *fuse_get_req(struct fuse_conn *fc, bool for_background)
>
>         if (fuse_block_alloc(fc, for_background)) {
>                 err = -EINTR;
> -               if (wait_event_killable_exclusive(fc->blocked_waitq,
> +               if (wait_event_freezable_killable_exclusive(fc->blocked_waitq,
>                                 !fuse_block_alloc(fc, for_background)))
>                         goto out;
>         }

This looks fine.  We can turn each wait into a freezable one inside
fuse.  But that still would leave core locks (inode lock, rename lock,
page lock, etc) unfreezable.  Turning those into freezable isn't
realistic...

But a partial solution might still be better than no solution.

Thanks,
Miklos
Re: fuse: suspend blockers
Posted by Sergey Senozhatsky 6 months, 1 week ago
On (25/06/06 10:26), Miklos Szeredi wrote:
> > @@ -241,7 +241,7 @@ static struct fuse_req *fuse_get_req(struct fuse_conn *fc, bool for_background)
> >
> >         if (fuse_block_alloc(fc, for_background)) {
> >                 err = -EINTR;
> > -               if (wait_event_killable_exclusive(fc->blocked_waitq,
> > +               if (wait_event_freezable_killable_exclusive(fc->blocked_waitq,
> >                                 !fuse_block_alloc(fc, for_background)))
> >                         goto out;
> >         }
> 
> This looks fine.  We can turn each wait into a freezable one inside
> fuse.  But that still would leave core locks (inode lock, rename lock,
> page lock, etc) unfreezable.  Turning those into freezable isn't
> realistic...
> 
> But a partial solution might still be better than no solution.

Thanks Miklos, I sent out a simple patch set [1]

[1] https://lore.kernel.org/linux-kernel/20250610045321.4030262-1-senozhatsky@chromium.org