2015-08-12 19:36:29

by Jan H. Schönherr

[permalink] [raw]
Subject: [PATCH v2 resend] sched: fix cpu_active_mask/cpu_online_mask race

From: Jan H. Schönherr <[email protected]>

There is a race condition in SMP bootup code, which may result in

WARNING: CPU: 0 PID: 1 at kernel/workqueue.c:4418
workqueue_cpu_up_callback()
or
kernel BUG at kernel/smpboot.c:135!

It can be triggered with a bit of luck in Linux guests running on
busy hosts.

CPU0 CPUn
==== ====

_cpu_up()
__cpu_up()
start_secondary()
set_cpu_online()
cpumask_set_cpu(cpu,
to_cpumask(cpu_online_bits));
cpu_notify(CPU_ONLINE)
<do stuff, see below>
cpumask_set_cpu(cpu,
to_cpumask(cpu_active_bits));

During the various CPU_ONLINE callbacks CPUn is online but not active.
Several things can go wrong at that point, depending on the scheduling
of tasks on CPU0.

Variant 1:

cpu_notify(CPU_ONLINE)
workqueue_cpu_up_callback()
rebind_workers()
set_cpus_allowed_ptr()

This call fails because it requires an active CPU; rebind_workers()
ends with a warning:

WARNING: CPU: 0 PID: 1 at kernel/workqueue.c:4418
workqueue_cpu_up_callback()

Variant 2:

cpu_notify(CPU_ONLINE)
smpboot_thread_call()
smpboot_unpark_threads()
..
__kthread_unpark()
__kthread_bind()
wake_up_state()
..
select_task_rq()
select_fallback_rq()

The ->wake_cpu of the unparked thread is not allowed, making a call
to select_fallback_rq() necessary. Then, select_fallback_rq() cannot
find an allowed, active CPU and promptly resets the allowed CPUs, so
that the task in question ends up on CPU0.

When those unparked tasks are eventually executed, they run
immediately into a BUG:

kernel BUG at kernel/smpboot.c:135!

