[PATCH] eal/linux: handle epoll error conditions

Kevin Traynor
2026-01-28T12:21:46Z

Add handling for epoll error conditions EPOLLERR, EPOLLHUP and
EPOLLRDHUP. These events indicate that the interrupt file descriptor
is in an error state or there has been a hangup.

This may happen when the interrupt file descriptor is deleted or for
mlx5 devices when the device is unbound from mlx5 kernel driver or if
the device is removed by the mlx5 kernel driver as part of LAG setup.

Previously, the interrupts were being read, but the condition was not
cleared and that may lead to an interrupt continuing to fire and a
busy-loop processing it.

Now when this condition is detected, an error message is logged and the
interrupt is removed to prevent busy-looping.

Also cover the case where no bytes are read even though the epoll has
indicated there is something to read.

Bugzilla ID: 1873
Fixes: af75078fece3 ("first public release")
Cc: stable@dpdk.org

Signed-off-by: Kevin Traynor <ktraynor@redhat.com>
---
lib/eal/linux/eal_interrupts.c | 63 ++++++++++++++++++++++++----------
1 file changed, 44 insertions(+), 19 deletions(-)

diff --git a/lib/eal/linux/eal_interrupts.c b/lib/eal/linux/eal_interrupts.c
index 9db978923a..eedc75d776 100644
--- a/lib/eal/linux/eal_interrupts.c
+++ b/lib/eal/linux/eal_interrupts.c
@@ -887,4 +887,21 @@ rte_intr_disable(const struct rte_intr_handle *intr_handle)
}

+static void
+eal_intr_source_free(struct rte_intr_source *src)
+{
+ struct rte_intr_callback *cb, *next;
+
+ /* Free all callbacks */
+ for (cb = TAILQ_FIRST(&src->callbacks); cb; cb = next) {
+ next = TAILQ_NEXT(cb, next);
+ TAILQ_REMOVE(&src->callbacks, cb, next);
+ free(cb);
+ }
+
+ /* Free the interrupt source */
+ rte_intr_instance_free(src->intr_handle);
+ free(src);
+}
+
static int
eal_intr_process_interrupts(struct epoll_event *events, int nfds)
@@ -918,4 +935,21 @@ eal_intr_process_interrupts(struct epoll_event *events, int nfds)
}

+ /* Check for error conditions on the fd before processing. */
+ if (events[n].events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)) {
+ EAL_LOG(WARNING, "Disconnect condition on fd %d "
+ "(events=0x%x), removing from epoll",
+ events[n].data.fd, events[n].events);
+ /*
+ * There is an error or a hangup. Remove the
+ * interrupt source and return to force the wait list
+ * to be rebuilt.
+ */
+ TAILQ_REMOVE(&intr_sources, src, next);
+ rte_spinlock_unlock(&intr_lock);
+
+ eal_intr_source_free(src);
+ return -1;
+ }
+
/* mark this interrupt source as active and release the lock. */
src->active = 1;
@@ -957,5 +991,7 @@ eal_intr_process_interrupts(struct epoll_event *events, int nfds)
*/
bytes_read = read(events[n].data.fd, &buf, bytes_read);
- if (bytes_read < 0) {
+ if (bytes_read > 0) {
+ call = true;
+ } else if (bytes_read < 0) {
if (errno == EINTR || errno == EWOULDBLOCK)
continue;
@@ -965,27 +1001,16 @@ eal_intr_process_interrupts(struct epoll_event *events, int nfds)
events[n].data.fd,
strerror(errno));
- /*
- * The device is unplugged or buggy, remove
- * it as an interrupt source and return to
- * force the wait list to be rebuilt.
- */
+ } else { /* bytes == 0 */
+ EAL_LOG(WARNING, "Read nothing from file "
+ "descriptor %d", events[n].data.fd);
+ }
+ if (bytes_read <= 0) {
rte_spinlock_lock(&intr_lock);
TAILQ_REMOVE(&intr_sources, src, next);
rte_spinlock_unlock(&intr_lock);

- for (cb = TAILQ_FIRST(&src->callbacks); cb;
- cb = next) {
- next = TAILQ_NEXT(cb, next);
- TAILQ_REMOVE(&src->callbacks, cb, next);
- free(cb);
- }
- rte_intr_instance_free(src->intr_handle);
- free(src);
+ eal_intr_source_free(src);
return -1;
- } else if (bytes_read == 0)
- EAL_LOG(ERR, "Read nothing from file "
- "descriptor %d", events[n].data.fd);
- else
- call = true;
+ }
}

--
2.52.0