[Devel] [PATCH VZ10 v2] fs/fuse kio: fix hung connection on vstorage-mount exit
Konstantin Khorenko
khorenko at virtuozzo.com
Fri May 15 15:33:16 MSK 2026
On 4/24/26 07:44, Liu Kui wrote:
> krpc_sender kthread holds a reference to vstorage-mount's file_struct,
> preventing /dev/fuse from closing after the process exits. This leaves
> the fuse connection without proper teardown and prevents systemd from
> restarting the vstorage-fs service.
>
> Fix this by adding a krpc_watcher kthread that monitors the userspace
> vstorage-mount process via its pidfd wait queue and stops the krpc_sender
> kthread when that process exits, triggering a clean fuse connection
> teardown and allowing systemd to restart the vstorage-fs service.
>
> Related to
> https://virtuozzo.atlassian.net/browse/VSTOR-128451
>
> Signed-off-by: Liu Kui <kui.liu at virtuozzo.com>
> ---
> fs/fuse/kio/pcs/pcs_krpc.c | 93 ++++++++++++++++++++++++++++++--------
> fs/fuse/kio/pcs/pcs_krpc.h | 4 +-
> 2 files changed, 77 insertions(+), 20 deletions(-)
>
> diff --git a/fs/fuse/kio/pcs/pcs_krpc.c b/fs/fuse/kio/pcs/pcs_krpc.c
> index 374cbce9c3c4..8c87feb78a34 100644
> --- a/fs/fuse/kio/pcs/pcs_krpc.c
> +++ b/fs/fuse/kio/pcs/pcs_krpc.c
> @@ -650,12 +650,49 @@ static void kreq_submit(struct krpc_req *kreq)
> }
> }
>
> -static int krpc_threadfn(void *data)
> +static bool process_has_exited(struct pid *pid)
> +{
> + struct task_struct *t;
> + bool exited = false;
> +
> + rcu_read_lock();
> + t = pid_task(pid, PIDTYPE_TGID);
> +
> + if (!t || READ_ONCE(t->exit_state) != 0)
> + exited = true;
> + rcu_read_unlock();
> +
> + return exited;
> +}
> +
> +static int krpc_watcher_fn(void *data)
> +{
> + struct pcs_krpc_set *krpcs = data;
> +
> + wait_event_interruptible(krpcs->watched_pid->wait_pidfd,
> + process_has_exited(krpcs->watched_pid) || kthread_should_stop());
If the wait returns spuriously (signal), the watcher proceeds to tear down the sender unconditionally.
For a kthread, signals are unlikely unless explicitly sent, but a spurious teardown would be
catastrophic.
May be to may an extra check after wait_event_interruptible():
if (!process_has_exited(krpcs->watched_pid) && !kthread_should_stop())
...
Or may be to use wait_event (non-interruptible), since kthreads typically don't need signal handling.
> +
> + kthread_stop(krpcs->sender_task);
> + put_task_struct(krpcs->sender_task);
> + krpcs->sender_task = NULL;
> + mmput(krpcs->mm);
> + krpcs->mm = NULL;
> + put_pid(krpcs->watched_pid);
> + krpcs->watched_pid = NULL;
> +
> + return 0;
> +}
> +
> +static int krpc_sender_fn(void *data)
> {
> struct pcs_krpc_set *krpcs = data;
> struct llist_node *ll;
> struct krpc_req *kreq, *kreq_next;
>
> + /* a rare case when creation of the watcher kthread failed */
> + if (!krpcs->mm)
> + return 0;
> +
Can you please explain in what case this check is needed?
When kthread_create(krpc_watcher_fn, ...) fails, here's what happens:
Error path in krpc_task_create:
krpc_task_create()
kthread_create(krpc_sender_fn, ...) -> success, sender in stopped state
kthread_create(krpc_watcher_fn, ...) -> fails, IS_ERR
goto err_watcher
kthread_stop(sender)
Inside kthread_stop(sender):
kthread_stop(sender)
set_bit(KTHREAD_SHOULD_STOP, ...)
wake_up_process(sender)
-> sender wakes from schedule_preempt_disabled() inside kthread()
The sender wakes up in the kthread infrastructure code, NOT in krpc_sender_fn:
kernel/kthread.c: kthread()
schedule_preempt_disabled() // was sleeping here
// wakes up, checks:
if (!test_bit(KTHREAD_SHOULD_STOP, ...)) // true, so SKIP threadfn
ret = threadfn(data); // krpc_sender_fn is NEVER called
do_exit(-EINTR)
So krpc_sender_fn() is never invoked. The !krpcs->mm check is dead code for the scenario described
in the comment.
> kthread_use_mm(krpcs->mm);
>
> for (;;) {
More information about the Devel
mailing list