In tests with multiple concurrent waiters on edge-triggered epoll
instances where an emitter writes to multiple sockets (epoll15,
epoll16, epoll56, epoll58):

When the emitter performs its first write(), ep_poll_callback() fires
and wakes up both waiters because one waiter uses epoll_wait() and the
other one uses poll(). This translates to different wait queues,
ep->wq for epoll and ep->poll_wait for poll/select, which are both
awoken by the kernel because of that single write. Next, both waiter
threads invoke epoll_wait(), but since there is only one event, only
one epoll_wait() will return non-zero because of the edge-triggered
mode being used (in level-triggered mode, the kernel would re-queue
the event because of remaining unread data).

Since the second waiter sees an empty ready list, it does not
increment ctx.count and the test fails spuriously with ctx.count == 1
instead of 2.

  Emitter (CPU 0)      Thread 0 (CPU 1)        Thread 1 (CPU 2)
  ===============      ================        ================
                       epoll_wait(e0, -1)      poll(e0, -1)
                       [on e0->wq]             [on e0->poll_wait]

  write(sfd[1])
       |
       +--(Kernel wakes BOTH e0->wq and e0->poll_wait via callback)--+
       |                                                             |
       |               wakes up                wakes up              |
       |               epoll_wait() reaps e1   poll() returns 1      |
       |               (e1 removed via ET)     (wants event)         |
       |               e0->rdllist is EMPTY          |               |
       |               count++ (count = 1)           v               |
       |                                       epoll_wait(e0, 0)     |
       |                                       sees EMPTY list!      |
       |                                       returns 0!            |
       |                                       thread exits          |
       v                                                             |
  write(sfd[3])                                                      |
  (event arrives too late!)                                          v
                           EXPECT_EQ(count, 2)  <-- SPURIOUS FAILURE!

Introduce waiter_entry1ap_loop() to retry poll() if the initial
epoll_wait(..., 0) yielded no events. This ensures the thread waits
for the subsequent write rather than failing immediately. Apply this
helper in epoll15, epoll16, epoll56, and for both waiter threads in
epoll58.

Fixes: f2728fe80cef ("selftests: add epoll selftests")
Signed-off-by: Florian Schmaus <[email protected]>
---
 .../filesystems/epoll/epoll_wakeup_test.c          | 34 +++++++++++++++-------
 1 file changed, 23 insertions(+), 11 deletions(-)

diff --git a/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c 
b/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c
index 81a994943e12..d4267730f5ae 100644
--- a/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c
+++ b/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c
@@ -74,6 +74,24 @@ static void *waiter_entry1ap(void *data)
        return NULL;
 }
 
+static void *waiter_entry1ap_loop(void *data)
+{
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext *ctx = data;
+
+       pfd.fd = ctx->efd[0];
+       pfd.events = POLLIN;
+       while (poll(&pfd, 1, 2000) > 0) {
+               if (epoll_wait(ctx->efd[0], &e, 1, 0) > 0) {
+                       __sync_fetch_and_add(&ctx->count, 1);
+                       break;
+               }
+       }
+
+       return NULL;
+}
+
 static void *waiter_entry1o(void *data)
 {
        struct epoll_event e;
@@ -760,7 +778,7 @@ TEST(epoll15)
        ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[2], events), 0);
 
        ctx.main = pthread_self();
-       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry2ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap_loop, 
&ctx), 0);
        ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0);
 
        if (epoll_wait(ctx.efd[0], events, 2, -1) > 0)
@@ -809,7 +827,7 @@ TEST(epoll16)
        ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[2], events), 0);
 
        ctx.main = pthread_self();
-       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap_loop, 
&ctx), 0);
        ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0);
 
        if (epoll_wait(ctx.efd[0], events, 1, -1) > 0)
@@ -2925,7 +2943,7 @@ TEST(epoll56)
        ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[2], &e), 0);
 
        ctx.main = pthread_self();
-       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap_loop, 
&ctx), 0);
        ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0);
 
        if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
@@ -3030,7 +3048,6 @@ TEST(epoll57)
 TEST(epoll58)
 {
        pthread_t emitter;
-       struct pollfd pfd;
        struct epoll_event e;
        struct epoll_mtcontext ctx = { 0 };
 
@@ -3061,15 +3078,10 @@ TEST(epoll58)
        ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[2], &e), 0);
 
        ctx.main = pthread_self();
-       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap_loop, 
&ctx), 0);
        ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0);
 
-       pfd.fd = ctx.efd[0];
-       pfd.events = POLLIN;
-       if (poll(&pfd, 1, -1) > 0) {
-               if (epoll_wait(ctx.efd[0], &e, 1, 0) > 0)
-                       __sync_fetch_and_add(&ctx.count, 1);
-       }
+       waiter_entry1ap_loop(&ctx);
 
        ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
        EXPECT_EQ(ctx.count, 2);

---
base-commit: 1b78070aaef63512688aebfbc82365ef9d6660f1
change-id: 20260828-selftest-epoll-fix-race-6f5fbe3c22e7

Best regards,
--  
Florian Schmaus <[email protected]>


Reply via email to