diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 64d0edf428f850..2ea7b04cc48b51 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -1997,7 +1997,40 @@ static bool manage_workers(struct worker *worker) maybe_create_worker(pool); pool->manager = NULL; + + /* + * Put the manager back to ->idle_list, this allows us to drop the + * pool->lock safely without racing with put_unbound_pool() + * + * + * worker_thread(): + * spin_lock_irq(&pool->lock); + * worker_leave_idle(); + * manage_workers(): // return true + * mutex_trylock(&pool->manager_arb); + * + * spin_unlock_irq(&pool->lock); + * mutex_unlock(&pool->manager_arb); + * + * put_unbound_pool(): + * mutex_lock(&pool->manager_arb); + * spin_lock_irq(&pool->lock); + * + * + * ... + * wait_for_completion(&pool->detach_completion); + * workers is not empty> + * + * spin_lock_irq(&pool->lock); + * worklist is empty, go to sleep> + * + * No one is going to wake up the manager worker, even so, it won't + * complete(->detach_completion), since it's not a DIE worker. + */ + worker_enter_idle(worker); + spin_unlock_irq(&pool->lock); mutex_unlock(&pool->manager_arb); + spin_lock_irq(&pool->lock); return true; } @@ -2202,6 +2235,7 @@ static int worker_thread(void *__worker) woke_up: spin_lock_irq(&pool->lock); +recheck: /* am I supposed to die? */ if (unlikely(worker->flags & WORKER_DIE)) { spin_unlock_irq(&pool->lock); @@ -2216,7 +2250,6 @@ static int worker_thread(void *__worker) } worker_leave_idle(worker); -recheck: /* no more worker necessary? */ if (!need_more_worker(pool)) goto sleep;