Skip to content

chcpu: bound the cpu list walk at the highest possible CPU - #634

Open
mmclinton wants to merge 2 commits into
uutils:mainfrom
mmclinton:chcpu-bound-cpu-list
Open

chcpu: bound the cpu list walk at the highest possible CPU#634
mmclinton wants to merge 2 commits into
uutils:mainfrom
mmclinton:chcpu-bound-cpu-list

Conversation

@mmclinton

Copy link
Copy Markdown
Contributor

Closes #620.

chcpu walked every index in a cpu-list range, one faccessat and one stderr line each, so a range as wide as the integer type took hours.

The walk now stops at the highest index in /sys/devices/system/cpu/possible, which is the bound I suggested on the issue. cpu_possible_mask is fixed during boot discovery, so nothing above it can be brought online for the life of the boot, not even by hot-add:

So the bound cannot refuse an operation that would have worked. With it in place, 0-4294967295 and 0-18446744073709551615 both return in about 2 ms.

--enable, --disable, --configure and --deconfigure all walk the same cpu-list through one entry point, so all four are bounded on the same terms.

Only the walk is bounded, not the parse, so which arguments are accepted is unchanged. Indices at or below the bound are walked exactly as before, which for --disable as root means the CPUs that do exist are still offlined; they were in range and were asked for. The remainder above it is reported one range at a time rather than per index: on this box, where possible is 0-23, --enable 24-30 becomes a single line instead of seven. A lone out-of-range index keeps its existing wording, so chcpu -e 24 is untouched. Exit status and stdout are unchanged for every argument: the collapsed remainder still counts as a failure, and an index above the bound never produced stdout to begin with.

Where possible is wider than present, the absent CPUs below the bound are still reported one line each: with possible=0-63 and present=0-23, chcpu -e 20-70 emits 40 per-index lines and then one collapsed range. Bounding at present instead would avoid that, but present changes on hot-add, so it can refuse an operation that would have worked.

Where possible cannot be read or parsed the walk stays unbounded, as it was before, so on such a host the symptom in the issue comes back. The bound is best-effort, not a guarantee.

mmc added 2 commits August 24, 2026 20:43
`enabled_cpu_list` inlined the open, the read and the error path for one
attribute name. A second cpu-list attribute is about to be read the same way,
and duplicating eleven lines to change one string is the wrong shape.

The helper takes `impl AsRef<Path>`, like every other accessor on `SysFSCpu`.
`enabled_cpu_list` is its only caller for now, so behavior is unchanged.
A cpu-list range was bounded only by the integer type, so `CpuList::run`
stepped through every index it named, one faccessat each, and printed one
stderr line per index that did not exist. Measured at roughly 241,000 indices
per second, which extrapolates to about five hours for `--enable 0-4294967295`;
`--disable` took the same path and, as root, offlined the CPUs that do exist
before grinding through the ones that do not.

The walk now stops at the highest index in /sys/devices/system/cpu/possible.
That mask is fixed during boot discovery, so no index above it can be brought
online for the life of the boot, not even by hot-add, and `possible_cpus=`
already pre-allocates slots for CPUs that are hot-added later:

- https://docs.kernel.org/core-api/cpu_hotplug.html
- https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu

Bounding there therefore cannot refuse an operation that could have succeeded.
Only the walk is bounded, not the parse, so which argv are accepted is
unchanged. Indices above the bound are reported one range at a time instead of
one index at a time; a single index keeps its existing wording, so the common
`chcpu -e 24` diagnostic is untouched. Exit status and stdout are unchanged for
every argv: the collapsed remainder still counts as a failure, and an index
above the bound never produced stdout to begin with.

The bound is taken inside `walk_cpu_list` rather than passed to `run`, so no
operation can be added that omits it: an omitted bound is the multi-hour walk
the bound exists to prevent, with no compile error to catch it.

Where the attribute cannot be read the walk stays unbounded, as before.
Refusing the operation instead would let one missing optional attribute stop a
CPU that does exist from being enabled.

Three tests cover the bound; all walk only indices that are absent or already
online, so they need no privileges and change no CPU state, and each skips
itself where the bound cannot be read rather than walking unbounded. The two
absent indices in the existing multi-element test are no longer adjacent,
because a cpu-list coalesces touching ranges and each range is now reported
once.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chcpu: a large cpu-list range runs for hours

1 participant