[Devel] [PATCH hci-8.1 v2 5/6] drivers/md/push_backup: arm deadline timer under pb->lock

no-reply at sashiko.virtuozzo.com no-reply at sashiko.virtuozzo.com
Tue Nov 3 03:45:10 MSK 2026


Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] dm-push-backup: pb_timer_func() can re-arm timer or queue work after stop/destroy
- [Medium] dm-push-backup: pb_destroy() uses non-waiting timer_delete_sync_try() instead of timer_shutdown_sync()
- [Medium] dm-push-backup: cleanup_backup() leaves deadline_jiffies stale, so a racing timer callback can re-arm or queue_work()
--

commit f322079c9ebbcb0d0c2a4b8257ff8a52b4e79a15
Author: Andrey Zhadchenko <andrey.zhadchenko at virtuozzo.com>

drivers/md/push_backup: arm deadline timer under pb->lock

This patch moves the mod_timer() calls in pb_timer_func() and
postpone_if_required_for_backup() under pb->lock. It also makes the timer
callback idle when deadline_jiffies is S64_MAX. It drops the
rcu_read_lock()/synchronize_rcu() pairing between
postpone_if_required_for_backup() and push_backup_stop().

Link: https://virtuozzo.atlassian.net/browse/VSTOR-152912

> diff --git a/drivers/md/dm-push-backup.c b/drivers/md/dm-push-backup.c
> --- a/drivers/md/dm-push-backup.c
> +++ b/drivers/md/dm-push-backup.c

[ ... ]

> @@ -241,23 +241,24 @@ static void pb_timer_func(struct timer_list *timer)
>  
>  	spin_lock_irqsave(&pb->lock, flags);
>  	deadline = pb->deadline_jiffies;
> -	spin_unlock_irqrestore(&pb->lock, flags);
> -
> +	if (deadline == S64_MAX)
> +		goto unlock; /* Nothing is delayed, let the timer idle */
>  	if (unlikely(time_before64(now, deadline)))
>  		mod_timer(timer, jiffies + (deadline - now) + 1);
>  	else
>  		queue_work(pb->wq, &pb->worker);
> +unlock:
> +	spin_unlock_irqrestore(&pb->lock, flags);
>  }

[Severity: Medium]
Can pb_timer_func() re-arm the timer or queue work after push_backup_stop()
has returned?

The callback does not check pb->alive. It only tests deadline_jiffies
against S64_MAX. cleanup_backup() sets pb->alive = false and drains
nr_delayed to 0, but it leaves deadline_jiffies at its old finite value.
Only setup_pb() and push_backup_write() write S64_MAX.

Consider this sequence:

CPU1
pb_timer_func()
    blocks on spin_lock_irqsave(&pb->lock)

CPU0
push_backup_stop()
    cleanup_backup()
        pb->alive = false;
        (deadline_jiffies still finite)
    timer_delete_sync_try()   <- returns -1, callback is running

CPU1 then takes pb->lock, sees a finite deadline, and calls mod_timer()
or queue_work(pb->wq, &pb->worker). If push_backup_start() has already run
for a new session, the stale do_pb_work() could call cleanup_backup() and
expire that session early.

Should cleanup_backup() set pb->deadline_jiffies = S64_MAX under pb->lock,
or should pb_timer_func() check pb->alive?

> @@ -272,16 +273,13 @@ static int postpone_if_required_for_backup(struct push_backup *pb,

[ ... ]

> @@ -400,8 +398,6 @@ static int push_backup_stop(struct push_backup *pb,
>  		return -EBADF;
>  	cleanup_backup(pb);
>  
> -	/* Wait postpone_if_required_for_backup() starts timer */
> -	synchronize_rcu();
>  	WARN_ON_ONCE(timer_delete_sync_try(&pb->deadline_timer) < 0);
>  	flush_workqueue(pb->wq);

[Severity: Medium]
The commit message says "the callback re-arming itself is handled by
timer_delete_sync_try()". Is that accurate?

timer_delete_sync_try() calls __try_to_del_timer_sync(timer, false). That
returns -1 without waiting when base->running_timer == timer, and it does
not prevent re-arming. Here the -1 result only triggers WARN_ON_ONCE().

The removed synchronize_rcu() only covered
postpone_if_required_for_backup() arming the timer. It never covered the
callback re-arming itself.

So after this change, can the callback still call mod_timer() or
queue_work() after flush_workqueue() has returned? Would
timer_shutdown_sync(), or a loop that waits for the running callback,
be more appropriate here?

The same WARN_ON_ONCE(timer_delete_sync_try(&pb->deadline_timer) < 0)
pattern in pb_destroy() is not touched by this patch. It runs before
destroy_workqueue(pb->wq) and pb_put(pb).

If pb_timer_func() is running on another CPU and blocked on pb->lock, can
it call queue_work() on a destroyed workqueue? Can it also touch pb after
it has been freed? When pb_destroy() runs without a prior push_backup_stop(),
pb->alive is still true and a finite deadline can still exist. Would
timer_shutdown_sync() be the right call in pb_destroy()?

-- 
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261103003246.712239-1-andrey.zhadchenko@virtuozzo.com?part=5



More information about the Devel mailing list