Skip to content

Commit

Permalink
kthread_worker: fix return value when kthread_mod_delayed_work() race…
Browse files Browse the repository at this point in the history
…s with kthread_cancel_delayed_work_sync()

kthread_mod_delayed_work() might race with
kthread_cancel_delayed_work_sync() or another kthread_mod_delayed_work()
call.  The function lets the other operation win when it sees
work->canceling counter set.  And it returns @false.

But it should return @true as it is done by the related workqueue API, see
mod_delayed_work_on().

The reason is that the return value might be used for reference counting.
It has to distinguish the case when the number of queued works has changed
or stayed the same.

The change is safe.  kthread_mod_delayed_work() return value is not
checked anywhere at the moment.

Link: https://lore.kernel.org/r/[email protected]
Link: https://lkml.kernel.org/r/[email protected]
Signed-off-by: Petr Mladek <[email protected]>
Reported-by: Oleg Nesterov <[email protected]>
Cc: Nathan Chancellor <[email protected]>
Cc: Nick Desaulniers <[email protected]>
Cc: Tejun Heo <[email protected]>
Cc: Minchan Kim <[email protected]>
Cc: <[email protected]>
Cc: Martin Liu <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
pmladek authored and torvalds committed Jun 29, 2021
1 parent 20ce0c2 commit d71ba16
Showing 1 changed file with 12 additions and 7 deletions.
19 changes: 12 additions & 7 deletions kernel/kthread.c
Original file line number Diff line number Diff line change
Expand Up @@ -1156,14 +1156,14 @@ static bool __kthread_cancel_work(struct kthread_work *work)
* modify @dwork's timer so that it expires after @delay. If @delay is zero,
* @work is guaranteed to be queued immediately.
*
* Return: %true if @dwork was pending and its timer was modified,
* %false otherwise.
* Return: %false if @dwork was idle and queued, %true otherwise.
*
* A special case is when the work is being canceled in parallel.
* It might be caused either by the real kthread_cancel_delayed_work_sync()
* or yet another kthread_mod_delayed_work() call. We let the other command
* win and return %false here. The caller is supposed to synchronize these
* operations a reasonable way.
* win and return %true here. The return value can be used for reference
* counting and the number of queued works stays the same. Anyway, the caller
* is supposed to synchronize these operations a reasonable way.
*
* This function is safe to call from any context including IRQ handler.
* See __kthread_cancel_work() and kthread_delayed_work_timer_fn()
Expand All @@ -1175,13 +1175,15 @@ bool kthread_mod_delayed_work(struct kthread_worker *worker,
{
struct kthread_work *work = &dwork->work;
unsigned long flags;
int ret = false;
int ret;

raw_spin_lock_irqsave(&worker->lock, flags);

/* Do not bother with canceling when never queued. */
if (!work->worker)
if (!work->worker) {
ret = false;
goto fast_queue;
}

/* Work must not be used with >1 worker, see kthread_queue_work() */
WARN_ON_ONCE(work->worker != worker);
Expand All @@ -1199,8 +1201,11 @@ bool kthread_mod_delayed_work(struct kthread_worker *worker,
* be used for reference counting.
*/
kthread_cancel_delayed_work_timer(work, &flags);
if (work->canceling)
if (work->canceling) {
/* The number of works in the queue does not change. */
ret = true;
goto out;
}
ret = __kthread_cancel_work(work);

fast_queue:
Expand Down

0 comments on commit d71ba16

Please sign in to comment.