[PATCH v12 20/21] selftests/futex: Add futex_priv_hash

Sebastian Andrzej Siewior posted 21 patches 8 months ago
[PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Sebastian Andrzej Siewior 8 months ago
Test the basic functionality of the private hash:
- Upon start, with no threads there is no private hash.
- The first thread initializes the private hash.
- More than four threads will increase the size of the private hash if
  the system has more than 16 CPUs online.
- Once the user sets the size of private hash, auto scaling is disabled.
- The user is only allowed to use numbers to the power of two.
- The user may request the global or make the hash immutable.
- Once the global hash has been set or the hash has been made immutable,
  further changes are not allowed.
- Futex operations should work the whole time. It must be possible to
  hold a lock, such a PI initialised mutex, during the resize operation.

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
 .../selftests/futex/functional/.gitignore     |   5 +-
 .../selftests/futex/functional/Makefile       |   1 +
 .../futex/functional/futex_priv_hash.c        | 315 ++++++++++++++++++
 .../testing/selftests/futex/functional/run.sh |   4 +
 4 files changed, 323 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/futex/functional/futex_priv_hash.c

diff --git a/tools/testing/selftests/futex/functional/.gitignore b/tools/testing/selftests/futex/functional/.gitignore
index fbcbdb6963b3a..d37ae7c6e879e 100644
--- a/tools/testing/selftests/futex/functional/.gitignore
+++ b/tools/testing/selftests/futex/functional/.gitignore
@@ -1,11 +1,12 @@
 # SPDX-License-Identifier: GPL-2.0-only
+futex_priv_hash
+futex_requeue
 futex_requeue_pi
 futex_requeue_pi_mismatched_ops
 futex_requeue_pi_signal_restart
+futex_wait
 futex_wait_private_mapped_file
 futex_wait_timeout
 futex_wait_uninitialized_heap
 futex_wait_wouldblock
-futex_wait
-futex_requeue
 futex_waitv
diff --git a/tools/testing/selftests/futex/functional/Makefile b/tools/testing/selftests/futex/functional/Makefile
index f79f9bac7918b..67d9e16d8a1f8 100644
--- a/tools/testing/selftests/futex/functional/Makefile
+++ b/tools/testing/selftests/futex/functional/Makefile
@@ -17,6 +17,7 @@ TEST_GEN_PROGS := \
 	futex_wait_private_mapped_file \
 	futex_wait \
 	futex_requeue \
+	futex_priv_hash \
 	futex_waitv
 
 TEST_PROGS := run.sh
diff --git a/tools/testing/selftests/futex/functional/futex_priv_hash.c b/tools/testing/selftests/futex/functional/futex_priv_hash.c
new file mode 100644
index 0000000000000..4d37650baa192
--- /dev/null
+++ b/tools/testing/selftests/futex/functional/futex_priv_hash.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <linux/prctl.h>
+#include <sys/prctl.h>
+
+#include "logging.h"
+
+#define MAX_THREADS	64
+
+static pthread_barrier_t barrier_main;
+static pthread_mutex_t global_lock;
+static pthread_t threads[MAX_THREADS];
+static int counter;
+
+#ifndef PR_FUTEX_HASH
+#define PR_FUTEX_HASH			78
+# define PR_FUTEX_HASH_SET_SLOTS	1
+# define PR_FUTEX_HASH_GET_SLOTS	2
+# define PR_FUTEX_HASH_GET_IMMUTABLE	3
+#endif
+
+static int futex_hash_slots_set(unsigned int slots, int immutable)
+{
+	return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, immutable);
+}
+
+static int futex_hash_slots_get(void)
+{
+	return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS);
+}
+
+static int futex_hash_immutable_get(void)
+{
+	return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_IMMUTABLE);
+}
+
+static void futex_hash_slots_set_verify(int slots)
+{
+	int ret;
+
+	ret = futex_hash_slots_set(slots, 0);
+	if (ret != 0) {
+		error("Failed to set slots to %d\n", errno, slots);
+		exit(1);
+	}
+	ret = futex_hash_slots_get();
+	if (ret != slots) {
+		error("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d\n",
+		       errno, slots, ret);
+		exit(1);
+	}
+}
+
+static void futex_hash_slots_set_must_fail(int slots, int immutable)
+{
+	int ret;
+
+	ret = futex_hash_slots_set(slots, immutable);
+	if (ret < 0)
+		return;
+
+	fail("futex_hash_slots_set(%d, %d) expected to fail but succeeded.\n",
+	       slots, immutable);
+	exit(1);
+}
+
+static void *thread_return_fn(void *arg)
+{
+	return NULL;
+}
+
+static void *thread_lock_fn(void *arg)
+{
+	pthread_barrier_wait(&barrier_main);
+
+	pthread_mutex_lock(&global_lock);
+	counter++;
+	usleep(20);
+	pthread_mutex_unlock(&global_lock);
+	return NULL;
+}
+
+static void create_max_threads(void *(*thread_fn)(void *))
+{
+	int i, ret;
+
+	for (i = 0; i < MAX_THREADS; i++) {
+		ret = pthread_create(&threads[i], NULL, thread_fn, NULL);
+		if (ret) {
+			error("pthread_create failed\n", errno);
+			exit(1);
+		}
+	}
+}
+
+static void join_max_threads(void)
+{
+	int i, ret;
+
+	for (i = 0; i < MAX_THREADS; i++) {
+		ret = pthread_join(threads[i], NULL);
+		if (ret) {
+			error("pthread_join failed for thread %d\n", errno, i);
+			exit(1);
+		}
+	}
+}
+
+static void usage(char *prog)
+{
+	printf("Usage: %s\n", prog);
+	printf("  -c    Use color\n");
+	printf("  -g    Test global hash instead intead local immutable \n");
+	printf("  -h    Display this help message\n");
+	printf("  -v L  Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
+	       VQUIET, VCRITICAL, VINFO);
+}
+
+int main(int argc, char *argv[])
+{
+	int futex_slots1, futex_slotsn, online_cpus;
+	pthread_mutexattr_t mutex_attr_pi;
+	int use_global_hash = 0;
+	int ret;
+	char c;
+
+	while ((c = getopt(argc, argv, "cghv:")) != -1) {
+		switch (c) {
+		case 'c':
+			log_color(1);
+			break;
+		case 'g':
+			use_global_hash = 1;
+			break;
+		case 'h':
+			usage(basename(argv[0]));
+			exit(0);
+			break;
+		case 'v':
+			log_verbosity(atoi(optarg));
+			break;
+		default:
+			usage(basename(argv[0]));
+			exit(1);
+		}
+	}
+
+
+	ret = pthread_mutexattr_init(&mutex_attr_pi);
+	ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT);
+	ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi);
+	if (ret != 0) {
+		fail("Failed to initialize pthread mutex.\n");
+		return 1;
+	}
+
+	/* First thread, expect to be 0, not yet initialized */
+	ret = futex_hash_slots_get();
+	if (ret != 0) {
+		error("futex_hash_slots_get() failed: %d\n", errno, ret);
+		return 1;
+	}
+	ret = futex_hash_immutable_get();
+	if (ret != 0) {
+		error("futex_hash_immutable_get() failed: %d\n", errno, ret);
+		return 1;
+	}
+
+	ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL);
+	if (ret != 0) {
+		error("pthread_create() failed: %d\n", errno, ret);
+		return 1;
+	}
+	ret = pthread_join(threads[0], NULL);
+	if (ret != 0) {
+		error("pthread_join() failed: %d\n", errno, ret);
+		return 1;
+	}
+	/* First thread, has to initialiaze private hash */
+	futex_slots1 = futex_hash_slots_get();
+	if (futex_slots1 <= 0) {
+		fail("Expected > 0 hash buckets, got: %d\n", futex_slots1);
+		return 1;
+	}
+
+	online_cpus = sysconf(_SC_NPROCESSORS_ONLN);
+	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);
+	if (ret != 0) {
+		error("pthread_barrier_init failed.\n", errno);
+		return 1;
+	}
+
+	ret = pthread_mutex_lock(&global_lock);
+	if (ret != 0) {
+		error("pthread_mutex_lock failed.\n", errno);
+		return 1;
+	}
+
+	counter = 0;
+	create_max_threads(thread_lock_fn);
+	pthread_barrier_wait(&barrier_main);
+
+	/*
+	 * The current default size of hash buckets is 16. The auto increase
+	 * works only if more than 16 CPUs are available.
+	 */
+	if (online_cpus > 16) {
+		futex_slotsn = futex_hash_slots_get();
+		if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) {
+			fail("Expected increase of hash buckets but got: %d -> %d\n",
+			      futex_slots1, futex_slotsn);
+			info("Online CPUs: %d\n", online_cpus);
+			return 1;
+		}
+	}
+	ret = pthread_mutex_unlock(&global_lock);
+
+	/* Once the user changes it, it has to be what is set */
+	futex_hash_slots_set_verify(2);
+	futex_hash_slots_set_verify(4);
+	futex_hash_slots_set_verify(8);
+	futex_hash_slots_set_verify(32);
+	futex_hash_slots_set_verify(16);
+
+	ret = futex_hash_slots_set(15, 0);
+	if (ret >= 0) {
+		fail("Expected to fail with 15 slots but succeeded: %d.\n", ret);
+		return 1;
+	}
+	futex_hash_slots_set_verify(2);
+	join_max_threads();
+	if (counter != MAX_THREADS) {
+		fail("Expected thread counter at %d but is %d\n",
+		       MAX_THREADS, counter);
+		return 1;
+	}
+	counter = 0;
+	/* Once the user set something, auto reisze must be disabled */
+	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
+
+	create_max_threads(thread_lock_fn);
+	join_max_threads();
+
+	ret = futex_hash_slots_get();
+	if (ret != 2) {
+		printf("Expected 2 slots, no auto-resize, got %d\n", ret);
+		return 1;
+	}
+
+	futex_hash_slots_set_must_fail(1 << 29, 0);
+
+	/*
+	 * Once the private hash has been made immutable or global hash has been requested,
+	 * then this requested can not be undone.
+	 */
+	if (use_global_hash) {
+		ret = futex_hash_slots_set(0, 0);
+		if (ret != 0) {
+			printf("Can't request global hash: %m\n");
+			return 1;
+		}
+	} else {
+		ret = futex_hash_slots_set(4, 1);
+		if (ret != 0) {
+			printf("Immutable resize to 4 failed: %m\n");
+			return 1;
+		}
+	}
+
+	futex_hash_slots_set_must_fail(4, 0);
+	futex_hash_slots_set_must_fail(4, 1);
+	futex_hash_slots_set_must_fail(8, 0);
+	futex_hash_slots_set_must_fail(8, 1);
+	futex_hash_slots_set_must_fail(0, 1);
+	futex_hash_slots_set_must_fail(6, 1);
+
+	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
+	if (ret != 0) {
+		error("pthread_barrier_init failed.\n", errno);
+		return 1;
+	}
+	create_max_threads(thread_lock_fn);
+	join_max_threads();
+
+	ret = futex_hash_slots_get();
+	if (use_global_hash) {
+		if (ret != 0) {
+			error("Expected global hash, got %d\n", errno, ret);
+			return 1;
+		}
+	} else {
+		if (ret != 4) {
+			error("Expected 4 slots, no auto-resize, got %d\n", errno, ret);
+			return 1;
+		}
+	}
+
+	ret = futex_hash_immutable_get();
+	if (ret != 1) {
+		fail("Expected immutable private hash, got %d\n", ret);
+		return 1;
+	}
+	return 0;
+}
diff --git a/tools/testing/selftests/futex/functional/run.sh b/tools/testing/selftests/futex/functional/run.sh
index 5ccd599da6c30..f0f0d2b683d7e 100755
--- a/tools/testing/selftests/futex/functional/run.sh
+++ b/tools/testing/selftests/futex/functional/run.sh
@@ -82,3 +82,7 @@ echo
 
 echo
 ./futex_waitv $COLOR
