Skip to content

Commit 89affbf

Browse files
dimaz-waymotorvalds
authored andcommitted
cpuset: fix a deadlock due to incomplete patching of cpusets_enabled()
In codepaths that use the begin/retry interface for reading mems_allowed_seq with irqs disabled, there exists a race condition that stalls the patch process after only modifying a subset of the static_branch call sites. This problem manifested itself as a deadlock in the slub allocator, inside get_any_partial. The loop reads mems_allowed_seq value (via read_mems_allowed_begin), performs the defrag operation, and then verifies the consistency of mem_allowed via the read_mems_allowed_retry and the cookie returned by xxx_begin. The issue here is that both begin and retry first check if cpusets are enabled via cpusets_enabled() static branch. This branch can be rewritted dynamically (via cpuset_inc) if a new cpuset is created. The x86 jump label code fully synchronizes across all CPUs for every entry it rewrites. If it rewrites only one of the callsites (specifically the one in read_mems_allowed_retry) and then waits for the smp_call_function(do_sync_core) to complete while a CPU is inside the begin/retry section with IRQs off and the mems_allowed value is changed, we can hang. This is because begin() will always return 0 (since it wasn't patched yet) while retry() will test the 0 against the actual value of the seq counter. The fix is to use two different static keys: one for begin (pre_enable_key) and one for retry (enable_key). In cpuset_inc(), we first bump the pre_enable key to ensure that cpuset_mems_allowed_begin() always return a valid seqcount if are enabling cpusets. Similarly, when disabling cpusets via cpuset_dec(), we first ensure that callers of cpuset_mems_allowed_retry() will start ignoring the seqcount value before we let cpuset_mems_allowed_begin() return 0. The relevant stack traces of the two stuck threads: CPU: 1 PID: 1415 Comm: mkdir Tainted: G L 4.9.36-00104-g540c51286237 Freescale#4 Hardware name: Default string Default string/Hardware, BIOS 4.29.1-20170526215256 05/26/2017 task: ffff8817f9c28000 task.stack: ffffc9000ffa4000 RIP: smp_call_function_many+0x1f9/0x260 Call Trace: smp_call_function+0x3b/0x70 on_each_cpu+0x2f/0x90 text_poke_bp+0x87/0xd0 arch_jump_label_transform+0x93/0x100 __jump_label_update+0x77/0x90 jump_label_update+0xaa/0xc0 static_key_slow_inc+0x9e/0xb0 cpuset_css_online+0x70/0x2e0 online_css+0x2c/0xa0 cgroup_apply_control_enable+0x27f/0x3d0 cgroup_mkdir+0x2b7/0x420 kernfs_iop_mkdir+0x5a/0x80 vfs_mkdir+0xf6/0x1a0 SyS_mkdir+0xb7/0xe0 entry_SYSCALL_64_fastpath+0x18/0xad ... CPU: 2 PID: 1 Comm: init Tainted: G L 4.9.36-00104-g540c51286237 Freescale#4 Hardware name: Default string Default string/Hardware, BIOS 4.29.1-20170526215256 05/26/2017 task: ffff8818087c0000 task.stack: ffffc90000030000 RIP: int3+0x39/0x70 Call Trace: <#DB> ? ___slab_alloc+0x28b/0x5a0 <EOE> ? copy_process.part.40+0xf7/0x1de0 __slab_alloc.isra.80+0x54/0x90 copy_process.part.40+0xf7/0x1de0 copy_process.part.40+0xf7/0x1de0 kmem_cache_alloc_node+0x8a/0x280 copy_process.part.40+0xf7/0x1de0 _do_fork+0xe7/0x6c0 _raw_spin_unlock_irq+0x2d/0x60 trace_hardirqs_on_caller+0x136/0x1d0 entry_SYSCALL_64_fastpath+0x5/0xad do_syscall_64+0x27/0x350 SyS_clone+0x19/0x20 do_syscall_64+0x60/0x350 entry_SYSCALL64_slow_path+0x25/0x25 Link: http://lkml.kernel.org/r/[email protected] Fixes: 46e700a ("mm, page_alloc: remove unnecessary taking of a seqlock when cpusets are disabled") Signed-off-by: Dima Zavin <[email protected]> Reported-by: Cliff Spradlin <[email protected]> Acked-by: Vlastimil Babka <[email protected]> Cc: Peter Zijlstra <[email protected]> Cc: Christopher Lameter <[email protected]> Cc: Li Zefan <[email protected]> Cc: Pekka Enberg <[email protected]> Cc: David Rientjes <[email protected]> Cc: Joonsoo Kim <[email protected]> Cc: Mel Gorman <[email protected]> Cc: <[email protected]> Signed-off-by: Andrew Morton <[email protected]> Signed-off-by: Linus Torvalds <[email protected]>
1 parent 9d95aa4 commit 89affbf

2 files changed

Lines changed: 18 additions & 2 deletions

File tree

include/linux/cpuset.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818

1919
#ifdef CONFIG_CPUSETS
2020

21+
/*
22+
* Static branch rewrites can happen in an arbitrary order for a given
23+
* key. In code paths where we need to loop with read_mems_allowed_begin() and
24+
* read_mems_allowed_retry() to get a consistent view of mems_allowed, we need
25+
* to ensure that begin() always gets rewritten before retry() in the
26+
* disabled -> enabled transition. If not, then if local irqs are disabled
27+
* around the loop, we can deadlock since retry() would always be
28+
* comparing the latest value of the mems_allowed seqcount against 0 as
29+
* begin() still would see cpusets_enabled() as false. The enabled -> disabled
30+
* transition should happen in reverse order for the same reasons (want to stop
31+
* looking at real value of mems_allowed.sequence in retry() first).
32+
*/
33+
extern struct static_key_false cpusets_pre_enable_key;
2134
extern struct static_key_false cpusets_enabled_key;
2235
static inline bool cpusets_enabled(void)
2336
{
@@ -32,12 +45,14 @@ static inline int nr_cpusets(void)
3245

3346
static inline void cpuset_inc(void)
3447
{
48+
static_branch_inc(&cpusets_pre_enable_key);
3549
static_branch_inc(&cpusets_enabled_key);
3650
}
3751

3852
static inline void cpuset_dec(void)
3953
{
4054
static_branch_dec(&cpusets_enabled_key);
55+
static_branch_dec(&cpusets_pre_enable_key);
4156
}
4257

4358
extern int cpuset_init(void);
@@ -115,7 +130,7 @@ extern void cpuset_print_current_mems_allowed(void);
115130
*/
116131
static inline unsigned int read_mems_allowed_begin(void)
117132
{
118-
if (!cpusets_enabled())
133+
if (!static_branch_unlikely(&cpusets_pre_enable_key))
119134
return 0;
120135

121136
return read_seqcount_begin(&current->mems_allowed_seq);
@@ -129,7 +144,7 @@ static inline unsigned int read_mems_allowed_begin(void)
129144
*/
130145
static inline bool read_mems_allowed_retry(unsigned int seq)
131146
{
132-
if (!cpusets_enabled())
147+
if (!static_branch_unlikely(&cpusets_enabled_key))
133148
return false;
134149

135150
return read_seqcount_retry(&current->mems_allowed_seq, seq);

kernel/cgroup/cpuset.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
#include <linux/cgroup.h>
6464
#include <linux/wait.h>
6565

66+
DEFINE_STATIC_KEY_FALSE(cpusets_pre_enable_key);
6667
DEFINE_STATIC_KEY_FALSE(cpusets_enabled_key);
6768

6869
/* See "Frequency meter" comments, below. */

0 commit comments

Comments
 (0)