From: Waiman Long <longman@redhat.com>
To: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
Frederic Weisbecker <frederic@kernel.org>,
Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Juri Lelli <juri.lelli@redhat.com>,
Vincent Guittot <vincent.guittot@linaro.org>
Cc: Ridong Chen <ridong.chen@linux.dev>,
Dietmar Eggemann <dietmar.eggemann@arm.com>,
Steven Rostedt <rostedt@goodmis.org>,
Ben Segall <bsegall@google.com>, Mel Gorman <mgorman@suse.de>,
Valentin Schneider <vschneid@redhat.com>,
K Prateek Nayak <kprateek.nayak@amd.com>,
Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
cgroups@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [BUG] sched/isolation: suspicious RCU usage with CONFIG_CPUSETS=n
Date: Sun, 8 Nov 2026 13:41:03 -0500 [thread overview]
Message-ID: <fc1d48ef-535f-42e2-bf1f-96461a2165a2@redhat.com> (raw)
In-Reply-To: <20261108145931.1532447-1-mathieu.desnoyers@efficios.com>
On 11/8/26 9:59 AM, Mathieu Desnoyers wrote:
> Hi,
>
> With CONFIG_CPUSETS=n and isolcpus=domain, a read of
> /proc/sys/kernel/sched_rt_runtime_us gives a "suspicious RCU usage"
> lockdep report. A write gives the same report. I first saw it while
> testing a SCHED_DEADLINE series [1]. I then reproduced it in a VM on tip
> sched/urgent (006991aa8997), without the series. A proposed fix follows
> the scissors line.
>
> Trigger
> =======
>
> Build with CONFIG_PROVE_RCU=y and CONFIG_CPUSETS=n. Boot 5 CPUs with
> isolcpus=domain,1,2. Then:
>
> cat /proc/sys/kernel/sched_rt_runtime_us
>
> isolcpus=1,2 gives the same report, since the domain flag is the
> default. Without isolcpus, there is no report.
>
> Log
> ===
>
> =============================
> WARNING: suspicious RCU usage
> 7.4.0-rc1-00267-g006991aa8997 #1 Not tainted
> -----------------------------
> kernel/sched/isolation.c:62 suspicious rcu_dereference_check() usage!
> other info that might help us debug this:
> rcu_scheduler_active = 2, debug_locks = 1
> locks held by cat/91: 3, last CPU#3:
> #0: ff1100000613e260 (&pipe->mutex){+.+.}-{4:4}, at: splice_file_to_pipe+0x23/0xd0
> #1: ffffffff8306c930 (cpu_hotplug_lock){++++}-{0:0}, at: sched_rt_handler+0xb5/0xf0
> #2: ffffffff8307aac0 (sched_domains_mutex){+.+.}-{4:4}, at: partition_sched_domains+0x37/0x990
> stack backtrace:
> CPU: 3 UID: 0 PID: 91 Comm: cat Not tainted 7.4.0-rc1-00267-g006991aa8997 #1 PREEMPT(lazy)
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> Call Trace:
> <TASK>
> dump_stack_lvl+0x5d/0x80
> lockdep_rcu_suspicious.cold+0x4e/0x9f
> housekeeping_cpumask+0xaa/0xb0
> partition_sched_domains+0x8e6/0x990
> sched_rt_handler+0xc3/0xf0
> proc_sys_call_handler+0x1cf/0x250
> copy_splice_read+0x217/0x3b0
> splice_file_to_pipe+0x60/0xd0
> do_sendfile+0x161/0x480
> __x64_sys_sendfile64+0xba/0xd0
> do_syscall_64+0x109/0x5d0
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> </TASK>
>
> Analysis
> ========
>
> sched_rt_handler() calls rebuild_sched_domains() after each read or
> write of sched_rt_period_us and sched_rt_runtime_us. Without
> CONFIG_CPUSETS, rebuild_sched_domains() is this stub in
> include/linux/cpuset.h:
>
> static inline void rebuild_sched_domains(void)
> {
> guard(cpus_read_lock)();
> partition_sched_domains(1, NULL, NULL);
> }
>
> With no doms_new, partition_sched_domains_locked() builds one domain
> from the housekeeping mask:
>
> cpumask_and(doms_new[0], cpu_active_mask,
> housekeeping_cpumask(HK_TYPE_DOMAIN));
>
> housekeeping_cpumask() reads housekeeping.cpumasks[HK_TYPE_DOMAIN] with
> rcu_dereference_all_check(). Its condition is
> housekeeping_dereference_check():
>
> /* Cpuset isn't even writable yet? */
> if (system_state <= SYSTEM_SCHEDULING)
> return true;
>
> /* CPU hotplug write locked, so cpuset partition can't be overwritten */
> if (IS_ENABLED(CONFIG_HOTPLUG_CPU) && lockdep_is_cpus_write_held())
> return true;
>
> /* Cpuset lock held, partitions not writable */
> if (IS_ENABLED(CONFIG_CPUSETS) && lockdep_is_cpuset_held())
> return true;
>
> return false;
>
> The caller holds cpus_read_lock(), not the write lock. It holds no RCU
> read lock. The cpuset case is compiled out. So the check fails, and
> lockdep warns.
>
> The report is a false positive. Only cpuset calls housekeeping_update().
> Without CONFIG_CPUSETS, housekeeping_init() sets the pointer at boot,
> and nothing changes it after that. Without isolcpus, the domain flag is
> not set. housekeeping_cpumask() then returns cpu_possible_mask without
> reading the pointer.
>
> Versions:
> - The check comes from 03ff73510169 ("cpuset: Update HK_TYPE_DOMAIN
> cpumask from cpuset"), in v7.0. It is the same in tip sched/urgent.
> - 666a32836c8f ("sched/topology: Add a cpus_read_lock to
> rebuild_sched_domains()"), in v7.3, fixed another warning on the same
> read path. Before it, the stub held no lock at all. So the check also
> fails from v7.0 to v7.2. This is from reading.
>
> Other callers of rebuild_sched_domains() would warn the same way with
> CONFIG_CPUSETS=n. Examples are the x86 sched_itmt_enabled debugfs file
> and rebuild_sched_domains_energy(). This is from reading. I did not
> run them.
>
> Where to fix
> ============
>
> The patch adds one case to housekeeping_dereference_check(). It accepts
> all readers when CONFIG_CPUSETS is off. This covers every caller of the
> stub. With CONFIG_CPUSETS=y, the new case is compiled out.
>
> Another option is an RCU read-side section around the two reads in
> partition_sched_domains_locked(). It would fix this path only.
>
> Testing
> =======
>
> I ran tip sched/urgent (006991aa8997) in a VM with 5 CPUs. The kernel
> had CONFIG_PROVE_RCU=y and CONFIG_CPUSETS=n. Without the patch:
> - isolcpus=domain,1,2: one report from a read alone. In another boot,
> one report from a write alone.
> - isolcpus=1,2: one report from a read.
> - No isolcpus: no report from reads and writes.
>
> With the patch, there is no report:
> - isolcpus=domain,1,2: a read alone. In another boot, a write alone.
> In a third boot, reads and writes of sched_rt_runtime_us and
> sched_rt_period_us.
> - isolcpus=1,2: reads and writes.
> - No isolcpus: reads and writes.
>
> debug_locks was still 1 at the end of each boot. So lockdep was still
> checking.
>
> With CONFIG_CPUSETS=y, the patch also builds. A boot with
> isolcpus=domain,1,2 and the same reads and writes gives no report. I
> did not run other tests.
>
> An open question, from reading only. Since a84097e625f2
> ("cgroup/cpuset: Call housekeeping_update() without holding
> cpus_read_lock"), cpuset calls housekeeping_update() with only
> cpuset_top_mutex held. The check accepts the hotplug write lock or
> cpuset_mutex. Do these two locks still keep the mask from changing
> under a reader? I did not look at a reader that holds only
> cpuset_mutex.
>
> The analysis and the patch were written by an LLM coding assistant
> under my direction and review.
>
> Thanks,
>
> Mathieu
>
> [1] https://lore.kernel.org/all/20261107014451.1487825-1-mathieu.desnoyers@efficios.com/
>
> -- >8 --
> From: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
> Subject: sched/isolation: Fix suspicious RCU usage without CONFIG_CPUSETS
>
> Since commit 03ff73510169 ("cpuset: Update HK_TYPE_DOMAIN cpumask from
> cpuset"), cpuset can change the HK_TYPE_DOMAIN mask at run time. Readers
> use RCU. housekeeping_dereference_check() also accepts readers that hold
> a lock which keeps cpuset partitions from changing: the CPU hotplug
> write lock or cpuset_mutex. During boot, before cpuset is writable, it
> accepts all readers.
>
> Without CONFIG_CPUSETS, nothing changes the mask after boot. But the
> check still asks for one of these locks. In that configuration,
> rebuild_sched_domains() holds only cpus_read_lock(). It calls
> partition_sched_domains(1, NULL, NULL). That reads
> housekeeping_cpumask(HK_TYPE_DOMAIN). sched_rt_handler() calls
> rebuild_sched_domains() on each read or write of sched_rt_period_us and
> sched_rt_runtime_us. So with isolcpus=domain, a read of
> /proc/sys/kernel/sched_rt_runtime_us gives:
>
> WARNING: suspicious RCU usage
> kernel/sched/isolation.c:62 suspicious rcu_dereference_check() usage!
> [...]
> locks held by cat/91: 3, last CPU#3:
> [...]
> #1: (cpu_hotplug_lock){++++}-{0:0}, at: sched_rt_handler+0xb5/0xf0
> #2: (sched_domains_mutex){+.+.}-{4:4}, at: partition_sched_domains+0x37/0x990
> [...]
> Call Trace:
> lockdep_rcu_suspicious.cold+0x4e/0x9f
> housekeeping_cpumask+0xaa/0xb0
> partition_sched_domains+0x8e6/0x990
> sched_rt_handler+0xc3/0xf0
> proc_sys_call_handler+0x1cf/0x250
>
> The report is a false positive. It needs CONFIG_PROVE_RCU and isolcpus=
> with the domain flag, which is also the default when no flag is given.
> Test runs that check the kernel log for lockdep reports flag it.
>
> Fix this by accepting all readers when CONFIG_CPUSETS is off.
>
> This change was developed with assistance from an LLM.
>
> Fixes: 03ff73510169 ("cpuset: Update HK_TYPE_DOMAIN cpumask from cpuset")
> Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
> ---
> kernel/sched/isolation.c | 4 ++++
> 1 file changed, 4 insertions(+)
>
> diff --git a/kernel/sched/isolation.c b/kernel/sched/isolation.c
> index 156025ef81b7..dac9a1735198 100644
> --- a/kernel/sched/isolation.c
> +++ b/kernel/sched/isolation.c
> @@ -43,6 +43,10 @@ static bool housekeeping_dereference_check(enum hk_type type)
> if (system_state <= SYSTEM_SCHEDULING)
> return true;
>
> + /* No cpuset, so the mask never changes after boot */
> + if (!IS_ENABLED(CONFIG_CPUSETS))
> + return true;
> +
> /* CPU hotplug write locked, so cpuset partition can't be overwritten */
> if (IS_ENABLED(CONFIG_HOTPLUG_CPU) && lockdep_is_cpus_write_held())
> return true;
Reviewed-by: Waiman Long <longman@redhat.com>
prev parent reply other threads:[~2026-11-08 18:41 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-08 14:59 Mathieu Desnoyers
2026-11-08 18:41 ` Waiman Long [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=fc1d48ef-535f-42e2-bf1f-96461a2165a2@redhat.com \
--to=longman@redhat.com \
--cc=bigeasy@linutronix.de \
--cc=bsegall@google.com \
--cc=cgroups@vger.kernel.org \
--cc=dietmar.eggemann@arm.com \
--cc=frederic@kernel.org \
--cc=juri.lelli@redhat.com \
--cc=kprateek.nayak@amd.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mathieu.desnoyers@efficios.com \
--cc=mgorman@suse.de \
--cc=mingo@redhat.com \
--cc=peterz@infradead.org \
--cc=ridong.chen@linux.dev \
--cc=rostedt@goodmis.org \
--cc=vincent.guittot@linaro.org \
--cc=vschneid@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®