Just changing the order in which the online/active bits are set (and
adding some memory barriers), would solve the two issues above.
However, it would change the order of operations back to the one
before commit 6acbfb96976f ("sched: Fix hotplug vs.
set_cpus_allowed_ptr()"), thus, reintroducing that particular problem.

Going further back into history, we have at least the following commits
touching this topic:
- commit 2baab4e90495 ("sched: Fix select_fallback_rq() vs
cpu_active/cpu_online")
- commit 5fbd036b552f ("sched: Cleanup cpu_active madness")

Together, these give us the following non-working solutions:
- secondary CPU sets active before online, because active is assumed to
be a subset of online;
- secondary CPU sets online before active, because the primary CPU
assumes that an online CPU is also active;
- secondary CPU sets online and waits for primary CPU to set active,
because it might deadlock.

Commit 875ebe940d77 ("powerpc/smp: Wait until secondaries are active &
online") introduces an arch-specific solution to this arch-independent
problem.

Now, go for a more general solution without explicit waiting and simply
set active twice: once on the secondary CPU after online was set and
once on the primary CPU after online was seen.

Fixes: 6acbfb96976f ("sched: Fix hotplug vs. set_cpus_allowed_ptr()")
Signed-off-by: Jan H. Schönherr <[email protected]>
Cc: Ingo Molnar <[email protected]>
Cc: Peter Zijlstra <[email protected]>

---

v2:
- Big honking comment in the CPU_ONLINE path for Peter. :)

kernel/sched/core.c | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 78b4bad10..e967343 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -5433,6 +5433,14 @@ static int sched_cpu_active(struct notifier_block *nfb,
case CPU_STARTING:
set_cpu_rq_start_time();
return NOTIFY_OK;
+ case CPU_ONLINE:
+ /*
+ * At this point a starting CPU has marked itself as online via
+ * set_cpu_online(). But it might not yet have marked itself
+ * as active, which is essential from here on.
+ *
+ * Thus, fall-through and help the starting CPU along.
+ */
case CPU_DOWN_FAILED:
set_cpu_active((long)hcpu, true);
return NOTIFY_OK;
--
2.4.6.1.gea4e83c


Subject: [tip:sched/urgent] sched: Fix cpu_active_mask/ cpu_online_mask race

Commit-ID: dd9d3843755da95f63dd3a376f62b3e45c011210
Gitweb: http://git.kernel.org/tip/dd9d3843755da95f63dd3a376f62b3e45c011210
Author: Jan H. Schönherr <[email protected]>
AuthorDate: Wed, 12 Aug 2015 21:35:56 +0200
Committer: Ingo Molnar <[email protected]>
CommitDate: Tue, 25 Aug 2015 12:14:39 +0200

sched: Fix cpu_active_mask/cpu_online_mask race

There is a race condition in SMP bootup code, which may result
in

WARNING: CPU: 0 PID: 1 at kernel/workqueue.c:4418
workqueue_cpu_up_callback()
or
kernel BUG at kernel/smpboot.c:135!

It can be triggered with a bit of luck in Linux guests running
on busy hosts.

CPU0 CPUn
==== ====

_cpu_up()
__cpu_up()
start_secondary()
set_cpu_online()
cpumask_set_cpu(cpu,
to_cpumask(cpu_online_bits));
cpu_notify(CPU_ONLINE)
<do stuff, see below>
cpumask_set_cpu(cpu,
to_cpumask(cpu_active_bits));

During the various CPU_ONLINE callbacks CPUn is online but not
active. Several things can go wrong at that point, depending on
the scheduling of tasks on CPU0.

Variant 1:

cpu_notify(CPU_ONLINE)
workqueue_cpu_up_callback()
rebind_workers()
set_cpus_allowed_ptr()

This call fails because it requires an active CPU; rebind_workers()
ends with a warning:

WARNING: CPU: 0 PID: 1 at kernel/workqueue.c:4418
workqueue_cpu_up_callback()

Variant 2:

cpu_notify(CPU_ONLINE)
smpboot_thread_call()
smpboot_unpark_threads()
..
__kthread_unpark()
__kthread_bind()
wake_up_state()
..
select_task_rq()
select_fallback_rq()

The ->wake_cpu of the unparked thread is not allowed, making a call
to select_fallback_rq() necessary. Then, select_fallback_rq() cannot
find an allowed, active CPU and promptly resets the allowed CPUs, so
that the task in question ends up on CPU0.

When those unparked tasks are eventually executed, they run
immediately into a BUG:

kernel BUG at kernel/smpboot.c:135!

Just changing the order in which the online/active bits are set
(and adding some memory barriers), would solve the two issues
above. However, it would change the order of operations back to
the one before commit 6acbfb96976f ("sched: Fix hotplug vs.
set_cpus_allowed_ptr()"), thus, reintroducing that particular
problem.

Going further back into history, we have at least the following
commits touching this topic:
- commit 2baab4e90495 ("sched: Fix select_fallback_rq() vs cpu_active/cpu_online")
- commit 5fbd036b552f ("sched: Cleanup cpu_active madness")

Together, these give us the following non-working solutions:

- secondary CPU sets active before online, because active is assumed to
be a subset of online;

- secondary CPU sets online before active, because the primary CPU
assumes that an online CPU is also active;

- secondary CPU sets online and waits for primary CPU to set active,
because it might deadlock.

Commit 875ebe940d77 ("powerpc/smp: Wait until secondaries are
active & online") introduces an arch-specific solution to this
arch-independent problem.

Now, go for a more general solution without explicit waiting and
simply set active twice: once on the secondary CPU after online
was set and once on the primary CPU after online was seen.

set_cpus_allowed_ptr()")

Signed-off-by: Jan H. Schönherr <[email protected]>
Acked-by: Peter Zijlstra <[email protected]>
Cc: <[email protected]>
Cc: Anton Blanchard <[email protected]>
Cc: Borislav Petkov <[email protected]>
Cc: Joerg Roedel <[email protected]>
Cc: Linus Torvalds <[email protected]>
Cc: Matt Wilson <[email protected]>
Cc: Michael Ellerman <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Fixes: 6acbfb96976f ("sched: Fix hotplug vs. set_cpus_allowed_ptr()")
Link: http://lkml.kernel.org/r/[email protected]
Signed-off-by: Ingo Molnar <[email protected]>
---
kernel/sched/core.c | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 78b4bad10..e967343 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -5433,6 +5433,14 @@ static int sched_cpu_active(struct notifier_block *nfb,
case CPU_STARTING:
set_cpu_rq_start_time();
return NOTIFY_OK;
+ case CPU_ONLINE:
+ /*
+ * At this point a starting CPU has marked itself as online via
+ * set_cpu_online(). But it might not yet have marked itself
+ * as active, which is essential from here on.
+ *
+ * Thus, fall-through and help the starting CPU along.
+ */
case CPU_DOWN_FAILED:
set_cpu_active((long)hcpu, true);
return NOTIFY_OK;