[PATCH] selftests/namespaces: Wait for thread exit via pidfd in active-ref tests

From: Ricardo B. Marlière (SUSE)

Date: Thu Oct 01 2026 - 08:29:57 EST


pthread_join() returns once exit_mm() wakes the CLEARTID futex, but the
exiting thread drops its namespace reference later, in
exit_nsproxy_namespaces(). The joining thread can therefore still see the
namespace as active, making the final open_by_handle_at() check in
thread_ns_inactive_after_exit and thread_ns_fd_keeps_active fail
intermittently.

Have the thread open a PIDFD_THREAD pidfd on itself and poll it before
pthread_join(). A pidfd becomes readable from exit_notify(), which runs
after exit_nsproxy_namespaces().

Fixes: 29f083c49982 ("selftests/namespace: first threaded active reference count test")
Fixes: ee861032381b ("selftests/namespace: second threaded active reference count test")
Assisted-by: LLM
Signed-off-by: Ricardo B. Marlière (SUSE) <ricardo@xxxxxxxxxxxx>
---
.../selftests/namespaces/ns_active_ref_test.c | 29 ++++++++++++++++++++++
1 file changed, 29 insertions(+)

diff --git a/tools/testing/selftests/namespaces/ns_active_ref_test.c b/tools/testing/selftests/namespaces/ns_active_ref_test.c
index 093268f0efaa..ab5ffe4de521 100644
--- a/tools/testing/selftests/namespaces/ns_active_ref_test.c
+++ b/tools/testing/selftests/namespaces/ns_active_ref_test.c
@@ -8,6 +8,7 @@
#include <stdlib.h>
#include <string.h>
#include <linux/nsfs.h>
+#include <poll.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/stat.h>
@@ -20,6 +21,10 @@
#include "../filesystems/utils.h"
#include "wrappers.h"

+#ifndef PIDFD_THREAD
+#define PIDFD_THREAD O_EXCL
+#endif
+
#ifndef FD_NSFS_ROOT
#define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */
#endif
@@ -2120,6 +2125,7 @@ TEST(ns_mixed_types_same_owner)
/* Thread test helpers and structures */
struct thread_ns_info {
__u64 ns_id;
+ int pidfd;
int pipefd;
int syncfd_read;
int syncfd_write;
@@ -2152,6 +2158,13 @@ static void *thread_create_namespace(void *arg)
return NULL;
}

+ /* Open a pidfd so the main thread can later poll it to detect actual thread exit. */
+ info->pidfd = syscall(__NR_pidfd_open, gettid(), PIDFD_THREAD);
+ if (info->pidfd < 0) {
+ info->exit_code = 6;
+ return NULL;
+ }
+
/* Send namespace ID to main thread */
if (write(info->pipefd, &info->ns_id, sizeof(info->ns_id)) != sizeof(info->ns_id)) {
info->exit_code = 4;
@@ -2169,6 +2182,20 @@ static void *thread_create_namespace(void *arg)
return NULL;
}

+/*
+ * pthread_join() returns once exit_mm() wakes the CLEARTID futex, before
+ * exit_nsproxy_namespaces() drops the namespace reference. A pidfd becomes
+ * readable only from exit_notify(), after that point.
+ */
+static int wait_thread_exited(int pidfd)
+{
+ struct pollfd pfd = { .fd = pidfd, .events = POLLIN };
+ int ret = poll(&pfd, 1, -1);
+
+ close(pidfd);
+ return ret;
+}
+
/*
* Test that namespace becomes inactive after thread exits.
* This verifies active reference counting works with threads, not just processes.
@@ -2234,6 +2261,7 @@ TEST(thread_ns_inactive_after_exit)
close(syncpipe[1]);

/* Wait for thread to exit */
+ ASSERT_EQ(wait_thread_exited(info.pidfd), 1);
ASSERT_EQ(pthread_join(thread, NULL), 0);
close(pipefd[0]);
close(pipefd[1]);
@@ -2317,6 +2345,7 @@ TEST(thread_ns_fd_keeps_active)
close(syncpipe[1]);

/* Wait for thread to exit */
+ ASSERT_EQ(wait_thread_exited(info.pidfd), 1);
pthread_join(thread, NULL);
close(pipefd[0]);
close(pipefd[1]);

---
base-commit: 551c722f40809618230001baccf219193e22fc5a
change-id: 20261001-selftests-namespaces-nsid_race-d65dac740e6b

Best regards,
--
Ricardo B. Marlière (SUSE) <ricardo@xxxxxxxxxxxx>