[PATCH] selftests/futex: Retry the wake in futex_wait instead of sleeping

From: Mathieu Desnoyers

Date: Sun Nov 08 2026 - 09:25:23 EST


Each futex_wait test starts a waiter thread, which calls futex_wait()
with a 30ms timeout. The main thread sleeps 10ms, then expects
futex_wake() to wake one waiter. So the test assumes that the waiter
reaches futex_wait() within 10ms, and that the main thread runs again
within 30ms.

A busy machine breaks both. The built-in locktorture at times holds a
spinlock for 100ms with preemption off. With it, private_futex failed in
about 0.5% of the runs in 8-CPU VMs, on v7.4-rc1.
Timestamps showed two cases. The waiter started up to 75ms late, after
the wake. Or the main thread woke up to 105ms late, after the waiter
had timed out.

Fix this by retrying futex_wake() every millisecond until it wakes the
waiter, for up to 5s. Give the waiter a timeout of 10s, so that it
outlasts the retries. The tests then wait for the waiter, instead of
assuming when it runs.

With this patch, private_futex passed 6000 runs of 6000 in the same VMs,
and the three tests 2000 runs of 2000.

Fixes: c3d128581f64 ("selftests: futex: Add futex wait test")
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
---
.../selftests/futex/functional/futex_wait.c | 39 ++++++++++++-------
1 file changed, 26 insertions(+), 13 deletions(-)

diff --git a/tools/testing/selftests/futex/functional/futex_wait.c b/tools/testing/selftests/futex/functional/futex_wait.c
index 5292f5d2c3b5..e7ebc7d8be1d 100644
--- a/tools/testing/selftests/futex/functional/futex_wait.c
+++ b/tools/testing/selftests/futex/functional/futex_wait.c
@@ -14,8 +14,9 @@
#include "futextest.h"
#include "kselftest_harness.h"

-#define timeout_ns 30000000
-#define WAKE_WAIT_US 10000
+#define WAIT_TIMEOUT_S 10
+#define WAKE_RETRY_US 1000
+#define WAKE_TRIES 5000
#define SHM_PATH "futex_shm_file"

void *futex;
@@ -32,8 +33,8 @@ static void *waiterfn(void *arg)
struct timespec to;
int res;

- to.tv_sec = 0;
- to.tv_nsec = timeout_ns;
+ to.tv_sec = WAIT_TIMEOUT_S;
+ to.tv_nsec = 0;

res = futex_wait(futex, 0, &to, args->flags);
if (res) {
@@ -45,6 +46,24 @@ static void *waiterfn(void *arg)
return NULL;
}

+/*
+ * Wake the waiter of @uaddr. The waiter thread may not have reached
+ * futex_wait() yet, so retry until futex_wake() wakes it, for up to
+ * WAKE_TRIES times WAKE_RETRY_US. Return what futex_wake() last returned.
+ */
+static int wake_waiter(void *uaddr, unsigned int flags)
+{
+ int i, res = 0;
+
+ for (i = 0; i < WAKE_TRIES; i++) {
+ res = futex_wake(uaddr, 1, flags);
+ if (res)
+ break;
+ usleep(WAKE_RETRY_US);
+ }
+ return res;
+}
+
TEST(private_futex)
{
struct waiter_args *args = malloc(sizeof(*args));
@@ -61,10 +80,8 @@ TEST(private_futex)
ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, args), 0)
TH_LOG("pthread_create failed");

- usleep(WAKE_WAIT_US);
-
TH_LOG("Calling private futex_wake on futex: %p", futex);
- res = futex_wake(futex, 1, FUTEX_PRIVATE_FLAG);
+ res = wake_waiter(futex, FUTEX_PRIVATE_FLAG);
EXPECT_EQ(res, 1)
TH_LOG("futex_wake private returned: %d %s", res, res < 0 ? strerror(errno) : "");

@@ -106,10 +123,8 @@ TEST(anon_page)
ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, args), 0)
TH_LOG("pthread_create failed");

- usleep(WAKE_WAIT_US);
-
TH_LOG("Calling shared (page anon) futex_wake on futex: %p", futex);
- res = futex_wake(futex, 1, 0);
+ res = wake_waiter(futex, 0);
EXPECT_EQ(res, 1) {
TH_LOG("futex_wake shared (page anon) returned: %d %s",
res, res < 0 ? strerror(errno) : "");
@@ -161,10 +176,8 @@ TEST(file_backed)
ASSERT_EQ(pthread_create(&waiter, NULL, waiterfn, args), 0)
TH_LOG("pthread_create failed");

- usleep(WAKE_WAIT_US);
-
TH_LOG("Calling shared (file backed) futex_wake on futex: %p", futex);
- res = futex_wake(shm, 1, 0);
+ res = wake_waiter(shm, 0);
EXPECT_EQ(res, 1) {
TH_LOG("futex_wake shared (file backed) returned: %d %s",
res, res < 0 ? strerror(errno) : "");

base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
--
2.47.3