mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Ridong Chen <ridong.chen@linux.dev>
To: Guopeng Zhang <guopeng.zhang@linux.dev>,
	Waiman Long <longman@redhat.com>, Tejun Heo <tj@kernel.org>
Cc: "Johannes Weiner" <hannes@cmpxchg.org>,
	"Michal Koutný" <mkoutny@suse.com>,
	"Shuah Khan" <shuah@kernel.org>,
	"Mathieu Desnoyers" <mathieu.desnoyers@efficios.com>,
	cgroups@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-kselftest@vger.kernel.org,
	"Guopeng Zhang" <zhangguopeng@kylinos.cn>
Subject: Re: [PATCH v3 3/3] cgroup/cpuset: Keep the top sched domain non-empty on CPU hotplug
Date: Tue, 10 Nov 2026 20:55:29 +0800	[thread overview]
Message-ID: <19708635-67de-45ab-9539-8d686caa7132@linux.dev> (raw)
In-Reply-To: <20261110050324.173461-4-guopeng.zhang@linux.dev>



On 11/10/2026 1:03 PM, Guopeng Zhang wrote:
> From: Guopeng Zhang <zhangguopeng@kylinos.cn>
> 
> In cpuset_hotplug_update_tasks(), the local partition check passes only
> the partition's own effective CPUs to tasks_nocpu_error(). For a
> top-level partition with children, top_cpuset_sd_nocpu() treats the
> children's CPUs as being returned, although hotplug does not return
> them. This can leave the top sched domain empty when its last boot
> domain housekeeping CPU goes offline.
> 
> For example, on a 32-CPU system with isolcpus=domain,10-31:
> 
>    echo 0,2-9 > A/cpuset.cpus
>    echo root > A/cpuset.cpus.partition
>    echo 5-9 > A/B/cpuset.cpus
>    echo root > A/B/cpuset.cpus.partition
>    echo 0 > /sys/devices/system/cpu/cpu1/online
>    Oops: general protection fault
>    RIP: 0010:build_perf_domains+...
> 
> The remote hotplug path does not check whether the top sched domain
> is empty.
> 
> Check the full effective_xcpus of top-level local and remote partitions
> without assuming any CPUs have been returned. Only invalidate a
> partition to restore the top domain if it can return an active boot
> domain housekeeping CPU. Partitions holding only boot-isolated or
> offline housekeeping CPUs cannot do that.
> 
> Add hotplug regression tests.
> 
> Fixes: 9b496a8bbed9 ("cgroup/cpuset: Prevent leakage of isolated CPUs into sched domains")
> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
> ---
>   kernel/cgroup/cpuset.c                        | 23 ++++++++++++++++---
>   .../selftests/cgroup/test_cpuset_prs.sh       | 19 +++++++++++++++
>   2 files changed, 39 insertions(+), 3 deletions(-)
> 
> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
> index 8b6831983803..0b392d50df27 100644
> --- a/kernel/cgroup/cpuset.c
> +++ b/kernel/cgroup/cpuset.c
> @@ -4043,6 +4043,7 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
>   	static nodemask_t new_mems;
>   	bool cpus_updated;
>   	bool mems_updated;
> +	bool nocpu;
>   	int partcmd = -1;
>   	struct cpuset *parent;
>   
> @@ -4054,9 +4055,24 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
>   	if (!tmp || !cs->partition_root_state)
>   		goto update_tasks;
>   
> +	if (parent == &top_cpuset || is_remote_partition(cs)) {
> +		/*
> +		 * Hotplug does not return CPUs held by child partitions. Only
> +		 * invalidate a partition for an empty top sched domain if it
> +		 * can return an active boot domain housekeeping CPU.
> +		 */
> +		cpumask_and(tmp->addmask, cs->effective_xcpus,
> +			    housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT));
> +		nocpu = cpumask_intersects(tmp->addmask, cpu_active_mask) &&
> +			top_cpuset_sd_nocpu(cs->effective_xcpus, NULL);
> +		nocpu |= cpumask_empty(&new_cpus) &&
> +			 partition_is_populated(cs, NULL);
> +	} else {
> +		nocpu = tasks_nocpu_error(parent, cs, &new_cpus);
> +	}
> +
>   	if (is_remote_partition(cs) && (cpumask_empty(subpartitions_cpus) ||
> -				       (cpumask_empty(&new_cpus) &&
> -					partition_is_populated(cs, NULL)))) {
> +				       nocpu)) {
>   		WRITE_ONCE(cs->prs_err, PERR_HOTPLUG);
>   		remote_partition_disable(cs, tmp);
>   		compute_effective_cpumask(&new_cpus, cs, parent);
> @@ -4069,10 +4085,11 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
>   	 * 2) parent is invalid or doesn't grant any cpus to child
>   	 *    partitions.
>   	 * 3) subpartitions_cpus is empty.
> +	 * 4) top_cpuset needs an active boot domain housekeeping CPU from cs.
>   	 */
>   	if (is_local_partition(cs) &&
>   	    (!is_partition_valid(parent) ||
> -	     tasks_nocpu_error(parent, cs, &new_cpus) ||
> +	     nocpu ||
>   	     cpumask_empty(subpartitions_cpus)))
>   		partcmd = partcmd_invalidate;
>   	/*
> diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
> index ee4d57b7e35b..f345696e5b53 100755
> --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
> +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
> @@ -648,8 +648,27 @@ ISOLCPUS_DOMAIN_TEST_MATRIX=(
>   
>   	# An invalid partition can recover once it leaves a top-level HK CPU.
>   	" C0-9:P1   .      .      .    C0-6,8-9 .      .      .     0 A1:0-6,8-9 A1:P1 7|"
> +
> +	# A local root must not count its child's CPUs as already returned.
> +	" C0,2-9:P1 C5-9:P1 . . O1=0 . . . 0 A1:0,2-9|A2:5-9 A1:P-1|A2:P-1 1|"
> +
> +	# A remote root must return CPUs when the top domain loses its last HK CPU.
> +	" X0,2-9 CX0,2-9:P1 . . O1=0 . . . 0 A2:0,2-9 A2:P-1 1|"
>   )
>   
> +# Keep an online boot-isolated CPU at the top so hotplug does not
> +# clear subpartitions_cpus before checking the individual partitions.
> +if [[ "$ONLINE_LAST" -gt 10 ]]; then
> +	ISOLCPUS_DOMAIN_TEST_MATRIX+=(
> +		# An offline HK CPU cannot restore the top domain. Keep this local
> +		# partition valid while its sibling returns active HK CPUs.
> +		" O2=0:C2,10:P2 . . C0,3-9:P1 O1=0 . . . 0 A1:10|B1:0,3-9 A1:P2|B1:P-1 1-2|2"
> +
> +		# Check the same active-CPU rule for a remote partition.
> +		" O2=0:X2,10 CX2,10:P2 . C0,3-9:P1 O1=0 . . . 0 A2:10|B1:0,3-9 A2:P2|B1:P-1 1-2|2"
> +	)
> +fi
> +
>   # Test matrix to be run when boot time nohz_full CPUs cover all CPUs except CPUs 0-7
>   # This also means that CPU 8 is available and is a nohz_full CPU.
>   NOHZ_FULL_TEST_MATRIX=(

Hi Guopeng,

Looking at patches 1-3, the same check is changed back and forth in
several places, and the places don't do it in the same way. I'd prefer
a single helper that is unified and easy to understand, so that every
place can just call it.

As tasks_nocpu_error() is shared by the CPU mask update paths and the
hotplug path, patches 1-3 end up checking the same thing in several
places with different masks. How about making tasks_nocpu_error() take
the CPUs to be returned as an explicit argument, the same way as
top_cpuset_sd_nocpu() does, and use it for all parents? Something like
the following (not tested):

/*
* tasks_nocpu_error - Return true if tasks will have no effective_cpus
* @parent: parent partition, NULL for a remote partition
* @cs: the partition
* @xcpus: exclusive CPUs that @cs will hold after the update
* @del_cpus: exclusive CPUs to be returned to @parent, can be NULL
*
* @del_cpus may include CPUs that are also in @xcpus and will stay in
* @cs. They are skipped by the @xcpus check, so callers can pass the
* current effective_xcpus of @cs without masking it first.
*
* top_cpuset always has tasks, and its sched domain needs an active boot
* domain housekeeping CPU.
*/
static bool tasks_nocpu_error(struct cpuset *parent, struct cpuset *cs,
							const struct cpumask *xcpus,
							const struct cpumask *del_cpus)
{
	const struct cpumask *mask = cpu_possible_mask;
	int cpu;

	/* A populated @cs must have an active CPU in @xcpus */
	if (!cpumask_intersects(xcpus, cpu_active_mask) &&
		partition_is_populated(cs, NULL))
			return true;

	/*
	 * A populated @parent must keep an active CPU in @mask, either from
	 * its effective_cpus or from @del_cpus, that isn't taken by @cs.
	 */
	if (!parent)
		  parent = &top_cpuset;

	if (parent == &top_cpuset)
		  mask = housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT);
	else if (!partition_is_populated(parent, cs))
		  return false;
		
	for_each_cpu_and(cpu, mask, cpu_active_mask) {
		  if (cpumask_test_cpu(cpu, xcpus))
				  continue;
		  if (cpumask_test_cpu(cpu, parent->effective_cpus) ||
			  (del_cpus && cpumask_test_cpu(cpu, del_cpus)))
				  return false;
	}
	return true;
}