+
+echo
+./futex_priv_hash $COLOR
+./futex_priv_hash -g $COLOR
-- 
2.49.0
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Mark Brown 6 months, 3 weeks ago
On Wed, Apr 16, 2025 at 06:29:20PM +0200, Sebastian Andrzej Siewior wrote:
> Test the basic functionality of the private hash:
> - Upon start, with no threads there is no private hash.
> - The first thread initializes the private hash.
> - More than four threads will increase the size of the private hash if
>   the system has more than 16 CPUs online.

This newly added test is not running successfully on arm64, it looks
like it's just a straightforward integration issue:

# Usage: futex_priv_hash
#   -c    Use color
#   -g    Test global hash instead intead local immutable 
#   -h    Display this help message
#   -v L  Verbosity level: 0=QUIET 1=CRITICAL 2=INFO
# Usage: futex_priv_hash
#   -c    Use color
#   -g    Test global hash instead intead local immutable 
#   -h    Display this help message
#   -v L  Verbosity level: 0=QUIET 1=CRITICAL 2=INFO
not ok 1 selftests: futex: run.sh # exit=1

Full log:

   https://lava.sirena.org.uk/scheduler/job/1414260#L9910
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Sebastian Andrzej Siewior 6 months, 3 weeks ago
On 2025-05-27 12:28:00 [+0100], Mark Brown wrote:
> On Wed, Apr 16, 2025 at 06:29:20PM +0200, Sebastian Andrzej Siewior wrote:
> > Test the basic functionality of the private hash:
> > - Upon start, with no threads there is no private hash.
> > - The first thread initializes the private hash.
> > - More than four threads will increase the size of the private hash if
> >   the system has more than 16 CPUs online.
> 
> This newly added test is not running successfully on arm64, it looks
> like it's just a straightforward integration issue:
> 
> # Usage: futex_priv_hash
> #   -c    Use color
> #   -g    Test global hash instead intead local immutable 
> #   -h    Display this help message
> #   -v L  Verbosity level: 0=QUIET 1=CRITICAL 2=INFO
> # Usage: futex_priv_hash
> #   -c    Use color
> #   -g    Test global hash instead intead local immutable 
> #   -h    Display this help message
> #   -v L  Verbosity level: 0=QUIET 1=CRITICAL 2=INFO
> not ok 1 selftests: futex: run.sh # exit=1

That is odd. If I run ./run.sh then it passes. I tried it with forcing
COLOR=-c and without it. This is the only option that is passed. That is on
x86 however but I doubt arm64 is doing anything special here.

A bit puzzled here.

> Full log:
> 
>    https://lava.sirena.org.uk/scheduler/job/1414260#L9910

Sebastian
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Mark Brown 6 months, 3 weeks ago
On Tue, May 27, 2025 at 02:23:32PM +0200, Sebastian Andrzej Siewior wrote:
> On 2025-05-27 12:28:00 [+0100], Mark Brown wrote:

> > This newly added test is not running successfully on arm64, it looks
> > like it's just a straightforward integration issue:

> > # Usage: futex_priv_hash
> > #   -c    Use color
> > #   -g    Test global hash instead intead local immutable 
> > #   -h    Display this help message
> > #   -v L  Verbosity level: 0=QUIET 1=CRITICAL 2=INFO

> That is odd. If I run ./run.sh then it passes. I tried it with forcing
> COLOR=-c and without it. This is the only option that is passed. That is on
> x86 however but I doubt arm64 is doing anything special here.

> A bit puzzled here.

Yeah, I was a bit confused as well.  This is running with an installed
copy of the selftests and IIRC the build is out of tree so it's possible
something is different with that path compared to what you're doing?
It's a common source of problems?
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Sebastian Andrzej Siewior 6 months, 3 weeks ago
On 2025-05-27 13:35:50 [+0100], Mark Brown wrote:
> On Tue, May 27, 2025 at 02:23:32PM +0200, Sebastian Andrzej Siewior wrote:
> > On 2025-05-27 12:28:00 [+0100], Mark Brown wrote:
> 
> > > This newly added test is not running successfully on arm64, it looks
> > > like it's just a straightforward integration issue:
> 
> > > # Usage: futex_priv_hash
> > > #   -c    Use color
> > > #   -g    Test global hash instead intead local immutable 
> > > #   -h    Display this help message
> > > #   -v L  Verbosity level: 0=QUIET 1=CRITICAL 2=INFO
> 
> > That is odd. If I run ./run.sh then it passes. I tried it with forcing
> > COLOR=-c and without it. This is the only option that is passed. That is on
> > x86 however but I doubt arm64 is doing anything special here.
> 
> > A bit puzzled here.
> 
> Yeah, I was a bit confused as well.  This is running with an installed
> copy of the selftests and IIRC the build is out of tree so it's possible
> something is different with that path compared to what you're doing?
> It's a common source of problems?

