* [PATCH v2 1/6] cgroup/cpuset: Respect child CPU ownership in type changes
2026-08-28 9:56 [PATCH v2 0/6] cgroup/cpuset: Fix partition type transitions Guopeng Zhang
@ 2026-08-28 9:56 ` Guopeng Zhang
2026-08-28 9:56 ` [PATCH v2 2/6] selftests/cgroup: Add tests for type-change isolation accounting Guopeng Zhang
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Guopeng Zhang @ 2026-08-28 9:56 UTC (permalink / raw)
To: cgroups, longman, ridong.chen
Cc: tj, hannes, mkoutny, shuah, linux-kernel, linux-kselftest,
Guopeng Zhang
From: Guopeng Zhang <zhangguopeng@kylinos.cn>
effective_xcpus includes CPUs granted to valid child partitions. Changing
a parent between root and isolated must not apply its new isolation state
or housekeeping constraints to those CPUs.
For example, on a cgroup v2 system with CPUs 0-3 online:
cd /sys/fs/cgroup
echo +cpuset > cgroup.subtree_control
mkdir type-repro
echo 1-3 > type-repro/cpuset.cpus
echo isolated > type-repro/cpuset.cpus.partition
echo +cpuset > type-repro/cgroup.subtree_control
mkdir type-repro/child
echo 2-3 > type-repro/child/cpuset.cpus
echo isolated > type-repro/child/cpuset.cpus.partition
echo root > type-repro/cpuset.cpus.partition
cat cpuset.cpus.isolated
The isolated mask should still contain CPUs 2-3 after the parent becomes a
root partition. Without this change, those CPUs are removed even though
the child remains isolated.
Compute the CPUs owned directly by a partition by subtracting the
effective_xcpus of valid children. Use this mask for type-change isolation
accounting and housekeeping checks. Apply the same ownership rule when
validating a trial CPU mask; otherwise a later CPU-mask update can mark a
parent invalid because of a boot-isolated CPU owned by a valid child.
Limiting the parent check to directly owned CPUs also allows a root child
to hold the last housekeeping CPU while its parent becomes isolated. If
the child then becomes a member, returning that CPU to the isolated parent
would violate the housekeeping constraint. The transition back to member
must remain allowed, so invalidate the outermost isolated ancestor and
return its CPUs to a root partition.
Sashiko pointed out the trial-validation and CPU-return gaps while
reviewing the original series.
Link: https://sashiko.dev/#/patchset/20260820124202.517160-1-guopeng.zhang%40linux.dev?part=6
Fixes: 4a74e418881f ("cgroup/cpuset: Check partition conflict with housekeeping setup")
Fixes: 11e5f407b64a ("cgroup/cpuset: Keep track of CPUs in isolated partitions")
Fixes: 103b08709e8a ("cgroup/cpuset: Fail if isolated and nohz_full don't leave any housekeeping")
Fixes: b1034a690129 ("cgroup/cpuset: Ensure domain isolated CPUs stay in root or isolated partition")
Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
---
kernel/cgroup/cpuset.c | 92 ++++++++++++++++++++++++++++++++++++++----
1 file changed, 84 insertions(+), 8 deletions(-)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 8f24171b6055..32a37d624c6b 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -2155,6 +2155,31 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
rcu_read_unlock();
}
+/*
+ * Compute CPUs owned directly by a partition.
+ *
+ * effective_xcpus includes CPUs granted to valid child partitions. Exclude
+ * those CPUs when checking or changing this partition's type.
+ */
+static void compute_partition_owned_cpumask(struct cpuset *cs,
+ const struct cpumask *partition_cpus,
+ struct cpumask *owned_cpus)
+{
+ struct cgroup_subsys_state *css;
+ struct cpuset *child;
+
+ lockdep_assert_held(&cpuset_mutex);
+ cpumask_copy(owned_cpus, partition_cpus);
+
+ rcu_read_lock();
+ cpuset_for_each_child(child, css, cs) {
+ if (is_partition_valid(child))
+ cpumask_andnot(owned_cpus, owned_cpus,
+ child->effective_xcpus);
+ }
+ rcu_read_unlock();
+}
+
/*
* update_cpumasks_hier - Update effective cpumasks and tasks in the subtree
* @cs: the cpuset to consider
@@ -2401,7 +2426,9 @@ static int parse_cpuset_cpulist(const char *buf, struct cpumask *out_mask)
*
* Return: PRS error code (0 if valid, non-zero error code if invalid)
*/
-static enum prs_errcode validate_partition(struct cpuset *cs, struct cpuset *trialcs)
+static enum prs_errcode validate_partition(struct cpuset *cs,
+ struct cpuset *trialcs,
+ struct cpumask *owned_cpus)
{
struct cpuset *parent = parent_cs(cs);
@@ -2411,8 +2438,10 @@ static enum prs_errcode validate_partition(struct cpuset *cs, struct cpuset *tri
if (cpumask_empty(trialcs->effective_xcpus))
return PERR_INVCPUS;
+ compute_partition_owned_cpumask(cs, trialcs->effective_xcpus,
+ owned_cpus);
if (prstate_housekeeping_conflict(trialcs->partition_root_state,
- trialcs->effective_xcpus))
+ owned_cpus))
return PERR_HKEEPING;
if (tasks_nocpu_error(parent, cs, trialcs->effective_xcpus))
@@ -2438,7 +2467,7 @@ static void partition_cpus_change(struct cpuset *cs, struct cpuset *trialcs,
if (cs_is_member(cs))
return;
- prs_err = validate_partition(cs, trialcs);
+ prs_err = validate_partition(cs, trialcs, tmp->new_cpus);
if (prs_err) {
WRITE_ONCE(cs->prs_err, prs_err);
trialcs->prs_err = prs_err;
@@ -2917,6 +2946,36 @@ int cpuset_update_flag(cpuset_flagbits_t bit, struct cpuset *cs,
return err;
}
+/*
+ * Invalidate the highest isolated partition that contains @cs.
+ *
+ * A root partition returning CPUs to an isolated parent can consume the last
+ * housekeeping CPU. Invalidating the whole chain returns the CPUs to a root
+ * partition instead.
+ */
+static struct cpuset *invalidate_isolated_ancestor(struct cpuset *cs,
+ struct tmpmasks *tmp)
+{
+ struct cpuset *ancestor = parent_cs(cs);
+ int err;
+
+ lockdep_assert_held(&cpuset_mutex);
+ while (!is_remote_partition(ancestor) &&
+ (parent_cs(ancestor)->partition_root_state == PRS_ISOLATED))
+ ancestor = parent_cs(ancestor);
+
+ WRITE_ONCE(ancestor->prs_err, PERR_HKEEPING);
+ if (is_remote_partition(ancestor)) {
+ remote_partition_disable(ancestor, tmp);
+ } else {
+ err = update_parent_effective_cpumask(ancestor,
+ partcmd_invalidate, NULL, tmp);
+ WARN_ON_ONCE(err);
+ }
+
+ return ancestor;
+}
+
/**
* update_prstate - update partition_root_state
* @cs: the cpuset to update
@@ -2929,6 +2988,7 @@ static int update_prstate(struct cpuset *cs, int new_prs)
{
int err = PERR_NONE, old_prs = cs->partition_root_state;
struct cpuset *parent = parent_cs(cs);
+ struct cpuset *invalidated = NULL;
struct tmpmasks tmpmask;
bool isolcpus_updated = false;
@@ -2985,11 +3045,14 @@ static int update_prstate(struct cpuset *cs, int new_prs)
} else if (old_prs && new_prs) {
/*
* A change in load balance state only, no change in cpumasks.
- * Need to update isolated_cpus.
+ * Need to update isolated_cpus for CPUs owned by this partition,
+ * excluding CPUs distributed to valid child partitions.
*/
+ compute_partition_owned_cpumask(cs, cs->effective_xcpus,
+ tmpmask.new_cpus);
if (((new_prs == PRS_ISOLATED) &&
- !isolated_cpus_can_update(cs->effective_xcpus, NULL)) ||
- prstate_housekeeping_conflict(new_prs, cs->effective_xcpus))
+ !isolated_cpus_can_update(tmpmask.new_cpus, NULL)) ||
+ prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus))
err = PERR_HKEEPING;
else
isolcpus_updated = true;
@@ -2998,6 +3061,13 @@ static int update_prstate(struct cpuset *cs, int new_prs)
* Switching back to member is always allowed even if it
* disables child partitions.
*/
+ if (old_prs == PRS_ROOT &&
+ parent->partition_root_state == PRS_ISOLATED &&
+ !isolated_cpus_can_update(cs->effective_xcpus, NULL))
+ invalidated = invalidate_isolated_ancestor(cs, &tmpmask);
+ if (invalidated)
+ goto out;
+
if (is_remote_partition(cs))
remote_partition_disable(cs, &tmpmask);
else
@@ -3025,11 +3095,17 @@ static int update_prstate(struct cpuset *cs, int new_prs)
if (!is_partition_valid(cs))
reset_partition_data(cs);
else if (isolcpus_updated)
- isolated_cpus_update(old_prs, new_prs, cs->effective_xcpus);
+ isolated_cpus_update(old_prs, new_prs, tmpmask.new_cpus);
spin_unlock_irq(&callback_lock);
/* Force update if switching back to member & update effective_xcpus */
- update_cpumasks_hier(cs, &tmpmask, !new_prs);
+ if (invalidated) {
+ update_cpumasks_hier(invalidated, &tmpmask, false);
+ update_partition_sd_lb(invalidated, PRS_ISOLATED);
+ notify_partition_change(invalidated, PRS_ISOLATED);
+ } else {
+ update_cpumasks_hier(cs, &tmpmask, !new_prs);
+ }
/* A newly created partition must have effective_xcpus set */
WARN_ON_ONCE(!old_prs && (new_prs > 0)
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 2/6] selftests/cgroup: Add tests for type-change isolation accounting
2026-08-28 9:56 [PATCH v2 0/6] cgroup/cpuset: Fix partition type transitions Guopeng Zhang
2026-08-28 9:56 ` [PATCH v2 1/6] cgroup/cpuset: Respect child CPU ownership in type changes Guopeng Zhang
@ 2026-08-28 9:56 ` Guopeng Zhang
2026-08-28 9:56 ` [PATCH v2 3/6] selftests/cgroup: Add tests for type changes with child-owned CPUs Guopeng Zhang
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Guopeng Zhang @ 2026-08-28 9:56 UTC (permalink / raw)
To: cgroups, longman, ridong.chen
Cc: tj, hannes, mkoutny, shuah, linux-kernel, linux-kselftest,
Guopeng Zhang
From: Guopeng Zhang <zhangguopeng@kylinos.cn>
Add two matrix cases for parent type changes with valid child partitions.
Cover root-to-isolated while the child remains root, and isolated-to-root
while the child remains isolated. Check that only CPUs owned directly by
the parent change isolation state.
Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
---
tools/testing/selftests/cgroup/test_cpuset_prs.sh | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
index 131d8b4551ef..9a1cce4807b4 100755
--- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
+++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
@@ -224,6 +224,8 @@ TEST_MATRIX=(
" C0-1 . . C2-3:P1 . . . C2 0 "
" C0-1 . . C2-3:P1 . . . C4-5 0 B1:4-5"
" C0-3:P1 C2-3:P1 . . . . . . 0 A1:0-1|A2:2-3|XA2:2-3"
+ " C0-3:P1 C2-3:P1 . . P2 . . . 0 A1:0-1|A2:2-3 A1:P2|A2:P1 0-1"
+ " C0-3:P2 C2-3:P2 . . P1 . . . 0 A1:0-1|A2:2-3 A1:P1|A2:P2 2-3"
" C0-3:P1 C2-3:P1 . . C1-3 . . . 0 A1:1|A2:2-3|XA2:2-3"
" C2-3:P1 C3:P1 . . C3 . . . 0 A1:|A2:3|XA2:3 A1:P1|A2:P1"
" C2-3:P1 C3:P1 . . C3 P0 . . 0 A1:3|A2:3 A1:P1|A2:P0"
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 3/6] selftests/cgroup: Add tests for type changes with child-owned CPUs
2026-08-28 9:56 [PATCH v2 0/6] cgroup/cpuset: Fix partition type transitions Guopeng Zhang
2026-08-28 9:56 ` [PATCH v2 1/6] cgroup/cpuset: Respect child CPU ownership in type changes Guopeng Zhang
2026-08-28 9:56 ` [PATCH v2 2/6] selftests/cgroup: Add tests for type-change isolation accounting Guopeng Zhang
@ 2026-08-28 9:56 ` Guopeng Zhang
2026-08-28 9:56 ` [PATCH v2 4/6] selftests/cgroup: Add tests for housekeeping CPU return to isolated parents Guopeng Zhang
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Guopeng Zhang @ 2026-08-28 9:56 UTC (permalink / raw)
To: cgroups, longman, ridong.chen
Cc: tj, hannes, mkoutny, shuah, linux-kernel, linux-kselftest,
Guopeng Zhang
From: Guopeng Zhang <zhangguopeng@kylinos.cn>
Create an isolated child that owns a boot-isolated CPU, then change its
parent type and CPU mask. Check that the child stays isolated and the
parent remains valid.
Cache the boot-isolated CPU data during setup so this test does not depend
on an earlier test calling get_boot_isolated_cpu(). Keep the per-test check
that the selected CPU is online.
Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
---
.../selftests/cgroup/test_cpuset_prs.sh | 69 +++++++++++++++++--
1 file changed, 65 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
index 9a1cce4807b4..ae27245e90bb 100755
--- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
+++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
@@ -107,6 +107,17 @@ then
echo "Pre-isolated CPUs: $BOOT_ISOLCPUS"
fi
+# Cache the CPUs isolated from scheduler domains at boot.
+BOOT_ISOLATED_FILE=/sys/devices/system/cpu/isolated
+BOOT_CPUS=
+BOOT_CPU=
+if [[ -r $BOOT_ISOLATED_FILE ]]
+then
+ BOOT_CPUS=$(cat $BOOT_ISOLATED_FILE)
+ [[ -n "$BOOT_CPUS" ]] &&
+ BOOT_CPU=$(echo "$BOOT_CPUS" | sed -e 's/[,-].*//')
+fi
+
cleanup()
{
online_cpus
@@ -1158,25 +1169,22 @@ test_isolated()
}
#
-# Select an online CPU isolated from scheduler domains at boot.
+# Check that a boot-isolated CPU selected during initialization is online.
# $1: test name used in the skip message
#
get_boot_isolated_cpu()
{
TEST_NAME=$1
- BOOT_ISOLATED_FILE=/sys/devices/system/cpu/isolated
[[ -r $BOOT_ISOLATED_FILE ]] || {
echo "$TEST_NAME test SKIPPED: boot isolation state unavailable"
return 1
}
- BOOT_CPUS=$(cat $BOOT_ISOLATED_FILE)
[[ -n "$BOOT_CPUS" ]] || {
echo "$TEST_NAME test SKIPPED: no boot-isolated CPU"
return 1
}
- BOOT_CPU=$(echo "$BOOT_CPUS" | sed -e 's/[,-].*//')
CPU_ONLINE=/sys/devices/system/cpu/cpu${BOOT_CPU}/online
[[ ! -e $CPU_ONLINE || $(cat $CPU_ONLINE) -eq 1 ]] || {
echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is offline"
@@ -1214,6 +1222,58 @@ test_boot_isolated()
echo "$TEST_NAME test PASSED."
}
+#
+# A parent's type and CPU-mask changes must check only CPUs owned directly by
+# the parent, not a boot-isolated CPU owned by a valid child partition.
+#
+test_child_owned_cpus()
+{
+ TEST_NAME="Child-owned CPU type change"
+ get_boot_isolated_cpu "$TEST_NAME" || return 0
+ echo "Running $TEST_NAME test ..."
+
+ cd $CGROUP2/test
+ echo member > cpuset.cpus.partition
+ echo +cpuset > cgroup.subtree_control
+ echo 2,$BOOT_CPU > cpuset.cpus
+ [[ $(cat cpuset.cpus.effective) = "2,$BOOT_CPU" ]] || {
+ echo "$TEST_NAME test SKIPPED: CPUs 2,$BOOT_CPU are unavailable"
+ echo "" > cpuset.cpus
+ cd $CGROUP2
+ return 0
+ }
+ test_partition isolated
+ mkdir A1
+ cd A1
+ echo $BOOT_CPU > cpuset.cpus
+ test_partition isolated
+ cd ..
+ test_effective_cpus 2
+ test_partition root
+ echo 2-3,$BOOT_CPU > cpuset.cpus
+ test_effective_cpus 2-3
+ [[ $(cat cpuset.cpus.partition) = root ]] || {
+ echo "Parent partition became invalid during CPU update"
+ exit 1
+ }
+ [[ $(cat A1/cpuset.cpus.partition) = isolated ]] || {
+ echo "Child partition changed during parent update"
+ exit 1
+ }
+ check_isolcpus "." || {
+ echo "Parent update corrupted child isolation"
+ exit 1
+ }
+ cd A1
+ test_partition member
+ cd ..
+ rmdir A1
+ test_partition member
+ echo "" > cpuset.cpus
+ cd $CGROUP2
+ echo "$TEST_NAME test PASSED."
+}
+
#
# Wait for inotify event for the given file and read it
# $1: cgroup file to wait for
@@ -1286,5 +1346,6 @@ run_state_test TEST_MATRIX
run_remote_state_test REMOTE_TEST_MATRIX
test_isolated
test_boot_isolated
+test_child_owned_cpus
test_inotify
echo "All tests PASSED."
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 4/6] selftests/cgroup: Add tests for housekeeping CPU return to isolated parents
2026-08-28 9:56 [PATCH v2 0/6] cgroup/cpuset: Fix partition type transitions Guopeng Zhang
` (2 preceding siblings ...)
2026-08-28 9:56 ` [PATCH v2 3/6] selftests/cgroup: Add tests for type changes with child-owned CPUs Guopeng Zhang
@ 2026-08-28 9:56 ` Guopeng Zhang
2026-08-28 9:56 ` [PATCH v2 5/6] cgroup/cpuset: Release CPUs when type-change validation fails Guopeng Zhang
2026-08-28 9:56 ` [PATCH v2 6/6] selftests/cgroup: Add CPU release tests for type-change validation failures Guopeng Zhang
5 siblings, 0 replies; 7+ messages in thread
From: Guopeng Zhang @ 2026-08-28 9:56 UTC (permalink / raw)
To: cgroups, longman, ridong.chen
Cc: tj, hannes, mkoutny, shuah, linux-kernel, linux-kselftest,
Guopeng Zhang
From: Guopeng Zhang <zhangguopeng@kylinos.cn>
Return the last housekeeping CPU from a root child to an isolated parent.
Check that the parent becomes invalid and releases its CPUs instead of
adding the returned CPU to the isolated mask.
Repeat the test with two isolated ancestors and check that the outermost
isolated partition is invalidated.
Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
---
.../selftests/cgroup/test_cpuset_prs.sh | 146 ++++++++++++++++++
1 file changed, 146 insertions(+)
diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
index ae27245e90bb..72fe5ce48eff 100755
--- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
+++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
@@ -1222,6 +1222,30 @@ test_boot_isolated()
echo "$TEST_NAME test PASSED."
}
+# Return success if CPU $2 is present in cpulist $1.
+cpulist_contains()
+{
+ local CPULIST_ARG=$1
+ local CPU_ARG=$2
+ local ITEM FIRST LAST
+ local OLD_IFS=$IFS
+
+ IFS=,
+ for ITEM in $CPULIST_ARG
+ do
+ FIRST=${ITEM%-*}
+ LAST=${ITEM#*-}
+ [[ $ITEM != *-* ]] && LAST=$FIRST
+ if [[ $CPU_ARG -ge $FIRST && $CPU_ARG -le $LAST ]]
+ then
+ IFS=$OLD_IFS
+ return 0
+ fi
+ done
+ IFS=$OLD_IFS
+ return 1
+}
+
#
# A parent's type and CPU-mask changes must check only CPUs owned directly by
# the parent, not a boot-isolated CPU owned by a valid child partition.
@@ -1274,6 +1298,127 @@ test_child_owned_cpus()
echo "$TEST_NAME test PASSED."
}
+#
+# Returning the last housekeeping CPU to an isolated parent must invalidate
+# the isolated partition instead of adding that CPU to the isolated mask.
+#
+test_housekeeping_cpu_return()
+{
+ TEST_NAME="Housekeeping CPU return"
+ NOHZ_FILE=/sys/devices/system/cpu/nohz_full
+ [[ -r $NOHZ_FILE ]] || {
+ echo "$TEST_NAME test SKIPPED: no nohz_full state"
+ return 0
+ }
+ NOHZ_CPUS=$(cat $NOHZ_FILE)
+ [[ -n "$NOHZ_CPUS" && "$NOHZ_CPUS" != "(null)" ]] || {
+ echo "$TEST_NAME test SKIPPED: no nohz_full CPUs"
+ return 0
+ }
+
+ HK_CPU=
+ HK_COUNT=0
+ TYPE_CPUS=()
+ for ((CPU=0; CPU < NR_CPUS; CPU++))
+ do
+ CPU_ONLINE=/sys/devices/system/cpu/cpu${CPU}/online
+ [[ ! -e $CPU_ONLINE || $(cat $CPU_ONLINE) -eq 1 ]] || continue
+ cpulist_contains "$BOOT_CPUS" $CPU && continue
+ if cpulist_contains "$NOHZ_CPUS" $CPU
+ then
+ [[ ${#TYPE_CPUS[@]} -lt 3 ]] && TYPE_CPUS+=("$CPU")
+ else
+ HK_CPU=$CPU
+ ((HK_COUNT++))
+ fi
+ done
+
+ [[ $HK_COUNT -eq 1 && ${#TYPE_CPUS[@]} -ge 2 ]] || {
+ echo "$TEST_NAME test SKIPPED: requires one full housekeeping CPU"
+ return 0
+ }
+ echo "Running $TEST_NAME test ..."
+
+ cd $CGROUP2/test
+ echo member > cpuset.cpus.partition
+ echo +cpuset > cgroup.subtree_control
+ echo $HK_CPU,${TYPE_CPUS[0]},${TYPE_CPUS[1]} > cpuset.cpus
+ test_partition root
+ mkdir A1
+ cd A1
+ echo $HK_CPU > cpuset.cpus
+ test_partition root
+ cd ..
+ test_partition isolated
+ cd A1
+ test_partition member
+ cd ..
+ grep -q '^isolated invalid (partition config conflicts with housekeeping setup)$' \
+ cpuset.cpus.partition || {
+ echo "Isolated parent remained valid after housekeeping CPU return"
+ exit 1
+ }
+ [[ $(cat $CGROUP2/cpuset.cpus.effective) = "$CPULIST" ]] || {
+ echo "Housekeeping CPU return did not release the partition CPUs"
+ exit 1
+ }
+ check_isolcpus "." || {
+ echo "Housekeeping CPU was added to the isolated mask"
+ exit 1
+ }
+ rmdir A1
+ echo member > cpuset.cpus.partition
+ echo "" > cpuset.cpus
+
+ if [[ ${#TYPE_CPUS[@]} -lt 3 ]]
+ then
+ echo "Nested $TEST_NAME test SKIPPED: requires three nohz_full CPUs"
+ cd $CGROUP2
+ echo "$TEST_NAME test PASSED."
+ return 0
+ fi
+
+ # Repeat the check with two isolated ancestors.
+ echo $HK_CPU,${TYPE_CPUS[0]},${TYPE_CPUS[1]},${TYPE_CPUS[2]} > cpuset.cpus
+ test_partition root
+ mkdir A1
+ cd A1
+ echo $HK_CPU,${TYPE_CPUS[1]},${TYPE_CPUS[2]} > cpuset.cpus
+ test_partition root
+ echo +cpuset > cgroup.subtree_control
+ mkdir A2
+ cd A2
+ echo $HK_CPU > cpuset.cpus
+ test_partition root
+ cd ..
+ test_partition isolated
+ cd ..
+ test_partition isolated
+ cd A1/A2
+ test_partition member
+ cd ../..
+ grep -q '^isolated invalid (partition config conflicts with housekeeping setup)$' \
+ cpuset.cpus.partition || {
+ echo "Outermost isolated partition remained valid after housekeeping CPU return"
+ exit 1
+ }
+ [[ $(cat $CGROUP2/cpuset.cpus.effective) = "$CPULIST" ]] || {
+ echo "Nested housekeeping CPU return did not release the partition CPUs"
+ exit 1
+ }
+ check_isolcpus "." || {
+ echo "Nested housekeeping CPU return added the CPU to the isolated mask"
+ exit 1
+ }
+ rmdir A1/A2
+ echo member > A1/cpuset.cpus.partition
+ rmdir A1
+ echo member > cpuset.cpus.partition
+ echo "" > cpuset.cpus
+ cd $CGROUP2
+ echo "$TEST_NAME test PASSED."
+}
+
#
# Wait for inotify event for the given file and read it
# $1: cgroup file to wait for
@@ -1347,5 +1492,6 @@ run_remote_state_test REMOTE_TEST_MATRIX
test_isolated
test_boot_isolated
test_child_owned_cpus
+test_housekeeping_cpu_return
test_inotify
echo "All tests PASSED."
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 5/6] cgroup/cpuset: Release CPUs when type-change validation fails
2026-08-28 9:56 [PATCH v2 0/6] cgroup/cpuset: Fix partition type transitions Guopeng Zhang
` (3 preceding siblings ...)
2026-08-28 9:56 ` [PATCH v2 4/6] selftests/cgroup: Add tests for housekeeping CPU return to isolated parents Guopeng Zhang
@ 2026-08-28 9:56 ` Guopeng Zhang
2026-08-28 9:56 ` [PATCH v2 6/6] selftests/cgroup: Add CPU release tests for type-change validation failures Guopeng Zhang
5 siblings, 0 replies; 7+ messages in thread
From: Guopeng Zhang @ 2026-08-28 9:56 UTC (permalink / raw)
To: cgroups, longman, ridong.chen
Cc: tj, hannes, mkoutny, shuah, linux-kernel, linux-kselftest,
Guopeng Zhang
From: Guopeng Zhang <zhangguopeng@kylinos.cn>
When housekeeping validation fails during a root-to-isolated or
isolated-to-root change, update_prstate() records the requested partition
type as invalid but leaves its effective_xcpus allocated. Those CPUs are
not returned to the partition which owns the invalidated subtree.
This can be reproduced on a cgroup v2 system booted with
isolcpus=domain,15:
cd /sys/fs/cgroup
echo +cpuset > cgroup.subtree_control
mkdir type-fail-repro
echo 15 > type-fail-repro/cpuset.cpus
echo isolated > type-fail-repro/cpuset.cpus.partition
echo root > type-fail-repro/cpuset.cpus.partition
cat type-fail-repro/cpuset.cpus.partition
cat cpuset.cpus.effective
The requested root type is recorded as invalid, but CPU 15 remains
unavailable to the top cpuset.
Use the common partition-disable path when housekeeping validation fails.
Disable a remote partition with remote_partition_disable(); for a local
partition, return effective_xcpus to its parent. If that return would
consume the last housekeeping CPU, invalidate the outermost isolated
ancestor instead.
Fixes: 103b08709e8a ("cgroup/cpuset: Fail if isolated and nohz_full don't leave any housekeeping")
Fixes: b1034a690129 ("cgroup/cpuset: Ensure domain isolated CPUs stay in root or isolated partition")
Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
---
kernel/cgroup/cpuset.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 32a37d624c6b..fefc0afa93ec 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -2990,6 +2990,7 @@ static int update_prstate(struct cpuset *cs, int new_prs)
struct cpuset *parent = parent_cs(cs);
struct cpuset *invalidated = NULL;
struct tmpmasks tmpmask;
+ bool disable_partition = false;
bool isolcpus_updated = false;
if (old_prs == new_prs)
@@ -3052,15 +3053,21 @@ static int update_prstate(struct cpuset *cs, int new_prs)
tmpmask.new_cpus);
if (((new_prs == PRS_ISOLATED) &&
!isolated_cpus_can_update(tmpmask.new_cpus, NULL)) ||
- prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus))
+ prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus)) {
err = PERR_HKEEPING;
- else
+ disable_partition = true;
+ } else {
isolcpus_updated = true;
+ }
} else {
/*
* Switching back to member is always allowed even if it
* disables child partitions.
*/
+ disable_partition = true;
+ }
+
+ if (disable_partition) {
if (old_prs == PRS_ROOT &&
parent->partition_root_state == PRS_ISOLATED &&
!isolated_cpus_can_update(cs->effective_xcpus, NULL))
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 6/6] selftests/cgroup: Add CPU release tests for type-change validation failures
2026-08-28 9:56 [PATCH v2 0/6] cgroup/cpuset: Fix partition type transitions Guopeng Zhang
` (4 preceding siblings ...)
2026-08-28 9:56 ` [PATCH v2 5/6] cgroup/cpuset: Release CPUs when type-change validation fails Guopeng Zhang
@ 2026-08-28 9:56 ` Guopeng Zhang
5 siblings, 0 replies; 7+ messages in thread
From: Guopeng Zhang @ 2026-08-28 9:56 UTC (permalink / raw)
To: cgroups, longman, ridong.chen
Cc: tj, hannes, mkoutny, shuah, linux-kernel, linux-kselftest,
Guopeng Zhang
From: Guopeng Zhang <zhangguopeng@kylinos.cn>
Exercise housekeeping validation failures for local and remote partition
type changes. Check that the requested partition type becomes invalid,
its CPUs are returned to the top cpuset and their boot-time isolation
state is preserved.
Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
---
.../selftests/cgroup/test_cpuset_prs.sh | 93 +++++++++++++++++++
1 file changed, 93 insertions(+)
diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
index 72fe5ce48eff..9b236826166c 100755
--- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
+++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
@@ -1419,6 +1419,97 @@ test_housekeeping_cpu_return()
echo "$TEST_NAME test PASSED."
}
+#
+# A local type change that fails the boot housekeeping constraint must release
+# the partition CPUs and preserve their boot-isolated state.
+#
+test_partition_type_failure()
+{
+ TEST_NAME="Local partition type-change validation failure"
+ get_boot_isolated_cpu "$TEST_NAME" || return 0
+ echo "Running $TEST_NAME test ..."
+
+ cd $CGROUP2/test
+ echo member > cpuset.cpus.partition
+ echo $BOOT_CPU > cpuset.cpus
+ [[ $(cat cpuset.cpus.effective) = "$BOOT_CPU" ]] || {
+ echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is unavailable"
+ echo "" > cpuset.cpus
+ cd $CGROUP2
+ return 0
+ }
+ test_partition isolated
+ echo root > cpuset.cpus.partition
+ grep -q '^root invalid (partition config conflicts with housekeeping setup)$' \
+ cpuset.cpus.partition || {
+ echo "Partition type failure did not produce the expected state"
+ exit 1
+ }
+ [[ $(cat $CGROUP2/cpuset.cpus.effective) = "$CPULIST" ]] || {
+ echo "Partition type failure did not release CPU $BOOT_CPU"
+ exit 1
+ }
+ check_isolcpus "." || {
+ echo "Partition type failure lost boot-isolated CPU $BOOT_CPU"
+ exit 1
+ }
+ echo member > cpuset.cpus.partition
+ echo "" > cpuset.cpus
+ cd $CGROUP2
+ echo "$TEST_NAME test PASSED."
+}
+
+#
+# Exercise the same type-change validation failure for a remote partition.
+#
+test_remote_partition_type_failure()
+{
+ TEST_NAME="Remote partition type-change validation failure"
+ get_boot_isolated_cpu "$TEST_NAME" || return 0
+ echo "Running $TEST_NAME test ..."
+
+ cd $CGROUP2/test
+ echo member > cpuset.cpus.partition
+ echo +cpuset > cgroup.subtree_control
+ echo $BOOT_CPU > cpuset.cpus
+ echo $BOOT_CPU > cpuset.cpus.exclusive
+ [[ $(cat cpuset.cpus.effective) = "$BOOT_CPU" ]] || {
+ echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is unavailable"
+ echo "" > cpuset.cpus.exclusive
+ echo "" > cpuset.cpus
+ cd $CGROUP2
+ return 0
+ }
+ mkdir A1
+ cd A1
+ echo $BOOT_CPU > cpuset.cpus
+ echo $BOOT_CPU > cpuset.cpus.exclusive
+ test_partition isolated
+ echo root > cpuset.cpus.partition
+ grep -q '^root invalid (partition config conflicts with housekeeping setup)$' \
+ cpuset.cpus.partition || {
+ echo "Remote type failure did not produce the expected state"
+ exit 1
+ }
+ [[ $(cat $CGROUP2/cpuset.cpus.effective) = "$CPULIST" ]] || {
+ echo "Remote type failure did not release CPU $BOOT_CPU"
+ exit 1
+ }
+ check_isolcpus "." || {
+ echo "Remote type failure lost boot-isolated CPU $BOOT_CPU"
+ exit 1
+ }
+ echo member > cpuset.cpus.partition
+ echo "" > cpuset.cpus.exclusive
+ echo "" > cpuset.cpus
+ cd ..
+ rmdir A1
+ echo "" > cpuset.cpus.exclusive
+ echo "" > cpuset.cpus
+ cd $CGROUP2
+ echo "$TEST_NAME test PASSED."
+}
+
#
# Wait for inotify event for the given file and read it
# $1: cgroup file to wait for
@@ -1493,5 +1584,7 @@ test_isolated
test_boot_isolated
test_child_owned_cpus
test_housekeeping_cpu_return
+test_partition_type_failure
+test_remote_partition_type_failure
test_inotify
echo "All tests PASSED."
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread