selftests: add epoll selftests
authorHeiher <r@hev.cc>
Thu, 5 Dec 2019 00:52:19 +0000 (16:52 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 5 Dec 2019 03:44:13 +0000 (19:44 -0800)
This adds the promised selftest for epoll.  It will verify the wakeups
of epoll.  Including leaf and nested mode, epoll_wait() and poll() and
multi-threads.

Link: http://lkml.kernel.org/r/20191009121518.4027-1-r@hev.cc
Signed-off-by: hev <r@hev.cc>
Reviewed-by: Roman Penyaev <rpenyaev@suse.de>
Cc: Jason Baron <jbaron@akamai.com>
Cc: Shuah Khan <shuah@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
tools/testing/selftests/Makefile
tools/testing/selftests/filesystems/epoll/.gitignore [new file with mode: 0644]
tools/testing/selftests/filesystems/epoll/Makefile [new file with mode: 0644]
tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c [new file with mode: 0644]

index d67f968..b001c60 100644 (file)
@@ -13,6 +13,7 @@ TARGETS += efivarfs
 TARGETS += exec
 TARGETS += filesystems
 TARGETS += filesystems/binderfs
+TARGETS += filesystems/epoll
 TARGETS += firmware
 TARGETS += ftrace
 TARGETS += futex
diff --git a/tools/testing/selftests/filesystems/epoll/.gitignore b/tools/testing/selftests/filesystems/epoll/.gitignore
new file mode 100644 (file)
index 0000000..9ae8db4
--- /dev/null
@@ -0,0 +1 @@
+epoll_wakeup_test
diff --git a/tools/testing/selftests/filesystems/epoll/Makefile b/tools/testing/selftests/filesystems/epoll/Makefile
new file mode 100644 (file)
index 0000000..e62f3d4
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+CFLAGS += -I../../../../../usr/include/
+LDFLAGS += -lpthread
+TEST_GEN_PROGS := epoll_wakeup_test
+
+include ../../lib.mk
diff --git a/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c b/tools/testing/selftests/filesystems/epoll/epoll_wakeup_test.c
new file mode 100644 (file)
index 0000000..37a04da
--- /dev/null
@@ -0,0 +1,3074 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <poll.h>
+#include <unistd.h>
+#include <signal.h>
+#include <pthread.h>
+#include <sys/epoll.h>
+#include <sys/socket.h>
+#include "../../kselftest_harness.h"
+
+struct epoll_mtcontext
+{
+       int efd[3];
+       int sfd[4];
+       int count;
+
+       pthread_t main;
+       pthread_t waiter;
+};
+
+static void signal_handler(int signum)
+{
+}
+
+static void kill_timeout(struct epoll_mtcontext *ctx)
+{
+       usleep(1000000);
+       pthread_kill(ctx->main, SIGUSR1);
+       pthread_kill(ctx->waiter, SIGUSR1);
+}
+
+static void *waiter_entry1a(void *data)
+{
+       struct epoll_event e;
+       struct epoll_mtcontext *ctx = data;
+
+       if (epoll_wait(ctx->efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx->count, 1);
+
+       return NULL;
+}
+
+static void *waiter_entry1ap(void *data)
+{
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext *ctx = data;
+
+       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);
+       }
+
+       return NULL;
+}
+
+static void *waiter_entry1o(void *data)
+{
+       struct epoll_event e;
+       struct epoll_mtcontext *ctx = data;
+
+       if (epoll_wait(ctx->efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_or(&ctx->count, 1);
+
+       return NULL;
+}
+
+static void *waiter_entry1op(void *data)
+{
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext *ctx = data;
+
+       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_or(&ctx->count, 1);
+       }
+
+       return NULL;
+}
+
+static void *waiter_entry2a(void *data)
+{
+       struct epoll_event events[2];
+       struct epoll_mtcontext *ctx = data;
+
+       if (epoll_wait(ctx->efd[0], events, 2, -1) > 0)
+               __sync_fetch_and_add(&ctx->count, 1);
+
+       return NULL;
+}
+
+static void *waiter_entry2ap(void *data)
+{
+       struct pollfd pfd;
+       struct epoll_event events[2];
+       struct epoll_mtcontext *ctx = data;
+
+       pfd.fd = ctx->efd[0];
+       pfd.events = POLLIN;
+       if (poll(&pfd, 1, -1) > 0) {
+               if (epoll_wait(ctx->efd[0], events, 2, 0) > 0)
+                       __sync_fetch_and_add(&ctx->count, 1);
+       }
+
+       return NULL;
+}
+
+static void *emitter_entry1(void *data)
+{
+       struct epoll_mtcontext *ctx = data;
+
+       usleep(100000);
+       write(ctx->sfd[1], "w", 1);
+
+       kill_timeout(ctx);
+
+       return NULL;
+}
+
+static void *emitter_entry2(void *data)
+{
+       struct epoll_mtcontext *ctx = data;
+
+       usleep(100000);
+       write(ctx->sfd[1], "w", 1);
+       write(ctx->sfd[3], "w", 1);
+
+       kill_timeout(ctx);
+
+       return NULL;
+}
+
+/*
+ *          t0
+ *           | (ew)
+ *          e0
+ *           | (lt)
+ *          s0
+ */
+TEST(epoll1)
+{
+       int efd;
+       int sfd[2];
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd = epoll_create(1);
+       ASSERT_GE(efd, 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd, &e, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd, &e, 1, 0), 1);
+
+       close(efd);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *          t0
+ *           | (ew)
+ *          e0
+ *           | (et)
+ *          s0
+ */
+TEST(epoll2)
+{
+       int efd;
+       int sfd[2];
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd = epoll_create(1);
+       ASSERT_GE(efd, 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd, &e, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd, &e, 1, 0), 0);
+
+       close(efd);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *           t0
+ *            | (ew)
+ *           e0
+ *     (lt) /  \ (lt)
+ *        s0    s2
+ */
+TEST(epoll3)
+{
+       int efd;
+       int sfd[4];
+       struct epoll_event events[2];
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[2]), 0);
+
+       efd = epoll_create(1);
+       ASSERT_GE(efd, 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[0], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[2], events), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+       ASSERT_EQ(write(sfd[3], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd, events, 2, 0), 2);
+       EXPECT_EQ(epoll_wait(efd, events, 2, 0), 2);
+
+       close(efd);
+       close(sfd[0]);
+       close(sfd[1]);
+       close(sfd[2]);
+       close(sfd[3]);
+}
+
+/*
+ *           t0
+ *            | (ew)
+ *           e0
+ *     (et) /  \ (et)
+ *        s0    s2
+ */
+TEST(epoll4)
+{
+       int efd;
+       int sfd[4];
+       struct epoll_event events[2];
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[2]), 0);
+
+       efd = epoll_create(1);
+       ASSERT_GE(efd, 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[0], events), 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[2], events), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+       ASSERT_EQ(write(sfd[3], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd, events, 2, 0), 2);
+       EXPECT_EQ(epoll_wait(efd, events, 2, 0), 0);
+
+       close(efd);
+       close(sfd[0]);
+       close(sfd[1]);
+       close(sfd[2]);
+       close(sfd[3]);
+}
+
+/*
+ *          t0
+ *           | (p)
+ *          e0
+ *           | (lt)
+ *          s0
+ */
+TEST(epoll5)
+{
+       int efd;
+       int sfd[2];
+       struct pollfd pfd;
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+
+       efd = epoll_create(1);
+       ASSERT_GE(efd, 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       pfd.fd = efd;
+       pfd.events = POLLIN;
+       ASSERT_EQ(poll(&pfd, 1, 0), 1);
+       ASSERT_EQ(epoll_wait(efd, &e, 1, 0), 1);
+
+       pfd.fd = efd;
+       pfd.events = POLLIN;
+       ASSERT_EQ(poll(&pfd, 1, 0), 1);
+       ASSERT_EQ(epoll_wait(efd, &e, 1, 0), 1);
+
+       close(efd);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *          t0
+ *           | (p)
+ *          e0
+ *           | (et)
+ *          s0
+ */
+TEST(epoll6)
+{
+       int efd;
+       int sfd[2];
+       struct pollfd pfd;
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+
+       efd = epoll_create(1);
+       ASSERT_GE(efd, 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       pfd.fd = efd;
+       pfd.events = POLLIN;
+       ASSERT_EQ(poll(&pfd, 1, 0), 1);
+       ASSERT_EQ(epoll_wait(efd, &e, 1, 0), 1);
+
+       pfd.fd = efd;
+       pfd.events = POLLIN;
+       ASSERT_EQ(poll(&pfd, 1, 0), 0);
+       ASSERT_EQ(epoll_wait(efd, &e, 1, 0), 0);
+
+       close(efd);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *           t0
+ *            | (p)
+ *           e0
+ *     (lt) /  \ (lt)
+ *        s0    s2
+ */
+
+TEST(epoll7)
+{
+       int efd;
+       int sfd[4];
+       struct pollfd pfd;
+       struct epoll_event events[2];
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[2]), 0);
+
+       efd = epoll_create(1);
+       ASSERT_GE(efd, 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[0], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[2], events), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+       ASSERT_EQ(write(sfd[3], "w", 1), 1);
+
+       pfd.fd = efd;
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd, events, 2, 0), 2);
+
+       pfd.fd = efd;
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd, events, 2, 0), 2);
+
+       close(efd);
+       close(sfd[0]);
+       close(sfd[1]);
+       close(sfd[2]);
+       close(sfd[3]);
+}
+
+/*
+ *           t0
+ *            | (p)
+ *           e0
+ *     (et) /  \ (et)
+ *        s0    s2
+ */
+TEST(epoll8)
+{
+       int efd;
+       int sfd[4];
+       struct pollfd pfd;
+       struct epoll_event events[2];
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[2]), 0);
+
+       efd = epoll_create(1);
+       ASSERT_GE(efd, 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[0], events), 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd, EPOLL_CTL_ADD, sfd[2], events), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+       ASSERT_EQ(write(sfd[3], "w", 1), 1);
+
+       pfd.fd = efd;
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd, events, 2, 0), 2);
+
+       pfd.fd = efd;
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 0);
+       EXPECT_EQ(epoll_wait(efd, events, 2, 0), 0);
+
+       close(efd);
+       close(sfd[0]);
+       close(sfd[1]);
+       close(sfd[2]);
+       close(sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll9)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *            | (et)
+ *           s0
+ */
+TEST(epoll10)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 1);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *     (lt) /  \ (lt)
+ *        s0    s2
+ */
+TEST(epoll11)
+{
+       pthread_t emitter;
+       struct epoll_event events[2];
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[0], events), 0);
+
+       events[0].events = EPOLLIN;
+       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_entry2a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], events, 2, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *     (et) /  \ (et)
+ *        s0    s2
+ */
+TEST(epoll12)
+{
+       pthread_t emitter;
+       struct epoll_event events[2];
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[0], events), 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       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_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], events, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll13)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *            | (et)
+ *           s0
+ */
+TEST(epoll14)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 1);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *     (lt) /  \ (lt)
+ *        s0    s2
+ */
+TEST(epoll15)
+{
+       pthread_t emitter;
+       struct epoll_event events[2];
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[0], events), 0);
+
+       events[0].events = EPOLLIN;
+       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(&emitter, NULL, emitter_entry2, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], events, 2, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *     (et) /  \ (et)
+ *        s0    s2
+ */
+TEST(epoll16)
+{
+       pthread_t emitter;
+       struct epoll_event events[2];
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.sfd[0], events), 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       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(&emitter, NULL, emitter_entry2, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], events, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+/*
+ *          t0
+ *           | (ew)
+ *          e0
+ *           | (lt)
+ *          e1
+ *           | (lt)
+ *          s0
+ */
+TEST(epoll17)
+{
+       int efd[2];
+       int sfd[2];
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *          t0
+ *           | (ew)
+ *          e0
+ *           | (lt)
+ *          e1
+ *           | (et)
+ *          s0
+ */
+TEST(epoll18)
+{
+       int efd[2];
+       int sfd[2];
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *           t0
+ *            | (ew)
+ *           e0
+ *            | (et)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll19)
+{
+       int efd[2];
+       int sfd[2];
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 0);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *           t0
+ *            | (ew)
+ *           e0
+ *            | (et)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll20)
+{
+       int efd[2];
+       int sfd[2];
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 0);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *          t0
+ *           | (p)
+ *          e0
+ *           | (lt)
+ *          e1
+ *           | (lt)
+ *          s0
+ */
+TEST(epoll21)
+{
+       int efd[2];
+       int sfd[2];
+       struct pollfd pfd;
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *          t0
+ *           | (p)
+ *          e0
+ *           | (lt)
+ *          e1
+ *           | (et)
+ *          s0
+ */
+TEST(epoll22)
+{
+       int efd[2];
+       int sfd[2];
+       struct pollfd pfd;
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *          t0
+ *           | (p)
+ *          e0
+ *           | (et)
+ *          e1
+ *           | (lt)
+ *          s0
+ */
+TEST(epoll23)
+{
+       int efd[2];
+       int sfd[2];
+       struct pollfd pfd;
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 0);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 0);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *          t0
+ *           | (p)
+ *          e0
+ *           | (et)
+ *          e1
+ *           | (et)
+ *          s0
+ */
+TEST(epoll24)
+{
+       int efd[2];
+       int sfd[2];
+       struct pollfd pfd;
+       struct epoll_event e;
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sfd), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], &e), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 0);
+       EXPECT_EQ(epoll_wait(efd[0], &e, 1, 0), 0);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(sfd[0]);
+       close(sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *            | (lt)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll25)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *            | (lt)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll26)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *            | (et)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll27)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 1);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *            | (et)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll28)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 1);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *            | (lt)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll29)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *            | (lt)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll30)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *            | (et)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll31)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 1);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *            | (et)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll32)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 1);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *    (ew) |    | (ew)
+ *         |   e0
+ *          \  / (lt)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll33)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *    (ew) |    | (ew)
+ *         |   e0
+ *          \  / (lt)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll34)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1o, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_or(&ctx.count, 2);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_TRUE((ctx.count == 2) || (ctx.count == 3));
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *    (ew) |    | (ew)
+ *         |   e0
+ *          \  / (et)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll35)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *    (ew) |    | (ew)
+ *         |   e0
+ *          \  / (et)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll36)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1o, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_or(&ctx.count, 2);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_TRUE((ctx.count == 2) || (ctx.count == 3));
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *     (p) |    | (ew)
+ *         |   e0
+ *          \  / (lt)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll37)
+{
+       pthread_t emitter;
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       pfd.fd = ctx.efd[1];
+       pfd.events = POLLIN;
+       if (poll(&pfd, 1, -1) > 0) {
+               if (epoll_wait(ctx.efd[1], &e, 1, 0) > 0)
+                       __sync_fetch_and_add(&ctx.count, 1);
+       }
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *     (p) |    | (ew)
+ *         |   e0
+ *          \  / (lt)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll38)
+{
+       pthread_t emitter;
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1o, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       pfd.fd = ctx.efd[1];
+       pfd.events = POLLIN;
+       if (poll(&pfd, 1, -1) > 0) {
+               if (epoll_wait(ctx.efd[1], &e, 1, 0) > 0)
+                       __sync_fetch_and_or(&ctx.count, 2);
+       }
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_TRUE((ctx.count == 2) || (ctx.count == 3));
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *     (p) |    | (ew)
+ *         |   e0
+ *          \  / (et)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll39)
+{
+       pthread_t emitter;
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       pfd.fd = ctx.efd[1];
+       pfd.events = POLLIN;
+       if (poll(&pfd, 1, -1) > 0) {
+               if (epoll_wait(ctx.efd[1], &e, 1, 0) > 0)
+                       __sync_fetch_and_add(&ctx.count, 1);
+       }
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *     (p) |    | (ew)
+ *         |   e0
+ *          \  / (et)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll40)
+{
+       pthread_t emitter;
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1o, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       pfd.fd = ctx.efd[1];
+       pfd.events = POLLIN;
+       if (poll(&pfd, 1, -1) > 0) {
+               if (epoll_wait(ctx.efd[1], &e, 1, 0) > 0)
+                       __sync_fetch_and_or(&ctx.count, 2);
+       }
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_TRUE((ctx.count == 2) || (ctx.count == 3));
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *    (ew) |    | (p)
+ *         |   e0
+ *          \  / (lt)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll41)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *    (ew) |    | (p)
+ *         |   e0
+ *          \  / (lt)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll42)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1op, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_or(&ctx.count, 2);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_TRUE((ctx.count == 2) || (ctx.count == 3));
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *    (ew) |    | (p)
+ *         |   e0
+ *          \  / (et)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll43)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *    (ew) |    | (p)
+ *         |   e0
+ *          \  / (et)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll44)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1op, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_or(&ctx.count, 2);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_TRUE((ctx.count == 2) || (ctx.count == 3));
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *     (p) |    | (p)
+ *         |   e0
+ *          \  / (lt)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll45)
+{
+       pthread_t emitter;
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       pfd.fd = ctx.efd[1];
+       pfd.events = POLLIN;
+       if (poll(&pfd, 1, -1) > 0) {
+               if (epoll_wait(ctx.efd[1], &e, 1, 0) > 0)
+                       __sync_fetch_and_add(&ctx.count, 1);
+       }
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *     (p) |    | (p)
+ *         |   e0
+ *          \  / (lt)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll46)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1op, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_or(&ctx.count, 2);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_TRUE((ctx.count == 2) || (ctx.count == 3));
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *     (p) |    | (p)
+ *         |   e0
+ *          \  / (et)
+ *           e1
+ *            | (lt)
+ *           s0
+ */
+TEST(epoll47)
+{
+       pthread_t emitter;
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1ap, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       pfd.fd = ctx.efd[1];
+       pfd.events = POLLIN;
+       if (poll(&pfd, 1, -1) > 0) {
+               if (epoll_wait(ctx.efd[1], &e, 1, 0) > 0)
+                       __sync_fetch_and_add(&ctx.count, 1);
+       }
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *        t0   t1
+ *     (p) |    | (p)
+ *         |   e0
+ *          \  / (et)
+ *           e1
+ *            | (et)
+ *           s0
+ */
+TEST(epoll48)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, ctx.sfd), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       ctx.main = pthread_self();
+       ASSERT_EQ(pthread_create(&ctx.waiter, NULL, waiter_entry1op, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry1, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[1], &e, 1, -1) > 0)
+               __sync_fetch_and_or(&ctx.count, 2);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_TRUE((ctx.count == 2) || (ctx.count == 3));
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+}
+
+/*
+ *           t0
+ *            | (ew)
+ *           e0
+ *     (lt) /  \ (lt)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll49)
+{
+       int efd[3];
+       int sfd[4];
+       struct epoll_event events[2];
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[2]), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       efd[2] = epoll_create(1);
+       ASSERT_GE(efd[2], 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[2], EPOLL_CTL_ADD, sfd[2], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[2], events), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+       ASSERT_EQ(write(sfd[3], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd[0], events, 2, 0), 2);
+       EXPECT_EQ(epoll_wait(efd[0], events, 2, 0), 2);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(efd[2]);
+       close(sfd[0]);
+       close(sfd[1]);
+       close(sfd[2]);
+       close(sfd[3]);
+}
+
+/*
+ *           t0
+ *            | (ew)
+ *           e0
+ *     (et) /  \ (et)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll50)
+{
+       int efd[3];
+       int sfd[4];
+       struct epoll_event events[2];
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[2]), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       efd[2] = epoll_create(1);
+       ASSERT_GE(efd[2], 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[2], EPOLL_CTL_ADD, sfd[2], events), 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], events), 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[2], events), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+       ASSERT_EQ(write(sfd[3], "w", 1), 1);
+
+       EXPECT_EQ(epoll_wait(efd[0], events, 2, 0), 2);
+       EXPECT_EQ(epoll_wait(efd[0], events, 2, 0), 0);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(efd[2]);
+       close(sfd[0]);
+       close(sfd[1]);
+       close(sfd[2]);
+       close(sfd[3]);
+}
+
+/*
+ *           t0
+ *            | (p)
+ *           e0
+ *     (lt) /  \ (lt)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll51)
+{
+       int efd[3];
+       int sfd[4];
+       struct pollfd pfd;
+       struct epoll_event events[2];
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[2]), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       efd[2] = epoll_create(1);
+       ASSERT_GE(efd[2], 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[2], EPOLL_CTL_ADD, sfd[2], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[2], events), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+       ASSERT_EQ(write(sfd[3], "w", 1), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], events, 2, 0), 2);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], events, 2, 0), 2);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(efd[2]);
+       close(sfd[0]);
+       close(sfd[1]);
+       close(sfd[2]);
+       close(sfd[3]);
+}
+
+/*
+ *           t0
+ *            | (p)
+ *           e0
+ *     (et) /  \ (et)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll52)
+{
+       int efd[3];
+       int sfd[4];
+       struct pollfd pfd;
+       struct epoll_event events[2];
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &sfd[2]), 0);
+
+       efd[0] = epoll_create(1);
+       ASSERT_GE(efd[0], 0);
+
+       efd[1] = epoll_create(1);
+       ASSERT_GE(efd[1], 0);
+
+       efd[2] = epoll_create(1);
+       ASSERT_GE(efd[2], 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[1], EPOLL_CTL_ADD, sfd[0], events), 0);
+
+       events[0].events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(efd[2], EPOLL_CTL_ADD, sfd[2], events), 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[1], events), 0);
+
+       events[0].events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(efd[0], EPOLL_CTL_ADD, efd[2], events), 0);
+
+       ASSERT_EQ(write(sfd[1], "w", 1), 1);
+       ASSERT_EQ(write(sfd[3], "w", 1), 1);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 1);
+       EXPECT_EQ(epoll_wait(efd[0], events, 2, 0), 2);
+
+       pfd.fd = efd[0];
+       pfd.events = POLLIN;
+       EXPECT_EQ(poll(&pfd, 1, 0), 0);
+       EXPECT_EQ(epoll_wait(efd[0], events, 2, 0), 0);
+
+       close(efd[0]);
+       close(efd[1]);
+       close(efd[2]);
+       close(sfd[0]);
+       close(sfd[1]);
+       close(sfd[2]);
+       close(sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *     (lt) /  \ (lt)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll53)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       ctx.efd[2] = epoll_create(1);
+       ASSERT_GE(ctx.efd[2], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[2], EPOLL_CTL_ADD, ctx.sfd[2], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       e.events = EPOLLIN;
+       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_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.efd[2]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (ew)
+ *           e0
+ *     (et) /  \ (et)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll54)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       ctx.efd[2] = epoll_create(1);
+       ASSERT_GE(ctx.efd[2], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[2], EPOLL_CTL_ADD, ctx.sfd[2], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       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_entry1a, &ctx), 0);
+       ASSERT_EQ(pthread_create(&emitter, NULL, emitter_entry2, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.efd[2]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *     (lt) /  \ (lt)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll55)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       ctx.efd[2] = epoll_create(1);
+       ASSERT_GE(ctx.efd[2], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[2], EPOLL_CTL_ADD, ctx.sfd[2], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       e.events = EPOLLIN;
+       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(&emitter, NULL, emitter_entry2, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.efd[2]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *     (ew) \  / (p)
+ *           e0
+ *     (et) /  \ (et)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll56)
+{
+       pthread_t emitter;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       ctx.efd[2] = epoll_create(1);
+       ASSERT_GE(ctx.efd[2], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[2], EPOLL_CTL_ADD, ctx.sfd[2], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       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(&emitter, NULL, emitter_entry2, &ctx), 0);
+
+       if (epoll_wait(ctx.efd[0], &e, 1, -1) > 0)
+               __sync_fetch_and_add(&ctx.count, 1);
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.efd[2]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *      (p) \  / (p)
+ *           e0
+ *     (lt) /  \ (lt)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll57)
+{
+       pthread_t emitter;
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       ctx.efd[2] = epoll_create(1);
+       ASSERT_GE(ctx.efd[2], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[2], EPOLL_CTL_ADD, ctx.sfd[2], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       e.events = EPOLLIN;
+       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(&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);
+       }
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.efd[2]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+/*
+ *        t0    t1
+ *      (p) \  / (p)
+ *           e0
+ *     (et) /  \ (et)
+ *        e1    e2
+ *    (lt) |     | (lt)
+ *        s0    s2
+ */
+TEST(epoll58)
+{
+       pthread_t emitter;
+       struct pollfd pfd;
+       struct epoll_event e;
+       struct epoll_mtcontext ctx = { 0 };
+
+       signal(SIGUSR1, signal_handler);
+
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[0]), 0);
+       ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx.sfd[2]), 0);
+
+       ctx.efd[0] = epoll_create(1);
+       ASSERT_GE(ctx.efd[0], 0);
+
+       ctx.efd[1] = epoll_create(1);
+       ASSERT_GE(ctx.efd[1], 0);
+
+       ctx.efd[2] = epoll_create(1);
+       ASSERT_GE(ctx.efd[2], 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[1], EPOLL_CTL_ADD, ctx.sfd[0], &e), 0);
+
+       e.events = EPOLLIN;
+       ASSERT_EQ(epoll_ctl(ctx.efd[2], EPOLL_CTL_ADD, ctx.sfd[2], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       ASSERT_EQ(epoll_ctl(ctx.efd[0], EPOLL_CTL_ADD, ctx.efd[1], &e), 0);
+
+       e.events = EPOLLIN | EPOLLET;
+       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(&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);
+       }
+
+       ASSERT_EQ(pthread_join(ctx.waiter, NULL), 0);
+       EXPECT_EQ(ctx.count, 2);
+
+       if (pthread_tryjoin_np(emitter, NULL) < 0) {
+               pthread_kill(emitter, SIGUSR1);
+               pthread_join(emitter, NULL);
+       }
+
+       close(ctx.efd[0]);
+       close(ctx.efd[1]);
+       close(ctx.efd[2]);
+       close(ctx.sfd[0]);
+       close(ctx.sfd[1]);
+       close(ctx.sfd[2]);
+       close(ctx.sfd[3]);
+}
+
+TEST_HARNESS_MAIN