From b137f071988d9b01c05cb693a56d4359f19cdb2c Mon Sep 17 00:00:00 2001
From: Andy Pan <i@andypan.me>
Date: Wed, 17 Apr 2024 10:36:47 +0000
Subject: [PATCH] Avoid calling read(2) on eventfd on each event-loop wakeup

Register the eventfd with EPOLLET to enable edge-triggered notification
where we don't need to read the data from the eventfd for every wakeup
event.

When the eventfd counter reaches the maximum value of the unsigned 64-bit,
we rewind the counter and retry again. This optimization saves one system
call on each event-loop wakeup, which eliminates the extra latency for epoll
as the EVFILT_USER filter does for the kqueue.
---
 event.c | 38 ++++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 14 deletions(-)

diff --git a/event.c b/event.c
index 7a42b73..e37e588 100644
--- a/event.c
+++ b/event.c
@@ -211,7 +211,7 @@ int event_debug_mode_on_ = 0;
  *        to be shared across threads (if thread support is enabled).
  *
  *        When and if evthreads are initialized, this variable will be evaluated,
- *        and if set to something other than zero, this means the evthread setup 
+ *        and if set to something other than zero, this means the evthread setup
  *        functions were called out of order.
  *
  *        See: "Locks and threading" in the documentation.
@@ -2523,13 +2523,30 @@ evthread_notify_base_default(struct event_base *base)
 static int
 evthread_notify_base_eventfd(struct event_base *base)
 {
+	int efd = base->th_notify_fd[0];
 	ev_uint64_t msg = 1;
-	int r;
-	do {
-		r = write(base->th_notify_fd[0], (void*) &msg, sizeof(msg));
-	} while (r < 0 && errno == EAGAIN);
+	ev_uint64_t val;
 
-	return (r < 0) ? -1 : 0;
+	int ret;
+	for (;;) {
+		ret = eventfd_write(efd, (eventfd_t) msg);
+		if (ret < 0) {
+			// When EAGAIN occurs, the eventfd counter hits the maximum value of the unsigned 64-bit.
+			// We need to first drain the eventfd and then write again.
+			//
+			// Check out https://man7.org/linux/man-pages/man2/eventfd.2.html for details.
+			if (errno == EAGAIN) {
+				// It's ready to retry.
+				if (eventfd_read(efd, &val) == 0 || errno == EAGAIN) {
+					continue;
+				}
+			}
+			// Unknown error occurs.
+			ret = -1;
+		}
+		break;
+	}
+	return ret;
 }
 #endif
 
@@ -3582,14 +3599,7 @@ event_set_mem_functions(void *(*malloc_fn)(size_t sz),
 static void
 evthread_notify_drain_eventfd(evutil_socket_t fd, short what, void *arg)
 {
-	ev_uint64_t msg;
-	ev_ssize_t r;
 	struct event_base *base = arg;
-
-	r = read(fd, (void*) &msg, sizeof(msg));
-	if (r<0 && errno != EAGAIN) {
-		event_sock_warn(fd, "Error reading from eventfd");
-	}
 	EVBASE_ACQUIRE_LOCK(base, th_base_lock);
 	base->is_notify_pending = 0;
 	EVBASE_RELEASE_LOCK(base, th_base_lock);
@@ -3667,7 +3677,7 @@ evthread_make_base_notifiable_nolock_(struct event_base *base)
 
 	/* prepare an event that we can use for wakeup */
 	event_assign(&base->th_notify, base, base->th_notify_fd[0],
-				 EV_READ|EV_PERSIST, cb, base);
+				 EV_READ|EV_PERSIST|EV_ET, cb, base);
 
 	/* we need to mark this as internal event */
 	base->th_notify.ev_flags |= EVLIST_INTERNAL;
-- 
2.27.0

