Re: [BUG] sched/fair: divide error in __calc_prop_weight() from the enqueue path (flat-hierarchy series)
From: Jake Steinman
Date: Wed Aug 19 2026 - 14:49:27 EST
The diagnostic WARN fired. tg_cpus() does return 0 in practice, so the
cpuset route is real and not just theoretically reachable.
21.4 h into the boot, on the kernel carrying the three guards:
sched: tg_cpus() == 0, empty cpuset
WARNING: kernel/sched/fair.c:4904 at calc_concur_shares+0xf8/0x130, CPU#14: swapper/14/0
CPU: 14 UID: 0 PID: 0 Comm: swapper/14
Tainted: G U C OE 7.2.0-1-cachyos-rc #1 PREEMPT(full)
Hardware name: Dell Inc. XPS 16 DA16260/0RMV2Y, BIOS 1.5.1 04/01/2026
RIP: 0010:calc_concur_shares+0xfe/0x130
RAX: ffffffff88eec4b0 RBX: 0000000000000000 RCX: 0000000000000000
RDX: ffff8aedcc2379a0 RSI: 0000000000000030 RDI: ffffffff88eec4b0
RBP: fffff3f0ff990cc0 R08: 0000000000000000 R09: 0000000000000000
R10: 000000000000b74f R11: 0000000000000001 R12: fffff3f0ff98c5c0
R13: 0000000000100000 R14: ffff8aed89796c00 R15: 0000000000000001
Call Trace:
<IRQ>
enqueue_task_fair+0x201/0x970
enqueue_task+0x8e/0x250
ttwu_do_activate+0x89/0x1d0
sched_ttwu_pending+0xce/0x1f0
__flush_smp_call_function_queue+0x2b8/0x3e0
__sysvec_call_function_single+0x22/0xb0
fred_sysvec_call_function_single+0x54/0x70
</IRQ>
<TASK>
fred_extint+0x4e/0x80
asm_fred_entrypoint_kernel+0x41/0x70
Two things worth noting.
**The context is not the one that crashed.** The original oops was
bash in fork(), wake_up_new_task() -> enqueue_task_fair(). This is a
remote wakeup: an idle CPU (swapper/14) draining its pending-wakeup
queue from a CALL_FUNCTION_SINGLE IPI, in IRQ context. Same
calc_concur_shares() -> tg_cpus() == 0, reached a different way. So the
zero is a property of the task_group's cpuset at that moment, not of
either call path.
**IRQ context may matter for your RCU-race question.** You wrote:
> Now, cpuset_num_cpus() uses RCU, so perhaps there is a race somewhere.
This fired inside a hardirq, which is at least consistent with that --
though I can't distinguish "raced with an update" from "legitimately
empty at that instant" from the WARN alone. Waiman, if there is a
specific place you'd want instrumented to tell those apart, I have a
machine that reproduces it in about a day of ordinary use and I'm happy
to run whatever you want.
For completeness on what was and wasn't happening:
- Fired 21.4 h into the boot (wall clock 2026-08-19 13:24), during
ordinary desktop use, no reproducer.
- One s2idle suspend/resume in that boot, which ended 4h20m earlier.
- No CPU hotplug events logged in the boot at all.
- No isolcpus, no domain isolation, no partition roots -- every
cpuset.cpus.partition on the system reads "member", and no cgroup
showed an empty cpuset.cpus.effective when I sampled afterwards.
- intel_lpmd is running and does rewrite AllowedCPUs on the top-level
systemd slices, but logged nothing near the event, so I still can't
pin it as the trigger. I mention it only because I raised it earlier
and want to be clear it remains unproven.
The guard did its job: WARN_ONCE, floor to 1, machine still up 22 h
later with no divide error and no panic. That is 21 h longer than the
unguarded kernel managed on the day it crashed, though obviously one
data point.
Nothing here changes the patch I sent -- flooring tg_cpus() is still
right on symmetry grounds. It does mean the floor is load-bearing
rather than defensive, and that whatever lets a cpuset read as empty
here is a real thing worth understanding separately.
Thanks,
Jake