It shouldn't be. The test is self-contained and as long as the run.sh
script invokes all is good.  I don't see what futex_priv_hash might be
is doing different compared to the previous test.

I just noticed that after the two futex_priv_hash invocations there
should be one futex_numa_mpol invocation. That one is missing in the
output. Any idea why?

Sebastian
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Mark Brown 6 months, 3 weeks ago
On Tue, May 27, 2025 at 02:43:27PM +0200, Sebastian Andrzej Siewior wrote:
> On 2025-05-27 13:35:50 [+0100], Mark Brown wrote:

> > Yeah, I was a bit confused as well.  This is running with an installed
> > copy of the selftests and IIRC the build is out of tree so it's possible
> > something is different with that path compared to what you're doing?
> > It's a common source of problems?

> It shouldn't be. The test is self-contained and as long as the run.sh
> script invokes all is good.  I don't see what futex_priv_hash might be
> is doing different compared to the previous test.

> I just noticed that after the two futex_priv_hash invocations there
> should be one futex_numa_mpol invocation. That one is missing in the
> output. Any idea why?

I'm not seeing that test being built or in the binary:

   https://builds.sirena.org.uk/cda95faef7bcf26ba3f54c3cddce66d50116d146/arm64/defconfig/build.log
   https://builds.sirena.org.uk/cda95faef7bcf26ba3f54c3cddce66d50116d146/arm64/defconfig/kselftest.tar.xz

(note that this is the specific commit that I'm replying to the patch
for, not -next.)  It looks like it's something's getting mistbuilt or
there's some logic bug with the argument parsing, if I run the binary
with -h it exits with return code 0 rather than 1.
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Sebastian Andrzej Siewior 6 months, 3 weeks ago
On 2025-05-27 13:59:38 [+0100], Mark Brown wrote:
> I'm not seeing that test being built or in the binary:
> 
>    https://builds.sirena.org.uk/cda95faef7bcf26ba3f54c3cddce66d50116d146/arm64/defconfig/build.log
>    https://builds.sirena.org.uk/cda95faef7bcf26ba3f54c3cddce66d50116d146/arm64/defconfig/kselftest.tar.xz
> 
> (note that this is the specific commit that I'm replying to the patch

Ach, okay. I assumed you had the master branch as of today. The whole
KTAP/ machine readable output was added later.

> for, not -next.)  It looks like it's something's getting mistbuilt or
> there's some logic bug with the argument parsing, if I run the binary
> with -h it exits with return code 0 rather than 1.

I copied the logic from the other tests in that folder. If you set -h (a
valid argument) then it exits with 0. If you an invalid argument it
exits with 1.

But now that I start the binary myself, it ends the same way. This
cures it:

diff --git a/tools/testing/selftests/futex/functional/futex_priv_hash.c b/tools/testing/selftests/futex/functional/futex_priv_hash.c
index 2dca18fefedcd..24a92dc94eb86 100644
--- a/tools/testing/selftests/futex/functional/futex_priv_hash.c
+++ b/tools/testing/selftests/futex/functional/futex_priv_hash.c
@@ -130,7 +130,7 @@ int main(int argc, char *argv[])
 	pthread_mutexattr_t mutex_attr_pi;
 	int use_global_hash = 0;
 	int ret;
-	char c;
+	int c;
 
 	while ((c = getopt(argc, argv, "cghv:")) != -1) {
 		switch (c) {

Sebastian
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Mark Brown 6 months, 3 weeks ago
On Tue, May 27, 2025 at 03:25:33PM +0200, Sebastian Andrzej Siewior wrote:
> On 2025-05-27 13:59:38 [+0100], Mark Brown wrote:

> >    https://builds.sirena.org.uk/cda95faef7bcf26ba3f54c3cddce66d50116d146/arm64/defconfig/build.log
> >    https://builds.sirena.org.uk/cda95faef7bcf26ba3f54c3cddce66d50116d146/arm64/defconfig/kselftest.tar.xz

> > (note that this is the specific commit that I'm replying to the patch

> Ach, okay. I assumed you had the master branch as of today. The whole
> KTAP/ machine readable output was added later.



> > for, not -next.)  It looks like it's something's getting mistbuilt or
> > there's some logic bug with the argument parsing, if I run the binary
> > with -h it exits with return code 0 rather than 1.

> I copied the logic from the other tests in that folder. If you set -h (a
> valid argument) then it exits with 0. If you an invalid argument it
> exits with 1.

Yeah, so it was actually parsing arguments.

> But now that I start the binary myself, it ends the same way. This
> cures it:

>  	int ret;
> -	char c;
> +	int c;
>  
>  	while ((c = getopt(argc, argv, "cghv:")) != -1) {

Ah, yes - that'd do it.  Looking at the other tests there they do have c
as int.
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Sebastian Andrzej Siewior 6 months, 3 weeks ago
On 2025-05-27 14:40:22 [+0100], Mark Brown wrote:
> >  	int ret;
> > -	char c;
> > +	int c;
> >  
> >  	while ((c = getopt(argc, argv, "cghv:")) != -1) {
> 
> Ah, yes - that'd do it.  Looking at the other tests there they do have c
> as int.

And in the mpol. I'm going to send a patch later…
Thank you.

Sebastian
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by André Almeida 7 months, 1 week ago
Hi Sebastian,

Thank you for adding a selftest for the new uAPI. The recent futex 
selftests accepted uses the kselftest helpers in a different way than 
the way you have used. I've attached a diff to exemplify how I would 
write this selftest. The advantage is to have a TAP output that can be 
easier used with automated testing, and that would not stop when the 
first test fails.

Em 16/04/2025 13:29, Sebastian Andrzej Siewior escreveu:
> Test the basic functionality of the private hash:
> - Upon start, with no threads there is no private hash.
> - The first thread initializes the private hash.
> - More than four threads will increase the size of the private hash if
>    the system has more than 16 CPUs online.
> - Once the user sets the size of private hash, auto scaling is disabled.
> - The user is only allowed to use numbers to the power of two.
> - The user may request the global or make the hash immutable.
> - Once the global hash has been set or the hash has been made immutable,
>    further changes are not allowed.
> - Futex operations should work the whole time. It must be possible to
>    hold a lock, such a PI initialised mutex, during the resize operation.
> 
> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> ---

-- >8 --

---
  .../futex/functional/futex_priv_hash.c        | 31 ++++++++++++-------
  1 file changed, 19 insertions(+), 12 deletions(-)

diff --git a/tools/testing/selftests/futex/functional/futex_priv_hash.c 
b/tools/testing/selftests/futex/functional/futex_priv_hash.c
index 4d37650baa19..33fa9ad11d69 100644
--- a/tools/testing/selftests/futex/functional/futex_priv_hash.c
+++ b/tools/testing/selftests/futex/functional/futex_priv_hash.c
@@ -51,15 +51,17 @@ static void futex_hash_slots_set_verify(int slots)

  	ret = futex_hash_slots_set(slots, 0);
  	if (ret != 0) {
-		error("Failed to set slots to %d\n", errno, slots);
-		exit(1);
+		ksft_test_result_fail("Failed to set slots to %d: %s\n", slots, 
strerror(errno));
+		return;
  	}
  	ret = futex_hash_slots_get();
  	if (ret != slots) {
-		error("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d\n",
-		       errno, slots, ret);
-		exit(1);
+		ksft_test_result_fail("Set %d slots but PR_FUTEX_HASH_GET_SLOTS 
returns: %d\n",
+		       		      slots, ret);
+		return;
  	}
+
+	ksft_test_result_pass("futex_hash_slots_set() and get() succeeded (%d 
slots)\n", slots);
  }

  static void futex_hash_slots_set_must_fail(int slots, int immutable)
@@ -67,12 +69,14 @@ static void futex_hash_slots_set_must_fail(int 
slots, int immutable)
  	int ret;

  	ret = futex_hash_slots_set(slots, immutable);
-	if (ret < 0)
+	if (ret < 0) {
+		ksft_test_result_pass("invalid futex_hash_slots_set(%d, %d) 
succeeded.\n",
+				slots, immutable);
  		return;
+	}

-	fail("futex_hash_slots_set(%d, %d) expected to fail but succeeded.\n",
+	ksft_test_result_fail("futex_hash_slots_set(%d, %d) expected to fail 
but succeeded.\n",
  	       slots, immutable);
-	exit(1);
  }

  static void *thread_return_fn(void *arg)
@@ -156,6 +160,8 @@ int main(int argc, char *argv[])
  		}
  	}

+	ksft_print_header();
+	ksft_set_plan(13);

  	ret = pthread_mutexattr_init(&mutex_attr_pi);
  	ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, 
PTHREAD_PRIO_INHERIT);
@@ -235,13 +241,13 @@ int main(int argc, char *argv[])

  	ret = futex_hash_slots_set(15, 0);
  	if (ret >= 0) {
-		fail("Expected to fail with 15 slots but succeeded: %d.\n", ret);
+		ksft_test_result_fail("Expected to fail with 15 slots but succeeded: 
%d.\n", ret);
  		return 1;
  	}
  	futex_hash_slots_set_verify(2);
  	join_max_threads();
  	if (counter != MAX_THREADS) {
-		fail("Expected thread counter at %d but is %d\n",
+		ksft_test_result_fail("Expected thread counter at %d but is %d\n",
  		       MAX_THREADS, counter);
  		return 1;
  	}
@@ -254,8 +260,7 @@ int main(int argc, char *argv[])

  	ret = futex_hash_slots_get();
  	if (ret != 2) {
-		printf("Expected 2 slots, no auto-resize, got %d\n", ret);
-		return 1;
+		ksft_test_result_fail("Expected 2 slots, no auto-resize, got %d\n", ret);
  	}

  	futex_hash_slots_set_must_fail(1 << 29, 0);
@@ -311,5 +316,7 @@ int main(int argc, char *argv[])
  		fail("Expected immutable private hash, got %d\n", ret);
  		return 1;
  	}
+
+	ksft_print_cnts();
  	return 0;
  }
-- 
2.49.0
Re: [PATCH v12 20/21] selftests/futex: Add futex_priv_hash
Posted by Sebastian Andrzej Siewior 7 months ago
On 2025-05-09 18:22:18 [-0300], André Almeida wrote:
> Hi Sebastian,
Hi,

> Thank you for adding a selftest for the new uAPI. The recent futex selftests
> accepted uses the kselftest helpers in a different way than the way you have
> used. I've attached a diff to exemplify how I would write this selftest. The
> advantage is to have a TAP output that can be easier used with automated
> testing, and that would not stop when the first test fails.

It copied somewhere from existing ones. I think. Let me try to adapt
here…

Sebastian
[tip: locking/futex] selftests/futex: Add futex_priv_hash
Posted by tip-bot2 for Sebastian Andrzej Siewior 7 months, 2 weeks ago
The following commit has been merged into the locking/futex branch of tip:

Commit-ID:     cda95faef7bcf26ba3f54c3cddce66d50116d146
Gitweb:        https://git.kernel.org/tip/cda95faef7bcf26ba3f54c3cddce66d50116d146
Author:        Sebastian Andrzej Siewior <bigeasy@linutronix.de>
AuthorDate:    Wed, 16 Apr 2025 18:29:20 +02:00
Committer:     Peter Zijlstra <peterz@infradead.org>
CommitterDate: Sat, 03 May 2025 12:02:10 +02:00

selftests/futex: Add futex_priv_hash

Test the basic functionality of the private hash:
- Upon start, with no threads there is no private hash.
- The first thread initializes the private hash.
- More than four threads will increase the size of the private hash if
  the system has more than 16 CPUs online.
- Once the user sets the size of private hash, auto scaling is disabled.
- The user is only allowed to use numbers to the power of two.
- The user may request the global or make the hash immutable.
- Once the global hash has been set or the hash has been made immutable,
  further changes are not allowed.
- Futex operations should work the whole time. It must be possible to
  hold a lock, such a PI initialised mutex, during the resize operation.

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lore.kernel.org/r/20250416162921.513656-21-bigeasy@linutronix.de
---
 tools/testing/selftests/futex/functional/.gitignore        |   5 +-
 tools/testing/selftests/futex/functional/Makefile          |   1 +-
 tools/testing/selftests/futex/functional/futex_priv_hash.c | 315 +++++++-
 tools/testing/selftests/futex/functional/run.sh            |   4 +-
 4 files changed, 323 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/futex/functional/futex_priv_hash.c

diff --git a/tools/testing/selftests/futex/functional/.gitignore b/tools/testing/selftests/futex/functional/.gitignore
index fbcbdb6..d37ae7c 100644
--- a/tools/testing/selftests/futex/functional/.gitignore
+++ b/tools/testing/selftests/futex/functional/.gitignore
@@ -1,11 +1,12 @@
 # SPDX-License-Identifier: GPL-2.0-only
+futex_priv_hash
+futex_requeue
 futex_requeue_pi
 futex_requeue_pi_mismatched_ops
 futex_requeue_pi_signal_restart
+futex_wait
 futex_wait_private_mapped_file
 futex_wait_timeout
 futex_wait_uninitialized_heap
 futex_wait_wouldblock
-futex_wait
-futex_requeue
 futex_waitv
diff --git a/tools/testing/selftests/futex/functional/Makefile b/tools/testing/selftests/futex/functional/Makefile
index f79f9ba..67d9e16 100644
--- a/tools/testing/selftests/futex/functional/Makefile
+++ b/tools/testing/selftests/futex/functional/Makefile
@@ -17,6 +17,7 @@ TEST_GEN_PROGS := \
 	futex_wait_private_mapped_file \
 	futex_wait \
 	futex_requeue \
+	futex_priv_hash \
 	futex_waitv
 
 TEST_PROGS := run.sh
