[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