Skip to content

Commit 9d0cce2

Browse files
committed
rcu-tasks: Fix boot-time RCU tasks debug-only deadlock
In kernels built with CONFIG_PROVE_RCU=y (for example, lockdep kernels), the following sequence of events can occur: o rcu_init_tasks_generic() is invoked just before init is spawned. It invokes rcu_spawn_tasks_kthread() and friends. o rcu_spawn_tasks_kthread() invokes rcu_spawn_tasks_kthread_generic(), which uses kthread_run() to create the needed kthread. o Control returns to rcu_init_tasks_generic(), which, because this is a CONFIG_PROVE_RCU=y kernel, invokes the version of the rcu_tasks_initiate_self_tests() function that actually does something, including invoking synchronize_rcu_tasks(), which in turn invokes synchronize_rcu_tasks_generic(). o synchronize_rcu_tasks_generic() sees that the ->kthread_ptr is still NULL, because the newly spawned kthread has not yet started. o The new kthread starts, preempting synchronize_rcu_tasks_generic() just after its check. This kthread invokes rcu_tasks_one_gp(), which acquires ->tasks_gp_mutex, and, seeing no work, blocks in rcuwait_wait_event(). Note that this step requires either a preemptible kernel or a fault-injection-style sleep at the beginning of mutex_lock(). o synchronize_rcu_tasks_generic() resumes and invokes rcu_tasks_one_gp(). o rcu_tasks_one_gp() attempts to acquire ->tasks_gp_mutex, which is still held by the newly spawned kthread's rcu_tasks_one_gp() function. Deadlock. Because the only reason for ->tasks_gp_mutex is to handle pre-kthread synchronous grace periods, this commit avoids this deadlock by having rcu_tasks_one_gp() momentarily release ->tasks_gp_mutex while invoking rcuwait_wait_event(). This allows the call to rcu_tasks_one_gp() from synchronize_rcu_tasks_generic() proceed. Note that it is not necessary to release the mutex anywhere else in rcu_tasks_one_gp() because rcuwait_wait_event() is the only function that can block indefinitely. Reported-by: Guenter Roeck <[email protected]> Reported-by: Roy Hopkins <[email protected]> Reported-by: Peter Zijlstra <[email protected]> Signed-off-by: Paul E. McKenney <[email protected]> Tested-by: Roy Hopkins <[email protected]>
1 parent cb88f7f commit 9d0cce2

File tree

1 file changed

+2
-0
lines changed

1 file changed

+2
-0
lines changed

kernel/rcu/tasks.h

+2
Original file line numberDiff line numberDiff line change
@@ -570,10 +570,12 @@ static void rcu_tasks_one_gp(struct rcu_tasks *rtp, bool midboot)
570570
if (unlikely(midboot)) {
571571
needgpcb = 0x2;
572572
} else {
573+
mutex_unlock(&rtp->tasks_gp_mutex);
573574
set_tasks_gp_state(rtp, RTGS_WAIT_CBS);
574575
rcuwait_wait_event(&rtp->cbs_wait,
575576
(needgpcb = rcu_tasks_need_gpcb(rtp)),
576577
TASK_IDLE);
578+
mutex_lock(&rtp->tasks_gp_mutex);
577579
}
578580

579581
if (needgpcb & 0x2) {

0 commit comments

Comments
 (0)