diff --git a/tools/testing/selftests/futex/functional/futex_priv_hash.c b/tools/testing/selftests/futex/functional/futex_priv_hash.c
new file mode 100644
index 0000000..4d37650
--- /dev/null
+++ b/tools/testing/selftests/futex/functional/futex_priv_hash.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2025 Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ */
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <linux/prctl.h>
+#include <sys/prctl.h>
+
+#include "logging.h"
+
+#define MAX_THREADS	64
+
+static pthread_barrier_t barrier_main;
+static pthread_mutex_t global_lock;
+static pthread_t threads[MAX_THREADS];
+static int counter;
+
+#ifndef PR_FUTEX_HASH
+#define PR_FUTEX_HASH			78
+# define PR_FUTEX_HASH_SET_SLOTS	1
+# define PR_FUTEX_HASH_GET_SLOTS	2
+# define PR_FUTEX_HASH_GET_IMMUTABLE	3
+#endif
+
+static int futex_hash_slots_set(unsigned int slots, int immutable)
+{
+	return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_SET_SLOTS, slots, immutable);
+}
+
+static int futex_hash_slots_get(void)
+{
+	return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_SLOTS);
+}
+
+static int futex_hash_immutable_get(void)
+{
+	return prctl(PR_FUTEX_HASH, PR_FUTEX_HASH_GET_IMMUTABLE);
+}
+
+static void futex_hash_slots_set_verify(int slots)
+{
+	int ret;
+
+	ret = futex_hash_slots_set(slots, 0);
+	if (ret != 0) {
+		error("Failed to set slots to %d\n", errno, slots);
+		exit(1);
+	}
+	ret = futex_hash_slots_get();
+	if (ret != slots) {
+		error("Set %d slots but PR_FUTEX_HASH_GET_SLOTS returns: %d\n",
+		       errno, slots, ret);
+		exit(1);
+	}
+}
+
+static void futex_hash_slots_set_must_fail(int slots, int immutable)
+{
+	int ret;
+
+	ret = futex_hash_slots_set(slots, immutable);
+	if (ret < 0)
+		return;
+
+	fail("futex_hash_slots_set(%d, %d) expected to fail but succeeded.\n",
+	       slots, immutable);
+	exit(1);
+}
+
+static void *thread_return_fn(void *arg)
+{
+	return NULL;
+}
+
+static void *thread_lock_fn(void *arg)
+{
+	pthread_barrier_wait(&barrier_main);
+
+	pthread_mutex_lock(&global_lock);
+	counter++;
+	usleep(20);
+	pthread_mutex_unlock(&global_lock);
+	return NULL;
+}
+
+static void create_max_threads(void *(*thread_fn)(void *))
+{
+	int i, ret;
+
+	for (i = 0; i < MAX_THREADS; i++) {
+		ret = pthread_create(&threads[i], NULL, thread_fn, NULL);
+		if (ret) {
+			error("pthread_create failed\n", errno);
+			exit(1);
+		}
+	}
+}
+
+static void join_max_threads(void)
+{
+	int i, ret;
+
+	for (i = 0; i < MAX_THREADS; i++) {
+		ret = pthread_join(threads[i], NULL);
+		if (ret) {
+			error("pthread_join failed for thread %d\n", errno, i);
+			exit(1);
+		}
+	}
+}
+
+static void usage(char *prog)
+{
+	printf("Usage: %s\n", prog);
+	printf("  -c    Use color\n");
+	printf("  -g    Test global hash instead intead local immutable \n");
+	printf("  -h    Display this help message\n");
+	printf("  -v L  Verbosity level: %d=QUIET %d=CRITICAL %d=INFO\n",
+	       VQUIET, VCRITICAL, VINFO);
+}
+
+int main(int argc, char *argv[])
+{
+	int futex_slots1, futex_slotsn, online_cpus;
+	pthread_mutexattr_t mutex_attr_pi;
+	int use_global_hash = 0;
+	int ret;
+	char c;
+
+	while ((c = getopt(argc, argv, "cghv:")) != -1) {
+		switch (c) {
+		case 'c':
+			log_color(1);
+			break;
+		case 'g':
+			use_global_hash = 1;
+			break;
+		case 'h':
+			usage(basename(argv[0]));
+			exit(0);
+			break;
+		case 'v':
+			log_verbosity(atoi(optarg));
+			break;
+		default:
+			usage(basename(argv[0]));
+			exit(1);
+		}
+	}
+
+
+	ret = pthread_mutexattr_init(&mutex_attr_pi);
+	ret |= pthread_mutexattr_setprotocol(&mutex_attr_pi, PTHREAD_PRIO_INHERIT);
+	ret |= pthread_mutex_init(&global_lock, &mutex_attr_pi);
+	if (ret != 0) {
+		fail("Failed to initialize pthread mutex.\n");
+		return 1;
+	}
+
+	/* First thread, expect to be 0, not yet initialized */
+	ret = futex_hash_slots_get();
+	if (ret != 0) {
+		error("futex_hash_slots_get() failed: %d\n", errno, ret);
+		return 1;
+	}
+	ret = futex_hash_immutable_get();
+	if (ret != 0) {
+		error("futex_hash_immutable_get() failed: %d\n", errno, ret);
+		return 1;
+	}
+
+	ret = pthread_create(&threads[0], NULL, thread_return_fn, NULL);
+	if (ret != 0) {
+		error("pthread_create() failed: %d\n", errno, ret);
+		return 1;
+	}
+	ret = pthread_join(threads[0], NULL);
+	if (ret != 0) {
+		error("pthread_join() failed: %d\n", errno, ret);
+		return 1;
+	}
+	/* First thread, has to initialiaze private hash */
+	futex_slots1 = futex_hash_slots_get();
+	if (futex_slots1 <= 0) {
+		fail("Expected > 0 hash buckets, got: %d\n", futex_slots1);
+		return 1;
+	}
+
+	online_cpus = sysconf(_SC_NPROCESSORS_ONLN);
+	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS + 1);
+	if (ret != 0) {
+		error("pthread_barrier_init failed.\n", errno);
+		return 1;
+	}
+
+	ret = pthread_mutex_lock(&global_lock);
+	if (ret != 0) {
+		error("pthread_mutex_lock failed.\n", errno);
+		return 1;
+	}
+
+	counter = 0;
+	create_max_threads(thread_lock_fn);
+	pthread_barrier_wait(&barrier_main);
+
+	/*
+	 * The current default size of hash buckets is 16. The auto increase
+	 * works only if more than 16 CPUs are available.
+	 */
+	if (online_cpus > 16) {
+		futex_slotsn = futex_hash_slots_get();
+		if (futex_slotsn < 0 || futex_slots1 == futex_slotsn) {
+			fail("Expected increase of hash buckets but got: %d -> %d\n",
+			      futex_slots1, futex_slotsn);
+			info("Online CPUs: %d\n", online_cpus);
+			return 1;
+		}
+	}
+	ret = pthread_mutex_unlock(&global_lock);
+
+	/* Once the user changes it, it has to be what is set */
+	futex_hash_slots_set_verify(2);
+	futex_hash_slots_set_verify(4);
+	futex_hash_slots_set_verify(8);
+	futex_hash_slots_set_verify(32);
+	futex_hash_slots_set_verify(16);
+
+	ret = futex_hash_slots_set(15, 0);
+	if (ret >= 0) {
+		fail("Expected to fail with 15 slots but succeeded: %d.\n", ret);
+		return 1;
+	}
+	futex_hash_slots_set_verify(2);
+	join_max_threads();
+	if (counter != MAX_THREADS) {
+		fail("Expected thread counter at %d but is %d\n",
+		       MAX_THREADS, counter);
+		return 1;
+	}
+	counter = 0;
+	/* Once the user set something, auto reisze must be disabled */
+	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
+
+	create_max_threads(thread_lock_fn);
+	join_max_threads();
+
+	ret = futex_hash_slots_get();
+	if (ret != 2) {
+		printf("Expected 2 slots, no auto-resize, got %d\n", ret);
+		return 1;
+	}
+
+	futex_hash_slots_set_must_fail(1 << 29, 0);
+
+	/*
+	 * Once the private hash has been made immutable or global hash has been requested,
+	 * then this requested can not be undone.
+	 */
+	if (use_global_hash) {
+		ret = futex_hash_slots_set(0, 0);
+		if (ret != 0) {
+			printf("Can't request global hash: %m\n");
+			return 1;
+		}
+	} else {
+		ret = futex_hash_slots_set(4, 1);
+		if (ret != 0) {
+			printf("Immutable resize to 4 failed: %m\n");
+			return 1;
+		}
+	}
+
+	futex_hash_slots_set_must_fail(4, 0);
+	futex_hash_slots_set_must_fail(4, 1);
+	futex_hash_slots_set_must_fail(8, 0);
+	futex_hash_slots_set_must_fail(8, 1);
+	futex_hash_slots_set_must_fail(0, 1);
+	futex_hash_slots_set_must_fail(6, 1);
+
+	ret = pthread_barrier_init(&barrier_main, NULL, MAX_THREADS);
+	if (ret != 0) {
+		error("pthread_barrier_init failed.\n", errno);
+		return 1;
+	}
+	create_max_threads(thread_lock_fn);
+	join_max_threads();
+
+	ret = futex_hash_slots_get();
+	if (use_global_hash) {
+		if (ret != 0) {
+			error("Expected global hash, got %d\n", errno, ret);
+			return 1;
+		}
+	} else {
+		if (ret != 4) {
+			error("Expected 4 slots, no auto-resize, got %d\n", errno, ret);
+			return 1;
+		}
+	}
+
+	ret = futex_hash_immutable_get();
+	if (ret != 1) {
+		fail("Expected immutable private hash, got %d\n", ret);
+		return 1;
+	}
+	return 0;
+}
diff --git a/tools/testing/selftests/futex/functional/run.sh b/tools/testing/selftests/futex/functional/run.sh
index 5ccd599..f0f0d2b 100755
--- a/tools/testing/selftests/futex/functional/run.sh
+++ b/tools/testing/selftests/futex/functional/run.sh
@@ -82,3 +82,7 @@ echo
 
 echo
 ./futex_waitv $COLOR
+
+echo
+./futex_priv_hash $COLOR
+./futex_priv_hash -g $COLOR