[PATCH AUTOSEL 6.18] sched_ext: Mark waker CPU busy when selected in WAKE_SYNC case

From: Sasha Levin

Date: Mon Aug 31 2026 - 13:17:31 EST


From: Kuba Piecuch <jpiecuch@xxxxxxxxxx>

[ Upstream commit 9591fcc95dddfd24298724f8eb5239e907980779 ]

SCX's built-in idle CPU tracking is imperfect and can be out-of-sync
with the actual idle state of CPUs, especially immediately after
enabling SCX due to scx_idle_enable() marking all online CPUs idle.

scx_select_cpu_dfl() skips marking the selected CPU as busy if
the selected CPU is the waker CPU in the SCX_WAKE_SYNC case.
If the waker CPU was marked idle by SCX, it will still be marked idle
after CPU selection and potentially even after switching to the wakee.

In the allowed_cpus selftest, this can manifest as the test failing with
the following message in dmesg:

allowed_cpus.bpf.c:21: CPU 0 should be marked as busy

This patch explicitly marks the waker CPU as busy. With this patch,
the test failure no longer reproduces. There are still some pretty
unlikely races that could make the test fail (e.g. pick_task_idle()
marking the selected CPU idle between selection and validation), but
these can't be fixed easily.

Signed-off-by: Kuba Piecuch <jpiecuch@xxxxxxxxxx>
Signed-off-by: Tejun Heo <tj@xxxxxxxxxx>
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `sched_ext: Mark waker CPU busy when
selected in WAKE_SYNC case`

**Local tree:** `v6.18.44` (`linux-6.18.y`, VERSION=6, PATCHLEVEL=18,
SUBLEVEL=44)

**Commit analyzed:** `b9f6521da1907` (upstream: `9591fcc95dddf`)

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: Subject Line
**Record:** `[sched_ext]` `[Mark]` — Mark waker CPU busy when selected
in WAKE_SYNC case. Action is a correctness/state fix (not a feature
add).

### Step 1.2: Tags
**Record:**
- **Signed-off-by:** Kuba Piecuch `<jpiecuch@xxxxxxxxxx>` (author)
- **Signed-off-by:** Tejun Heo `<tj@xxxxxxxxxx>` (sched_ext maintainer)
- No Fixes:, Reported-by:, Tested-by:, Reviewed-by:, Link:, or Cc:
stable tags
- `[Upstream commit 9591fcc95dddf]` marker present (pipeline artifact —
ignored per instructions)

### Step 1.3: Body Analysis
**Record:**
- **Bug:** SCX built-in idle CPU tracking can be out of sync with real
CPU idle state, especially right after `scx_idle_enable()` marks all
online CPUs idle.
- **Mechanism:** In `scx_select_cpu_dfl()`, the `SCX_WAKE_SYNC` path
selects the waker CPU but skips `scx_idle_test_and_clear_cpu()`,
unlike every other selection path in the same function.
- **Symptom:** Selected waker CPU remains marked idle after CPU
selection (and potentially after wakee switch).
- **Observable failure:** `allowed_cpus` selftest fails with
`allowed_cpus.bpf.c:21: CPU 0 should be marked as busy` in dmesg.
- **Root cause (author):** Missing idle-bit clear on the WAKE_SYNC
waker-CPU fast path.

### Step 1.4: Hidden Bug Fix?
**Record:** Yes — despite not using "fix" in the subject, this is a
logic/correctness bug in idle tracking state management, not cleanup or
optimization.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: Inventory
**Record:**
- **File:** `kernel/sched/ext_idle.c` (+3, -1 lines)
- **Function:** `scx_select_cpu_dfl()`
- **Scope:** Single-file, surgical fix in one code path

### Step 2.2: Code Flow Change
**Record:**
- **Before:** In `SCX_WAKE_SYNC` path, when waker's local DSQ is empty
and system underutilized, if waker CPU is in `allowed` mask → `goto
out_unlock` without updating idle tracking.
- **After:** Same path, but calls `scx_idle_test_and_clear_cpu(cpu)`
before `goto out_unlock`, matching all other CPU selection exits in
this function.
- **Path affected:** WAKE_SYNC synchronous wakeup CPU selection (normal
wakeup path, not error path).

### Step 2.3: Bug Mechanism
**Record:** **Logic / correctness fix** — inconsistent idle-mask
bookkeeping. Other selection branches call
`scx_idle_test_and_clear_cpu()` when claiming a CPU; this branch was the
sole exception, leaving the waker CPU incorrectly marked idle in SCX's
built-in idle cpumask.

