From nobody Sat Jul 25 22:31:46 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6786B2D4B68 for ; Sun, 12 Jul 2026 15:01:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783868506; cv=none; b=gD9324+5lLNRQkf4yRuU4JqAHOHrf4/rX+2uhkuJRMcgGeFeeDYpaHDpJwqiWAPA/qToM3KVlKn/sK5eWutnD55rszSGrYSs5ofNs9aN58rUdfDRwHPkuPrKyc2wDKMf55usmtdx4fcoOD/ArdTUd89DIkj45c0V0/eSPhz0FT4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783868506; c=relaxed/simple; bh=pHDGywNEMjucH1pm/H1+YHJKdnYw+vQDrwC1wqvPfvo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=o98z28XkRGMMDlWg1+xSD74F1izxaZ0BlpWDiYVU5uUGsNnMWqE71pKAJR5BTKwgAQ4FY92CVt3jtfMUEvoDkEC/5P4pHPig413bqPmlocR1x78wVsC5KrMHDearzWaCALTPI484W5+E/GjmwgO1BZP4+W1fGBhg4W4KQe+Gc6w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=DA6GQSzA; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="DA6GQSzA" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1783868502; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=FB69Y+VvNoTOSkjmMegtswoHj9QUfF0cVIM7Z0B1wYY=; b=DA6GQSzAwDLpOkDx3dUfu7+Xa111c1CS6Prp0IABhXry3Oc4tjztSiNaAnLYLiOqANQHm/ DCg64yZPZv6QrbUVeUPT7UXdjKZhoBMF3sUtoZFuBmcKRd5mTZM7y0AvCyxj1DzglzMt1M OANQUoKC2Yt7euBqArF6yEBKb0xORsc= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-308-SD9P9dNHOY236uZa85uewA-1; Sun, 12 Jul 2026 11:01:39 -0400 X-MC-Unique: SD9P9dNHOY236uZa85uewA-1 X-Mimecast-MFC-AGG-ID: SD9P9dNHOY236uZa85uewA_1783868497 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 788BA18009E4; Sun, 12 Jul 2026 15:01:37 +0000 (UTC) Received: from llong-thinkpadp1gen5.rmtusnh.csb (unknown [10.22.80.43]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id E73383000B54; Sun, 12 Jul 2026 15:01:35 +0000 (UTC) From: Waiman Long To: Ridong Chen , Tejun Heo , Johannes Weiner , =?UTF-8?q?Michal=20Koutn=C3=BD?= , Shuah Khan Cc: cgroups@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Waiman Long Subject: [PATCH-next v2 1/3] cgroup/cpuset: Handle the special case of non-moving tasks in cpuset_can_attach() Date: Sun, 12 Jul 2026 11:01:25 -0400 Message-ID: <20260712150127.236790-2-longman@redhat.com> In-Reply-To: <20260712150127.236790-1-longman@redhat.com> References: <20260712150127.236790-1-longman@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" With cgroup v2 migration of a multithreaded process having threads in different cgroups of a threaded subtree, it is possible that cpuset_can_attach() can be called with tasks that are not migrating with respect to cpuset if cpuset controller is not enabled in some of the subtree cgroups. IOW, the old cpuset can be the same as the new one. This can cause problem when we need to track the set of old cpusets and the new cpusets in singly linked lists as a cpuset cannot be in both lists. As reported by Tejun, the following is an example threaded subtree with partial cpuset delegation that can cause this issue to show up. P (+cpuset) |- R (cpuset) <- destination | `- C (no cpuset) -> effective cpuset =3D=3D R `- W (cpuset) Group leader in R, thread_a in C, thread_b in W; migrate the whole process into R (echo $PID > R/cgroup.procs). thread_a moves C->R: its cgroup changes so compare_css_sets() keeps it in the taskset, but its cpuset css is unchanged (C inherits R's), so task_cs() =3D=3D cs =3D=3D R. cpuset is in ss_mask because thread_b (W->R) changed. can_attach() then tags R as a source (thread_a) and the destination (thread_b): Handle this special case by skipping tasks that are not migrating in cpuset_can_attach() and avoid calling cpuset_can_attach_check() in this case. By doing so, the destination cpuset will not be put into source cpuset linked list. As the source cpuset cannot be easily determined in cpuset_attach(), unnecessary work can be performed if a task is not actually migrating. However, no harm will be done except wasting some CPU cycles. If it happens that none of the tasks is migrating, attach_ctx.old_cs will be NULL and task iteration won't be needed. Reported-by: Tejun Heo Closes: https://lore.kernel.org/lkml/e254af713b5345aec3d086771ecf1e71@kerne= l.org Signed-off-by: Waiman Long --- kernel/cgroup/cpuset.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index cf0d005d2b78..957c80a99fde 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -3134,20 +3134,12 @@ static int cpuset_can_attach(struct cgroup_taskset = *tset) bool setsched_check; int ret; =20 - /* used later by cpuset_attach() */ - attach_ctx.old_cs =3D task_cs(cgroup_taskset_first(tset, &css)); - oldcs =3D attach_ctx.old_cs; - cs =3D css_cs(css); - + oldcs =3D NULL; mutex_lock(&cpuset_mutex); + attach_ctx.old_cs =3D NULL; /* Used later in cpuset_attach_task() */ attach_ctx.cpus_updated =3D false; attach_ctx.mems_updated =3D false; =20 - /* Check to see if task is allowed in the cpuset */ - ret =3D cpuset_can_attach_check(cs, oldcs, &setsched_check); - if (ret) - goto out_unlock; - /* * The attach_ctx.old_cs is used mainly by cpuset_migrate_mm() to get * the old_mems_allowed value. There are two ways that many-to-one @@ -3164,17 +3156,30 @@ static int cpuset_can_attach(struct cgroup_taskset = *tset) * of child cpusets must always be a subset of the parent. So no real * page migration will be necessary no matter which child cpuset is * selected as attach_ctx.old_cs. + * + * For a v2 threaded subtree where cpuset isn't enabled in some of the + * cgroups, it is possible that oldcs =3D=3D cs for some of the tasks. + * In this case, we can skip checking on those tasks as there is no + * actual migration wrt cpuset. */ cgroup_taskset_for_each(task, css, tset) { struct cpuset *new_oldcs =3D task_cs(task); =20 + cs =3D css_cs(css); if (new_oldcs !=3D oldcs) { oldcs =3D new_oldcs; + if (oldcs =3D=3D cs) + continue; + if (!attach_ctx.old_cs) + attach_ctx.old_cs =3D oldcs; ret =3D cpuset_can_attach_check(cs, oldcs, &setsched_check); if (ret) goto out_unlock; } =20 + if (oldcs =3D=3D cs) + continue; + ret =3D task_can_attach(task); if (ret) goto out_unlock; @@ -3312,6 +3317,14 @@ static void cpuset_attach(struct cgroup_taskset *tse= t) attach_ctx.task_work_queued =3D false; guarantee_online_mems(cs, &attach_ctx.nodemask_to); =20 + /* + * attach_ctx.old_cs can only be NULL if no task is actually migrating. + * This is highly unlikely. If it happens at all, there is nothing we + * need to do here. + */ + if (unlikely(!attach_ctx.old_cs)) + goto out; + /* * In the default hierarchy, enabling cpuset in the child cgroups * will trigger a cpuset_attach() call with no change in effective cpus --=20 2.55.0 From nobody Sat Jul 25 22:31:46 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 75EB333F369 for ; Sun, 12 Jul 2026 15:01:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783868507; cv=none; b=XAtMg3REizFKCryvrPDRA0H6kXZs9pQP4BmtJFvjbgOHeFh4GiuDmUQ5o6VUyddrOUk1UxsSURGWWwt0UzF2T3SYBPpVq6P1gbDxqTHELatnkCmXSHU43BN3UfaNLXpHzzxVsnYcPcnJN8nRKyDKFkvYhoRAot9dD9xOi+CFKFI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783868507; c=relaxed/simple; bh=NO+Q1tA0UXDjBP3qUjGbf1y5iQw/K8VO90tA/da1nTM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Fhn8pXtiDX5GDyOSupUYo16wdmxUf2GEtgJWzULKVd4h3DptrcIF369n4fAw6XdBsKJXyjWECwVayq6bwIYzsBthx1yBDUWcZuMgax2xLBt9ZQAG+Ekyap5HONZvbHne0OkekvFW76wzWjIr4QdO9jVTbFDPXm7qLyJJJzLdTZo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=QOkmgI7J; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="QOkmgI7J" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1783868504; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=0W6tLG6Kpl3RclFp+3DsQZreevL1sy3cazDoDupnOC4=; b=QOkmgI7Jxw2bTtg8CRYheapASrcnNfrfX3KZL3jdofow1Z9CYM3FX1O0049mpNZ7c1YfKG qQGhyZ5UQaHjfJQlgbqqNQT/OiKTpLHnCrRp97LtU1EhKyq2ecSa2JNr8l04vIYeAJoZl4 a0s57ilsST8k04Rv5bxd8N7UjCDFjN0= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-118-rUlbfuLlMzm_Wi450kdgLA-1; Sun, 12 Jul 2026 11:01:40 -0400 X-MC-Unique: rUlbfuLlMzm_Wi450kdgLA-1 X-Mimecast-MFC-AGG-ID: rUlbfuLlMzm_Wi450kdgLA_1783868499 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 342C61956076; Sun, 12 Jul 2026 15:01:39 +0000 (UTC) Received: from llong-thinkpadp1gen5.rmtusnh.csb (unknown [10.22.80.43]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id C8FCC3000B50; Sun, 12 Jul 2026 15:01:37 +0000 (UTC) From: Waiman Long To: Ridong Chen , Tejun Heo , Johannes Weiner , =?UTF-8?q?Michal=20Koutn=C3=BD?= , Shuah Khan Cc: cgroups@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Waiman Long Subject: [PATCH-next v2 2/3] cgroup/cpuset: Support multiple destination cpusets for cpuset_*attach() Date: Sun, 12 Jul 2026 11:01:26 -0400 Message-ID: <20260712150127.236790-3-longman@redhat.com> In-Reply-To: <20260712150127.236790-1-longman@redhat.com> References: <20260712150127.236790-1-longman@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Content-Type: text/plain; charset="utf-8" The only case where the cgroup_taskset structure requires task migration to multiple cpusets is when enabling a cpuset controller in cgroup v2 where the newly created child cpusets inherits the same effective CPUs and memory nodes from the parent. In that case, task migration can happen directly with no update to tasks' CPU and memory nodes assignment and no further work needed from the cpuset side except updating nr_deadline_tasks when DL tasks are involved and setting old_mems_allowed in the child cpusets. Do that by tracking all the destination cpusets with a new dst_cs_head singly linked list. The reset_migrate_dl_data() function is integrated into clear_attach_data() so that it can be used for both source and destination cpusets. A warning will be printed if there are multiple destination cpusets but it is not on default hierarchy or when the CPUs or memory nodes change. Signed-off-by: Waiman Long --- kernel/cgroup/cpuset.c | 104 +++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 957c80a99fde..9eb982c5e4a5 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -366,10 +366,12 @@ static struct { bool cpus_updated; bool mems_updated; bool task_work_queued; + bool many_dest_cs; /* Have many destination cpusets */ struct cpuset *old_cs; /* Source cpuset */ nodemask_t nodemask_to; } attach_ctx; static LLIST_HEAD(src_cs_head); +static LLIST_HEAD(dst_cs_head); =20 /* * Wait if task attach is in progress until it is done and then acquire @@ -3052,6 +3054,9 @@ static int cpuset_can_attach_check(struct cpuset *cs,= struct cpuset *oldcs, if (!llist_on_list(&oldcs->attach_node)) llist_add(&oldcs->attach_node, &src_cs_head); =20 + if (!llist_on_list(&cs->attach_node)) + llist_add(&cs->attach_node, &dst_cs_head); + cpus_updated =3D !cpumask_equal(cs->effective_cpus, oldcs->effective_cpus= ); mems_updated =3D !nodes_equal(cs->effective_mems, oldcs->effective_mems); =20 @@ -3080,35 +3085,31 @@ static int cpuset_can_attach_check(struct cpuset *c= s, struct cpuset *oldcs, return 0; } =20 -static int cpuset_reserve_dl_bw(struct cpuset *cs) +static int cpuset_reserve_dl_bw(void) { + struct cpuset *cs; int cpu, ret; =20 - if (!cs->sum_migrate_dl_bw) - return 0; + llist_for_each_entry(cs, dst_cs_head.first, attach_node) { + if (!cs->sum_migrate_dl_bw) + continue; =20 - cpu =3D cpumask_any_and(cpu_active_mask, cs->effective_cpus); - if (unlikely(cpu >=3D nr_cpu_ids)) - return -EINVAL; + cpu =3D cpumask_any_and(cpu_active_mask, cs->effective_cpus); + if (unlikely(cpu >=3D nr_cpu_ids)) + return -EINVAL; =20 - ret =3D dl_bw_alloc(cpu, cs->sum_migrate_dl_bw); - if (ret) - return ret; + ret =3D dl_bw_alloc(cpu, cs->sum_migrate_dl_bw); + if (ret) + return ret; =20 - cs->dl_bw_cpu =3D cpu; + cs->dl_bw_cpu =3D cpu; + } return 0; } =20 -static void reset_migrate_dl_data(struct cpuset *cs) -{ - cs->nr_migrate_dl_tasks =3D 0; - cs->sum_migrate_dl_bw =3D 0; - cs->dl_bw_cpu =3D -1; -} - /* * Clear and optionally apply (@cancel is false) the attach related data i= n the - * source cpusets. + * source or destination cpuset. */ static void clear_attach_data(struct llist_head *head, bool cancel) { @@ -3120,7 +3121,11 @@ static void clear_attach_data(struct llist_head *hea= d, bool cancel) if (cs->nr_migrate_dl_tasks) { if (!cancel) atomic_add(cs->nr_migrate_dl_tasks, &cs->nr_deadline_tasks); + else if (cs->dl_bw_cpu >=3D 0) /* && cacnel */ + dl_bw_free(cs->dl_bw_cpu, cs->sum_migrate_dl_bw); cs->nr_migrate_dl_tasks =3D 0; + cs->sum_migrate_dl_bw =3D 0; + cs->dl_bw_cpu =3D -1; } } } @@ -3134,11 +3139,12 @@ static int cpuset_can_attach(struct cgroup_taskset = *tset) bool setsched_check; int ret; =20 - oldcs =3D NULL; + cs =3D oldcs =3D NULL; mutex_lock(&cpuset_mutex); attach_ctx.old_cs =3D NULL; /* Used later in cpuset_attach_task() */ attach_ctx.cpus_updated =3D false; attach_ctx.mems_updated =3D false; + attach_ctx.many_dest_cs =3D false; =20 /* * The attach_ctx.old_cs is used mainly by cpuset_migrate_mm() to get @@ -3163,10 +3169,13 @@ static int cpuset_can_attach(struct cgroup_taskset = *tset) * actual migration wrt cpuset. */ cgroup_taskset_for_each(task, css, tset) { + struct cpuset *new_cs =3D css_cs(css); struct cpuset *new_oldcs =3D task_cs(task); =20 - cs =3D css_cs(css); - if (new_oldcs !=3D oldcs) { + if ((new_oldcs !=3D oldcs) || (new_cs !=3D cs)) { + if (cs && (new_cs !=3D cs)) + attach_ctx.many_dest_cs =3D true; + cs =3D new_cs; oldcs =3D new_oldcs; if (oldcs =3D=3D cs) continue; @@ -3207,12 +3216,28 @@ static int cpuset_can_attach(struct cgroup_taskset = *tset) } } =20 - ret =3D cpuset_reserve_dl_bw(cs); + /* + * The only case where there are multiple destination cpusets for + * task migration is when enabling a v2 cpuset controllers where + * tasks will be migrated to multiple child cpusets from a parent + * cpuset with the same effective CPUs and memory nodes. IOW, + * both attach_cpus_updated and attach_mems_updated should be false. + * If not, it is a condition that the current code cannot handle. + * Print a warning and abort the attach operation as further code + * change may be needed. + */ + if (WARN_ON_ONCE(attach_ctx.many_dest_cs && (!cpuset_v2() || + attach_ctx.cpus_updated || attach_ctx.mems_updated))) { + ret =3D -EINVAL; + goto out_unlock; + } + + ret =3D cpuset_reserve_dl_bw(); =20 out_unlock: if (ret) { - reset_migrate_dl_data(cs); /* Destination cpuset only */ clear_attach_data(&src_cs_head, true); + clear_attach_data(&dst_cs_head, true); } else { attach_ctx.in_progress++; } @@ -3223,22 +3248,10 @@ static int cpuset_can_attach(struct cgroup_taskset = *tset) =20 static void cpuset_cancel_attach(struct cgroup_taskset *tset) { - struct cgroup_subsys_state *css; - struct cpuset *cs; - - cgroup_taskset_first(tset, &css); - cs =3D css_cs(css); - mutex_lock(&cpuset_mutex); dec_attach_in_progress_locked(); clear_attach_data(&src_cs_head, true); - - if (cs->dl_bw_cpu >=3D 0) - dl_bw_free(cs->dl_bw_cpu, cs->sum_migrate_dl_bw); - - if (cs->nr_migrate_dl_tasks) - reset_migrate_dl_data(cs); - + clear_attach_data(&dst_cs_head, true); mutex_unlock(&cpuset_mutex); } =20 @@ -3320,7 +3333,7 @@ static void cpuset_attach(struct cgroup_taskset *tset) /* * attach_ctx.old_cs can only be NULL if no task is actually migrating. * This is highly unlikely. If it happens at all, there is nothing we - * need to do here. + * need to do here not even setting old_mems_allowed. */ if (unlikely(!attach_ctx.old_cs)) goto out; @@ -3329,25 +3342,24 @@ static void cpuset_attach(struct cgroup_taskset *ts= et) * In the default hierarchy, enabling cpuset in the child cgroups * will trigger a cpuset_attach() call with no change in effective cpus * and mems. In that case, we can optimize out by skipping the task - * iteration and update. + * iteration and the destination cpuset list is iterated to set + * old_mems_allowed. */ - if (cpuset_v2() && !attach_ctx.cpus_updated && !attach_ctx.mems_updated) + if (cpuset_v2() && !attach_ctx.cpus_updated && !attach_ctx.mems_updated) { + llist_for_each_entry(cs, dst_cs_head.first, attach_node) + cs->old_mems_allowed =3D attach_ctx.nodemask_to; goto out; + } =20 cgroup_taskset_for_each(task, css, tset) cpuset_attach_task(cs, task); =20 -out: if (attach_ctx.task_work_queued) schedule_flush_migrate_mm(); cs->old_mems_allowed =3D attach_ctx.nodemask_to; - - if (cs->nr_migrate_dl_tasks) { - atomic_add(cs->nr_migrate_dl_tasks, &cs->nr_deadline_tasks); - reset_migrate_dl_data(cs); - } - +out: clear_attach_data(&src_cs_head, false); + clear_attach_data(&dst_cs_head, false); dec_attach_in_progress_locked(); =20 mutex_unlock(&cpuset_mutex); --=20 2.55.0 From nobody Sat Jul 25 22:31:46 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 919AD33F5A4 for ; Sun, 12 Jul 2026 15:01:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783868507; cv=none; b=NXIDF3FpOVJr25yeuaOPM2JsF7ewzIq637A0a8gy7A1reizP8wt6h1xKADITSXfAvYBihwnjP9et7VSv76s8qMVn25INjrU2rREdoogEShxMfxeogZZTDov6ZW6CG2/QohRhvAHrbMu0hrUYkbhvZF69soWmggACIks9DHVZhYs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783868507; c=relaxed/simple; bh=U3XeUnEL+rDTz+vN8wgmKNKJy9pBXpmiepr3NAb/tR8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=bH3YEIVP4DkBp/tK/HxdWgsJGLeQfTgz3y3BGCvZO5sKA0s+ALxvzLOQFUBziR7Q8AUx1M1OVuRUOUxImxpi2E6gCS1MXig0GlhR/h36BEy1Cyx3XZo0xfwATzKcf9vLoGUcl41/6zwAycuYi0bi8JZQGzHvOhMMohha3ui/IBs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=Vfzd7ssR; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="Vfzd7ssR" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1783868504; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=/Qe+95+MXsZNLDf2pjnP57V9Q3yNl9VozQHNGuksZog=; b=Vfzd7ssRWKOAwxkWA9KdNEzUW0IQJosql5Dmc7ZYN9wkSLzyo5rYv1qJmsfhKxfuW/iGik Iog9+ujHz/tb3vE+ti4a+Bvf/HreNe2QyChDHR4a7h2+8Mzv4gD+pg37OGYHOfG6Yn3LPs eEXfHi09MFTLDK8RjcaNCu4NfatdXEo= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-683-lffXiap-NA6n2b3dfVQDXQ-1; Sun, 12 Jul 2026 11:01:42 -0400 X-MC-Unique: lffXiap-NA6n2b3dfVQDXQ-1 X-Mimecast-MFC-AGG-ID: lffXiap-NA6n2b3dfVQDXQ_1783868501 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 25DCC180064A; Sun, 12 Jul 2026 15:01:41 +0000 (UTC) Received: from llong-thinkpadp1gen5.rmtusnh.csb (unknown [10.22.80.43]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 89B9F3000B50; Sun, 12 Jul 2026 15:01:39 +0000 (UTC) From: Waiman Long To: Ridong Chen , Tejun Heo , Johannes Weiner , =?UTF-8?q?Michal=20Koutn=C3=BD?= , Shuah Khan Cc: cgroups@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, Waiman Long Subject: [PATCH-next v2 3/3] selftests/cgroup: Add test for cpuset affinity on controller disable Date: Sun, 12 Jul 2026 11:01:27 -0400 Message-ID: <20260712150127.236790-4-longman@redhat.com> In-Reply-To: <20260712150127.236790-1-longman@redhat.com> References: <20260712150127.236790-1-longman@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 From: Michal Koutn=C3=BD Add a new selftest that exposes a bug in cpuset_attach() where thread CPU affinity is not properly updated when the cpuset controller is disabled in a threaded cgroup hierarchy. The test creates a threaded cgroup hierarchy with two child cgroups (A and B) having different cpuset.cpus constraints: - Parent: cpuset.cpus=3D0-1 - Child A: cpuset.cpus=3D0-1 - Child B: cpuset.cpus=3D1 (restricted to CPU 1 only) A multithreaded process is created with threads placed in different cgroups. When the cpuset controller is disabled on the parent, thread affinities should be updated to match the parent's cpuset. Expected behavior: - thread_a affinity: {0-1} before and after (unchanged) - thread_b affinity: {1} before, {0-1} after (expanded) Current buggy behavior: - thread_b affinity remains {1} after controller disable Assisted-by: Claude:claude-sonnet-4-5 Signed-off-by: Michal Koutn=C3=BD Acked-by: Waiman Long --- tools/testing/selftests/cgroup/test_cpuset.c | 243 +++++++++++++++++++ 1 file changed, 243 insertions(+) diff --git a/tools/testing/selftests/cgroup/test_cpuset.c b/tools/testing/s= elftests/cgroup/test_cpuset.c index c5cf8b56ceb8..8b4c4a9dd78b 100644 --- a/tools/testing/selftests/cgroup/test_cpuset.c +++ b/tools/testing/selftests/cgroup/test_cpuset.c @@ -1,7 +1,13 @@ // SPDX-License-Identifier: GPL-2.0 =20 +#define _GNU_SOURCE +#include #include +#include +#include #include +#include +#include =20 #include "kselftest.h" #include "cgroup_util.h" @@ -232,6 +238,242 @@ static int test_cpuset_perms_subtree(const char *root) return ret; } =20 +static int get_cpu_affinity(cpu_set_t *mask) +{ + CPU_ZERO(mask); + return sched_getaffinity(0, sizeof(*mask), mask); +} + +static int cpu_set_equal(cpu_set_t *dst, unsigned long mask) +{ + cpu_set_t expected; + + CPU_ZERO(&expected); + assert(sizeof(mask) < CPU_SETSIZE); + + for (int cpu =3D 0; cpu < sizeof(mask); ++cpu) + if ((1UL << cpu) & mask) + CPU_SET(cpu, &expected); + + return CPU_EQUAL(&expected, dst); +} + +enum test_phase { + AFFINITY_SETUP, + AFFINITY_THREAD_A_READY, + AFFINITY_THREADS_READY, + AFFINITY_CONTROLLER_DISABLED, + AFFINITY_COMPLETE, + AFFINITY_ERROR +}; + +struct thread_args { + const char *cgroup; + cpu_set_t *affinity_before; + cpu_set_t *affinity_after; + enum test_phase ready_phase; +}; + +static pthread_mutex_t test_mutex =3D PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t test_cond =3D PTHREAD_COND_INITIALIZER; +static enum test_phase test_phase; + +static void *affinity_thread_fn(void *arg) +{ + struct thread_args *args =3D (struct thread_args *)arg; + + if (cg_enter_current_thread(args->cgroup)) + goto fail; + + if (get_cpu_affinity(args->affinity_before) !=3D 0) + goto fail; + + pthread_mutex_lock(&test_mutex); + if (test_phase < args->ready_phase) + test_phase =3D args->ready_phase; + pthread_cond_broadcast(&test_cond); + + while (test_phase < AFFINITY_CONTROLLER_DISABLED) + pthread_cond_wait(&test_cond, &test_mutex); + pthread_mutex_unlock(&test_mutex); + + if (get_cpu_affinity(args->affinity_after) !=3D 0) + goto fail; + + + return NULL; + +fail: + pthread_mutex_lock(&test_mutex); + test_phase =3D AFFINITY_ERROR; + pthread_cond_broadcast(&test_cond); + pthread_mutex_unlock(&test_mutex); + return NULL; +} + +/* + * Test that disabling cpuset controller properly updates thread affinity. + * + * This test exposes a bug in cpuset_attach() where threads in child cgrou= ps + * don't get their affinity updated when the cpuset controller is disabled. + * + * Setup: + * - Create parent cgroup with cpuset.cpus=3D0-1 + * - Create child A with cpuset.cpus=3D0-1 + * - Create child B with cpuset.cpus=3D1 + * - Place multithreaded process: group leader + thread_a in A, thread_b i= n B + * - Disable cpuset controller on parent + * + * Expected: thread_b's affinity should expand from {1} to {0-1} + * Buggy: thread_b's affinity remains {1} + */ +static int test_cpuset_affinity_on_controller_disable(const char *root) +{ + char *parent =3D NULL, *child_a =3D NULL, *child_b =3D NULL; + pthread_t thread_a, thread_b; + int thread_a_created =3D 0, thread_b_created =3D 0; + cpu_set_t affinity_a_before, affinity_a_after; + cpu_set_t affinity_b_before, affinity_b_after; + int ret =3D KSFT_FAIL; + + parent =3D cg_name(root, "cpuset_affinity_test"); + if (!parent) + goto cleanup; + if (cg_create(parent)) + goto cleanup; + if (cg_write(parent, "cgroup.type", "threaded")) + goto cleanup; + + child_a =3D cg_name(parent, "A"); + if (!child_a) + goto cleanup; + if (cg_create(child_a)) + goto cleanup; + if (cg_write(child_a, "cgroup.type", "threaded")) + goto cleanup; + + child_b =3D cg_name(parent, "B"); + if (!child_b) + goto cleanup; + if (cg_create(child_b)) + goto cleanup; + if (cg_write(child_b, "cgroup.type", "threaded")) + goto cleanup; + + /* Now enable cpuset controller in parent */ + if (cg_write(parent, "cgroup.subtree_control", "+cpuset")) { + ret =3D KSFT_SKIP; + goto cleanup; + } + + /* Set CPU affinity constraints */ + if (cg_write(parent, "cpuset.cpus", "0-1")) + goto cleanup; + if (cg_write(child_a, "cpuset.cpus", "0-1")) + goto cleanup; + if (cg_write(child_b, "cpuset.cpus", "1")) + goto cleanup; + + /* Move group leader (main thread) to child A */ + if (cg_enter_current(child_a)) + goto cleanup; + + /* Create threads - they will move themselves to their respective cgroups= */ + test_phase =3D AFFINITY_SETUP; + + struct thread_args args_a =3D { + .cgroup =3D child_a, + .affinity_before =3D &affinity_a_before, + .affinity_after =3D &affinity_a_after, + .ready_phase =3D AFFINITY_THREAD_A_READY, + }; + if (pthread_create(&thread_a, NULL, affinity_thread_fn, &args_a)) + goto cleanup; + thread_a_created =3D 1; + + struct thread_args args_b =3D { + .cgroup =3D child_b, + .affinity_before =3D &affinity_b_before, + .affinity_after =3D &affinity_b_after, + .ready_phase =3D AFFINITY_THREADS_READY, + }; + if (pthread_create(&thread_b, NULL, affinity_thread_fn, &args_b)) + goto cleanup_threads; + thread_b_created =3D 1; + + pthread_mutex_lock(&test_mutex); + while (test_phase < AFFINITY_THREADS_READY) + pthread_cond_wait(&test_cond, &test_mutex); + + /* If a thread failed during setup, bail out */ + if (test_phase =3D=3D AFFINITY_ERROR) { + pthread_mutex_unlock(&test_mutex); + goto cleanup_threads; + } + pthread_mutex_unlock(&test_mutex); + + if (!cpu_set_equal(&affinity_a_before, 0x3)) { + ksft_print_msg("FAIL: thread_a initial affinity incorrect\n"); + goto cleanup_threads; + } + + if (!cpu_set_equal(&affinity_b_before, 0x2)) { + ksft_print_msg("FAIL: thread_b initial affinity incorrect\n"); + goto cleanup_threads; + } + + /* Disable cpuset controller - this should trigger affinity update */ + if (cg_write(parent, "cgroup.subtree_control", "-cpuset")) + goto cleanup_threads; + + /* Signal threads to save their final affinity and exit */ + pthread_mutex_lock(&test_mutex); + test_phase =3D AFFINITY_CONTROLLER_DISABLED; + pthread_cond_broadcast(&test_cond); + pthread_mutex_unlock(&test_mutex); + + pthread_join(thread_a, NULL); + pthread_join(thread_b, NULL); + + /* Verify thread affinities AFTER disabling controller */ + if (!cpu_set_equal(&affinity_a_after, 0x3)) { + ksft_print_msg("FAIL: thread_a final affinity incorrect\n"); + goto cleanup; + } + + if (!cpu_set_equal(&affinity_b_after, 0x3)) { + ksft_print_msg("FAIL: thread_b affinity did not expand to {0-1}\n"); + goto cleanup; + } + + ret =3D KSFT_PASS; + goto cleanup; + +cleanup_threads: + pthread_mutex_lock(&test_mutex); + test_phase =3D AFFINITY_COMPLETE; + pthread_cond_broadcast(&test_cond); + pthread_mutex_unlock(&test_mutex); + + if (thread_a_created) + pthread_join(thread_a, NULL); + if (thread_b_created) + pthread_join(thread_b, NULL); + +cleanup: + /* Move back to root before cleanup */ + cg_enter_current(root); + + cg_destroy(child_b); + free(child_b); + cg_destroy(child_a); + free(child_a); + cg_destroy(parent); + free(parent); + + return ret; +} + =20 #define T(x) { x, #x } struct cpuset_test { @@ -241,6 +483,7 @@ struct cpuset_test { T(test_cpuset_perms_object_allow), T(test_cpuset_perms_object_deny), T(test_cpuset_perms_subtree), + T(test_cpuset_affinity_on_controller_disable), }; #undef T =20 --=20 2.55.0