Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions module/os/linux/spl/spl-taskq.c
Original file line number Diff line number Diff line change
Expand Up @@ -633,14 +633,31 @@ taskq_cancel_id(taskq_t *tq, taskqid_t id)

/*
* The task_expire() function takes the tq->tq_lock so drop
* drop the lock before synchronously cancelling the timer.
* the lock before synchronously cancelling the timer.
*
* Always call timer_delete_sync() unconditionally. A
* timer_pending() check would be insufficient and unsafe.
* When a timer expires, it is immediately dequeued from the
* timer wheel (timer_pending() returns FALSE), but the
* callback (task_expire) may not run until later.
*
* The race window:
* 1) Timer expires and is dequeued - timer_pending() now
* returns FALSE
* 2) task_done() is called below, freeing the task, sets
* tqent_func = NULL and clears flags including CANCEL
* 3) Timer callback finally runs, sees no CANCEL flag,
* queues task to prio_list
* 4) Worker thread attempts to execute NULL tqent_func
* and panics
*
* timer_delete_sync() prevents this by ensuring the timer
* callback completes before the task is freed.
*/
if (timer_pending(&t->tqent_timer)) {
spin_unlock_irqrestore(&tq->tq_lock, flags);
timer_delete_sync(&t->tqent_timer);
spin_lock_irqsave_nested(&tq->tq_lock, flags,
tq->tq_lock_class);
}
spin_unlock_irqrestore(&tq->tq_lock, flags);
timer_delete_sync(&t->tqent_timer);
spin_lock_irqsave_nested(&tq->tq_lock, flags,
tq->tq_lock_class);

if (!(t->tqent_flags & TQENT_FLAG_PREALLOC))
task_done(tq, t);
Expand Down
Loading