### Step 2.4: Fix Quality
**Record:**
- Obviously correct: mirrors existing pattern used at lines 528, 566,
614, 937 in the same file.
- Minimal change, no API changes, no new behavior beyond fixing state.
- **Regression risk:** Very low — `scx_idle_test_and_clear_cpu()` is
idempotent-safe for a CPU about to run a task; it's already called on
every other selection path.

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: Blame
**Record:**
- WAKE_SYNC block introduced in `48849271e66114` (Andrea Righi, Feb
2025, "sched_ext: idle: Per-node idle cpumasks").
- The `goto out_unlock` without idle clear at lines 551–552 introduced
in `23c63a965275ce` (Apr 2025, refactoring to pass explicit allowed
cpumask — "pure refactoring with no functional changes", but the
WAKE_SYNC path never had the clear call).
- Buggy code **is an ancestor of HEAD** in this tree.

### Step 3.2: Fixes: Tag
**Record:** Not applicable — no Fixes: tag present.

### Step 3.3: Related File History
**Record:** Recent stable-relevant sched_ext idle fixes already in
`linux-6.18.y`:
- `a8f4a82e5629c` — idle: Recheck prev_cpu after narrowing allowed mask
- `b49bf41b41148` — Fix inconsistent NUMA node lookup in
scx_select_cpu_dfl()
- `72c43eb2e334f` — Fix is_bpf_migration_disabled() false negative

This fix is the same category: small sched_ext idle-selection
correctness fix.

### Step 3.4: Author Context
**Record:** Kuba Piecuch (Google). No prior commits in `kernel/sched/`
in this tree; patch went through Tejun Heo's sched_ext tree
(`for-7.2-fixes`).

### Step 3.5: Dependencies
**Record:** Standalone — no series dependencies, no prerequisite
commits. Applies cleanly to current HEAD (`git apply --check` passes).

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: Original Discussion
**Record:**
- `b4 dig -c b9f6521da1907 -a` found v1 submission:
https://patch.msgid.link/20260722143307.2772632-1-jpiecuch@xxxxxxxxxx
(2026-07-22, sched_ext/for-7.2-fixes)
- `b4 dig -c` without `-a` could not match by patch-id alone
- Lore/msgid.link fetch blocked by bot protection — could not read
thread content

### Step 4.2: Reviewers
**Record:** `b4 dig -w` did not return recipient details (same patch-id
match failure). Commit has Tejun Heo SOB (maintainer acceptance).

### Step 4.3: Bug Report
**Record:** Failure documented in commit message via `allowed_cpus`
selftest. No syzbot, no user bugzilla report.

### Step 4.4: Related Patches
**Record:** Standalone 1-patch fix for for-7.2-fixes. No multi-patch
series dependency.

### Step 4.5: Stable List History
**Record:** Could not search lore stable list (bot protection). However,
similar sched_ext idle fixes are already present in this 6.18.y tree
(see Phase 3.3).

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: Key Functions
**Record:** `scx_select_cpu_dfl()`, `scx_idle_test_and_clear_cpu()`,
`scx_bpf_select_cpu_and()` (wrapper calling `scx_select_cpu_dfl`)

