From: "Michal Koutný" <mkoutny@suse.com>
To: Waiman Long <longman@redhat.com>
Cc: Chen Ridong <chenridong@huaweicloud.com>,
Tejun Heo <tj@kernel.org>, Johannes Weiner <hannes@cmpxchg.org>,
Peter Zijlstra <peterz@infradead.org>,
cgroups@vger.kernel.org, linux-kernel@vger.kernel.org,
Aaron Tomlin <atomlin@atomlin.com>,
Guopeng Zhang <guopeng.zhang@linux.dev>
Subject: Re: [PATCH-next v5 0/6] cgroup/cpuset: Support multiple source/destination cpusets for cpuset_*attach()
Date: Wed, 24 Jun 2026 17:51:20 +0200 [thread overview]
Message-ID: <ajv79c9bTlrGThdF@localhost.localdomain> (raw)
In-Reply-To: <20260602023203.248077-1-longman@redhat.com>
[-- Attachment #1: Type: text/plain, Size: 9453 bytes --]
On Mon, Jun 01, 2026 at 10:31:57PM -0400, Waiman Long <longman@redhat.com> wrote:
> Patch 6 makes the necessary changes to enable the support of multiple
> source and destination cpusets by keeping all the source and destination
> cpusets found during task iterations in two singly linked lists for
> source and destination cpusets respectively.
Thanks for looking into this!
I've played with a coding assistant and produced the following selftest
(it (expectedly) fails on my machine), feel free to include in the
series (if it validates the fix).
-- 8< --
From ed4e6cf91413bb4b64befb1c15412c8cfd205d73 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Michal=20Koutn=C3=BD?= <mkoutny@suse.com>
Date: Wed, 24 Jun 2026 16:39:30 +0200
Subject: [PATCH] selftests/cgroup: Add test for cpuset affinity on controller
disable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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=0-1
- Child A: cpuset.cpus=0-1
- Child B: cpuset.cpus=1 (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ý <mkoutny@suse.com>
---
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/selftests/cgroup/test_cpuset.c
index c5cf8b56ceb8f..1d72a199ca552 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
+#define _GNU_SOURCE
+#include <assert.h>
#include <linux/limits.h>
+#include <pthread.h>
+#include <sched.h>
#include <signal.h>
+#include <sys/syscall.h>
+#include <unistd.h>
#include "kselftest.h"
#include "cgroup_util.h"
@@ -232,6 +238,242 @@ static int test_cpuset_perms_subtree(const char *root)
return ret;
}
+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 = 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 = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t test_cond = PTHREAD_COND_INITIALIZER;
+static enum test_phase test_phase;
+
+static void *affinity_thread_fn(void *arg)
+{
+ struct thread_args *args = (struct thread_args *)arg;
+
+ if (cg_enter_current_thread(args->cgroup))
+ goto fail;
+
+ if (get_cpu_affinity(args->affinity_before) != 0)
+ goto fail;
+
+ pthread_mutex_lock(&test_mutex);
+ if (test_phase < args->ready_phase)
+ test_phase = 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) != 0)
+ goto fail;
+
+
+ return NULL;
+
+fail:
+ pthread_mutex_lock(&test_mutex);
+ test_phase = 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 cgroups
+ * don't get their affinity updated when the cpuset controller is disabled.
+ *
+ * Setup:
+ * - Create parent cgroup with cpuset.cpus=0-1
+ * - Create child A with cpuset.cpus=0-1
+ * - Create child B with cpuset.cpus=1
+ * - Place multithreaded process: group leader + thread_a in A, thread_b in 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 = NULL, *child_a = NULL, *child_b = NULL;
+ pthread_t thread_a, thread_b;
+ int thread_a_created = 0, thread_b_created = 0;
+ cpu_set_t affinity_a_before, affinity_a_after;
+ cpu_set_t affinity_b_before, affinity_b_after;
+ int ret = KSFT_FAIL;
+
+ parent = 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 = 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 = 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 = 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 = AFFINITY_SETUP;
+
+ struct thread_args args_a = {
+ .cgroup = child_a,
+ .affinity_before = &affinity_a_before,
+ .affinity_after = &affinity_a_after,
+ .ready_phase = AFFINITY_THREAD_A_READY,
+ };
+ if (pthread_create(&thread_a, NULL, affinity_thread_fn, &args_a))
+ goto cleanup;
+ thread_a_created = 1;
+
+ struct thread_args args_b = {
+ .cgroup = child_b,
+ .affinity_before = &affinity_b_before,
+ .affinity_after = &affinity_b_after,
+ .ready_phase = AFFINITY_THREADS_READY,
+ };
+ if (pthread_create(&thread_b, NULL, affinity_thread_fn, &args_b))
+ goto cleanup_threads;
+ thread_b_created = 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 == 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 = 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 = KSFT_PASS;
+ goto cleanup;
+
+cleanup_threads:
+ pthread_mutex_lock(&test_mutex);
+ test_phase = 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;
+}
+
#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
--
2.54.0
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 265 bytes --]
prev parent reply other threads:[~2026-06-24 15:51 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-02 2:31 [PATCH-next v5 0/6] cgroup/cpuset: Support multiple source/destination cpusets for cpuset_*attach() Waiman Long
2026-06-02 2:31 ` [PATCH-next v5 1/6] cgroup/cpuset: Fix node inconsistencies between cpuset_update_tasks_nodemask() and cpuset_attach() Waiman Long
2026-06-02 13:37 ` Ridong Chen
2026-06-02 18:43 ` Waiman Long
2026-06-02 2:31 ` [PATCH-next v5 2/6] cgroup/cpuset: Add a cpuset_reserve_dl_bw() helper Waiman Long
2026-06-02 13:40 ` Ridong Chen
2026-06-02 2:32 ` [PATCH-next v5 3/6] cgroup/cpuset: Expand the scope of cpuset_can_attach_check() Waiman Long
2026-06-02 13:51 ` Ridong Chen
2026-06-02 2:32 ` [PATCH-next v5 4/6] cgroup/cpuset: Make cpuset_attach_old_cs track task group leaders Waiman Long
2026-06-02 13:58 ` Ridong Chen
2026-06-02 2:32 ` [PATCH-next v5 5/6] cgroup/cpuset: Move mpol_rebind_mm/cpuset_migrate_mm() calls inside cpuset_attach_task() Waiman Long
2026-06-02 2:32 ` [PATCH-next v5 6/6] cgroup/cpuset: Support multiple source/destination cpusets for cpuset_*attach() Waiman Long
2026-06-03 10:26 ` [PATCH] cgroup/cpuset: Support multiple source/destination cpusets using pids pattern Ridong Chen
2026-06-03 10:32 ` Ridong Chen
2026-06-03 18:47 ` Waiman Long
2026-06-05 7:35 ` Ridong Chen
2026-06-05 17:15 ` Waiman Long
2026-06-07 3:12 ` Ridong Chen
2026-06-08 18:49 ` Waiman Long
2026-06-11 6:17 ` Ridong Chen
2026-06-07 3:22 ` Ridong Chen
2026-06-24 15:45 ` [PATCH-next v5 6/6] cgroup/cpuset: Support multiple source/destination cpusets for cpuset_*attach() Michal Koutný
2026-06-24 15:51 ` Michal Koutný [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=ajv79c9bTlrGThdF@localhost.localdomain \
--to=mkoutny@suse.com \
--cc=atomlin@atomlin.com \
--cc=cgroups@vger.kernel.org \
--cc=chenridong@huaweicloud.com \
--cc=guopeng.zhang@linux.dev \
--cc=hannes@cmpxchg.org \
--cc=linux-kernel@vger.kernel.org \
--cc=longman@redhat.com \
--cc=peterz@infradead.org \
--cc=tj@kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.