A callback may register another callback for the same interrupt handle
while it runs. This is what net/tap does when a tap interface moves to
another netns: the LSC callback unregisters itself (pending) and
registers again after the handle points to a netlink socket of the new
namespace.

Since the interrupt source lookup is now handle pointer based, the new
callback is appended to the source being dispatched. The callback loop
then invokes it in the same pass with the event reported for the old FD.
net/tap reads the new netlink FD, which has no pending data, and blocks
forever in recv(). The interrupt source stays active and any later
unregister retries on -EAGAIN indefinitely.

Only invoke callbacks which were registered before the event was
dispatched.

Fixes: c663018eb7f3 ("interrupts: do not duplicate interrupt handles")

Signed-off-by: Robin Jarry <[email protected]>
---
 lib/eal/freebsd/eal_interrupts.c | 16 +++++++++++++++-
 lib/eal/linux/eal_interrupts.c   | 16 +++++++++++++++-
 2 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/lib/eal/freebsd/eal_interrupts.c b/lib/eal/freebsd/eal_interrupts.c
index b29a2b174e10..f6eec2208875 100644
--- a/lib/eal/freebsd/eal_interrupts.c
+++ b/lib/eal/freebsd/eal_interrupts.c
@@ -529,8 +529,19 @@ eal_intr_process_interrupts(struct kevent *events, int 
nfds)
                rte_spinlock_lock(&intr_lock);
 
                if (call) {
+                       struct rte_intr_callback *last;
+
+                       /*
+                        * Only invoke the callbacks which were registered 
before
+                        * this event was dispatched. A callback may register a
+                        * new one (e.g. net/tap re-arming its LSC interrupt on
+                        * netns change, possibly on a different FD). It must 
not
+                        * receive the current event.
+                        */
+                       last = TAILQ_LAST(&src->callbacks, rte_intr_cb_list);
                        /* Finally, call all callbacks. */
-                       TAILQ_FOREACH(cb, &src->callbacks, next) {
+                       for (cb = TAILQ_FIRST(&src->callbacks); cb != NULL; cb 
= next) {
+                               next = TAILQ_NEXT(cb, next);
 
                                /* make a copy and unlock. */
                                active_cb = *cb;
@@ -541,6 +552,9 @@ eal_intr_process_interrupts(struct kevent *events, int nfds)
 
                                /*get the lock back. */
                                rte_spinlock_lock(&intr_lock);
+
+                               if (cb == last)
+                                       break;
                        }
                }
 
diff --git a/lib/eal/linux/eal_interrupts.c b/lib/eal/linux/eal_interrupts.c
index dfe1660dd016..1572e4b29df4 100644
--- a/lib/eal/linux/eal_interrupts.c
+++ b/lib/eal/linux/eal_interrupts.c
@@ -1019,9 +1019,20 @@ eal_intr_process_interrupts(struct epoll_event *events, 
int nfds)
                rte_spinlock_lock(&intr_lock);
 
                if (call) {
+                       struct rte_intr_callback *last;
+
+                       /*
+                        * Only invoke the callbacks which were registered 
before
+                        * this event was dispatched. A callback may register a
+                        * new one (e.g. net/tap re-arming its LSC interrupt on
+                        * netns change, possibly on a different FD). It must 
not
+                        * receive the current event.
+                        */
+                       last = TAILQ_LAST(&src->callbacks, rte_intr_cb_list);
                        active_events = epoll_to_intr_events(events[n].events);
                        /* Finally, call all callbacks. */
-                       TAILQ_FOREACH(cb, &src->callbacks, next) {
+                       for (cb = TAILQ_FIRST(&src->callbacks); cb != NULL; cb 
= next) {
+                               next = TAILQ_NEXT(cb, next);
 
                                /* make a copy and unlock. */
                                active_cb = *cb;
@@ -1032,6 +1043,9 @@ eal_intr_process_interrupts(struct epoll_event *events, 
int nfds)
 
                                /*get the lock back. */
                                rte_spinlock_lock(&intr_lock);
+
+                               if (cb == last)
+                                       break;
                        }
                        active_events = 0;
                }
-- 
2.56.0

Reply via email to