The callers then become:

/* update_parent_effective_cpumask() */
nocpu = tasks_nocpu_error(parent, cs, tmp->delmask,
			is_partition_valid(cs) ? cs->effective_xcpus : NULL);
						
/* validate_partition() */
if (tasks_nocpu_error(is_remote_partition(cs) ? NULL : parent, cs,
			trialcs->effective_xcpus,
			is_partition_valid(cs) ? cs->effective_xcpus : NULL))
					
/* remote_partition_enable(), instead of top_cpuset_sd_nocpu() */
tasks_nocpu_error(NULL, cs, tmp->new_cpus, NULL)

/* remote_cpus_update(), instead of top_cpuset_sd_nocpu() */
tasks_nocpu_error(NULL, cs, excpus, cs->effective_xcpus)

/* cpuset_hotplug_update_tasks(), hotplug returns nothing */
nocpu = tasks_nocpu_error(is_remote_partition(cs) ? NULL : parent, cs,
						&new_cpus, NULL);
							
In the hotplug path, @del_cpus is NULL as nothing is returned to the
parent. new_cpus can be used as @xcpus for both checks, as the CPUs of
the child partitions of cs are not in the parent's effective_cpus
anyway. The check that cs can return an active boot domain housekeeping
CPU before invalidating it can stay there.