### Step 5.2: Callers
**Record:**
- `scx_select_cpu_dfl()` called from `scx_bpf_select_cpu_and()` (BPF
kfunc, line 942) and `kernel/sched/ext.c:2578` (default select_cpu
when BPF scheduler doesn't implement `ops.select_cpu`)
- `SCX_WAKE_SYNC` originates from normal scheduler wakeups (`WF_SYNC` in
`kernel/sched/fair.c`, `kernel/sched/core.c`)
- Reachable from syscall-driven task wakeups when sched_ext is active

### Step 5.3: Callees
**Record:** `scx_idle_test_and_clear_cpu()` clears CPU (and SMT cluster)
from per-node idle cpumasks via `cpumask_test_and_clear_cpu()`.

### Step 5.4: Call Chain / Reachability
**Record:** `wake_up_*` → CFS/SCX wakeup → `scx_select_cpu_dfl()` with
`SCX_WAKE_SYNC` → BPF kfunc `scx_bpf_select_cpu_and()` → BPF scheduler
validation. **Reachable from userspace** via normal process wakeup when
`CONFIG_SCHED_CLASS_EXT` is enabled and a BPF scheduler is loaded.

### Step 5.5: Similar Patterns
**Record:** Every other CPU-claim path in `scx_select_cpu_dfl()` calls
`scx_idle_test_and_clear_cpu()` before returning the selected CPU. The
WAKE_SYNC waker path was the only exception — systematic omission, not
an isolated quirk.

---

## PHASE 6: CROSS-REFERENCING AGAINST LOCAL TREE

### Step 6.1: Buggy Code Exists?
**Record:** **Yes.** Current HEAD at `kernel/sched/ext_idle.c:551-552`:

```551:552:kernel/sched/ext_idle.c
if (cpumask_test_cpu(cpu, allowed))
goto out_unlock;
```

Missing `scx_idle_test_and_clear_cpu(cpu)` call. Fix commit
`b9f6521da1907` is **NOT** an ancestor of HEAD.

### Step 6.2: Backport Complications
**Record:** **Clean apply** — patch targets `kernel/sched/ext_idle.c`
(user-provided diff showed `kernel/sched/ext/idle.c` from mainline post-
refactor; this tree uses the pre-refactor filename). `git apply --check`
on the actual backport commit succeeds with no conflicts.

### Step 6.3: Related Fixes Already Present?
**Record:** No equivalent fix already in tree. Related idle-selection
fixes are present (NUMA lookup, prev_cpu recheck) but not this WAKE_SYNC
path.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: Subsystem Criticality
**Record:** **IMPORTANT** — `kernel/sched/ext_*` (sched_ext BPF
scheduler class). Not universal (requires `CONFIG_SCHED_CLASS_EXT`), but
scheduling correctness affects all tasks when enabled.

### Step 7.2: Subsystem Activity
**Record:** Highly active in 6.18.y — sched_ext merged for 6.18
(`fd95357fd8c67`), with ongoing idle-selection fixes backported to
stable.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: Who Is Affected
**Record:** Users with `CONFIG_SCHED_CLASS_EXT` enabled running BPF
schedulers that use built-in idle CPU selection
(`SCX_OPS_KEEP_BUILTIN_IDLE` or no `ops.update_idle`). Includes
selftests and production schedulers (scx_simple, custom Google/Meta
schedulers, etc.).

### Step 8.2: Trigger Conditions
**Record:**
- Synchronous wakeup (`SCX_WAKE_SYNC` / `WF_SYNC`)
- Waker's local DSQ empty, system underutilized (idle CPUs exist)
- Waker CPU is in the allowed cpumask
- Particularly visible right after SCX enable when all CPUs are marked
idle
- **Common enough** in normal wakeup patterns; not an obscure error path

### Step 8.3: Failure Mode Severity
**Record:**
- **Incorrect idle tracking** → BPF schedulers see CPU as idle when it's
selected for a task
- **Selftest:** `scx_bpf_error()` → BPF scheduler disabled
(`SCX_EXIT_ERROR_BPF`)
- **Production:** Suboptimal/wrong CPU placement, potential double-
selection of "idle" CPUs, broken scheduler invariants for BPF code
relying on idle mask accuracy
- **Severity: MEDIUM-HIGH** for sched_ext users (functional correctness
/ scheduler failure), not kernel oops/panic/data corruption

### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** Fixes real API contract violation; restores consistency
with all other selection paths; fixes reproducible selftest failure;
aligns with other sched_ext idle fixes already in 6.18.y
- **Risk:** Very low — 3-line addition of existing helper call
- **Ratio:** Strong benefit, minimal risk for affected users

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: Evidence Summary

**FOR backport:**
- Real bug: WAKE_SYNC path omits idle-bit clear that all sibling paths
perform
- Bug present in v6.18.44 tree; fix not yet applied
- Patch applies cleanly
- Small, surgical, maintainer-signed (Tejun Heo)
- Breaks `scx_bpf_select_cpu_and()` API contract (returned CPU should
not still be idle)
- Reproducible via `allowed_cpus` selftest
- Similar sched_ext idle fixes already backported to this tree
- sched_ext is a supported subsystem in 6.18.y

**AGAINST backport:**
- No production user crash report or CVE
- Only affects `CONFIG_SCHED_CLASS_EXT` users (optional config)
- Author acknowledges remaining unlikely races (fix is not complete for
all edge cases)
- Not a kernel panic/oops — BPF scheduler error/disable at worst
- Lore discussion content unverified (bot protection)

**UNRESOLVED:**
- Full mailing list review thread content (could not fetch)
- Whether any reviewer explicitly nominated for stable

### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — mirrors existing pattern;
selftest no longer reproduces
2. Fixes a real bug affecting users? **PASS** — idle tracking
inconsistency in sched_ext
3. Important issue? **PASS** — scheduling correctness bug; can disable
BPF scheduler via `scx_bpf_error()`
4. Small and contained? **PASS** — 3 lines, 1 file, 1 function
5. No new features or APIs? **PASS** — state bookkeeping fix only
6. Can apply to local tree? **PASS** — clean apply verified

### Step 9.3: Exception Categories
**Record:** Not applicable (not device ID, quirk, DT, build, or docs
fix).

### Step 9.4: Decision Rationale
This is a clear correctness bug in sched_ext's built-in idle CPU
tracking that exists in the 6.18.y tree. The fix is trivial, follows an
established pattern already used throughout the same function, applies
cleanly, and is consistent with other sched_ext idle-selection fixes
already backported to this stable series. While the primary documented
failure is a selftest, the underlying bug violates the
`scx_bpf_select_cpu_and()` contract and can affect real BPF scheduler
behavior on synchronous wakeups — a path reachable from normal process
activity.

---

## Verification

- **[Phase 1]** `git show b9f6521da1907 --format=fuller --no-patch`:
parsed commit message and tags
- **[Phase 2]** `git show b9f6521da1907`: confirmed 3-line fix in
`scx_select_cpu_dfl()` WAKE_SYNC path
- **[Phase 2]** Read `kernel/sched/ext_idle.c:519-570`: confirmed
missing `scx_idle_test_and_clear_cpu(cpu)` on buggy path vs. present
on other paths
- **[Phase 3]** `git blame -L 546,555 kernel/sched/ext_idle.c`:
WAKE_SYNC block from `48849271e66114`, goto without clear from
`23c63a965275ce`
- **[Phase 3]** `git merge-base --is-ancestor 23c63a965275ce HEAD`:
buggy code is in tree
- **[Phase 3]** `git merge-base --is-ancestor b9f6521da1907 HEAD`: fix
NOT in tree
- **[Phase 3]** `git log --oneline -15 stable/linux-6.18.y --
kernel/sched/ext_idle.c`: related idle fixes already backported
- **[Phase 4]** `b4 dig -c b9f6521da1907 -a`: found v1 at
patch.msgid.link/20260722143307.2772632-1-jpiecuch@xxxxxxxxxx
- **[Phase 4]** WebFetch lore/msgid.link: **BLOCKED** by bot protection
— thread content unverified
- **[Phase 5]** `Grep SCX_WAKE_SYNC, scx_select_cpu_dfl`: traced call
paths through `scx_bpf_select_cpu_and()` and `ext.c:2578`
- **[Phase 5]** Read `allowed_cpus.bpf.c:17-27`: selftest validates CPU
must not be idle after selection
- **[Phase 5]** Read `ext.c:6420-6431`: `scx_bpf_error()` triggers
scheduler disable
- **[Phase 6]** `git describe HEAD`: v6.18.44 confirmed
- **[Phase 6]** `git show b9f6521da1907 -- kernel/sched/ext_idle.c | git
apply --check`: clean apply
- **[Phase 6]** `Grep kernel/sched/ext`: directory does not exist; code
is in `ext_idle.c` (filename differs from user's diff but same code)
- **[Phase 7]** `kernel/Kconfig.preempt:166-191`:
`CONFIG_SCHED_CLASS_EXT` present in tree
- **[Phase 8]** Read `reset_idle_masks()` at `ext_idle.c:775-795`:
confirms all online CPUs marked idle at SCX enable

**YES****Result:** Analysis of `sched_ext: Mark waker CPU busy when
selected in WAKE_SYNC case` (`b9f6521da1907`) for **linux-6.18.y
(v6.18.44)** is complete.

**Finding:** The bug is present in this tree — in
`scx_select_cpu_dfl()`’s `SCX_WAKE_SYNC` path, the waker CPU is selected
without calling `scx_idle_test_and_clear_cpu()`, unlike every other
selection path. That leaves SCX’s built-in idle tracking wrong and
breaks the `scx_bpf_select_cpu_and()` contract (reproducible via the
`allowed_cpus` selftest).

**Backport suitability:** The fix is 3 lines, applies cleanly to
`kernel/sched/ext_idle.c`, matches other sched_ext idle fixes already in
6.18.y, and is signed off by Tejun Heo.

**Verdict: YES** — backport to this tree.

kernel/sched/ext_idle.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/kernel/sched/ext_idle.c b/kernel/sched/ext_idle.c
index d16a1de078656..42a65a9535aef 100644
--- a/kernel/sched/ext_idle.c
+++ b/kernel/sched/ext_idle.c
@@ -548,8 +548,10 @@ s32 scx_select_cpu_dfl(struct task_struct *p, s32 prev_cpu, u64 wake_flags,
cpu_rq(cpu)->scx.local_dsq.nr == 0 &&
(!(flags & SCX_PICK_IDLE_IN_NODE) || (waker_node == node)) &&
!cpumask_empty(idle_cpumask(waker_node)->cpu)) {
- if (cpumask_test_cpu(cpu, allowed))
+ if (cpumask_test_cpu(cpu, allowed)) {
+ scx_idle_test_and_clear_cpu(cpu);
goto out_unlock;
+ }
}
}

--
2.53.0