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
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
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
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?
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
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.
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
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.
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
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
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
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
© 2016 - 2025 Red Hat, Inc.