This also covers the local parent case I mentioned in patch 2, so
patch 2 won't be needed. Could you give it a try with your test cases
and that reproducer?

-- 
Best regards
Ridong


      reply	other threads:[~2026-11-10 12:55 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10  5:03 [PATCH v3 0/3] cgroup/cpuset: Keep the top scheduling domain populated Guopeng Zhang
2026-11-10  5:03 ` [PATCH v3 1/3] cgroup/cpuset: Reject updates that empty the top sched domain Guopeng Zhang
2026-11-10 12:13   ` Ridong Chen
2026-11-10  5:03 ` [PATCH v3 2/3] cgroup/cpuset: Allow top-level partition CPU replacement Guopeng Zhang
2026-11-10 12:11   ` Ridong Chen
2026-11-10  5:03 ` [PATCH v3 3/3] cgroup/cpuset: Keep the top sched domain non-empty on CPU hotplug Guopeng Zhang
2026-11-10 12:55   ` Ridong Chen [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=19708635-67de-45ab-9539-8d686caa7132@linux.dev \
    --to=ridong.chen@linux.dev \
    --cc=cgroups@vger.kernel.org \
    --cc=guopeng.zhang@linux.dev \
    --cc=hannes@cmpxchg.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=longman@redhat.com \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=mkoutny@suse.com \
    --cc=shuah@kernel.org \
    --cc=tj@kernel.org \
    --cc=zhangguopeng@kylinos.cn \
    /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®