eventhandler: Fix a race when pruning eventhandlers

By default, eventhandler_deregister() blocks until it reaches some point
where no threads are invoking the event.  At this point, it knows that
1) no threads are currently executing the handler,
2) some thread has freed the eventhandler structure by virtue of having
   called eventhandler_prune_list(),
so it is safe to return.

Suppose a thread is trying to deregister an event handler.  A different
thread prunes it, and wakes up the first thread.  Before the first
thread runs, a third thread grabs the event handler lock, and starts
executing handlers.  The first thread observes el_runcount > 0, and goes
back to sleep.  The third thread sees no event handlers to prune, and
doesn't wake up the first thread, which sleeps forever.

This change fixes the race and tries to make eventhandler_invoke() more
efficient: keep a count of the number of dead list entries and only
prune the list if there is at least one dead entry.  Also, in
eventhandler_deregister(), we only need to sleep if some dead entries
are present, rather than sleeping whenever some thread is running
handlers.

Reviewed by:	kib
MFC after:	2 weeks
Differential Revision:	https://reviews.freebsd.org/D56767
This commit is contained in:
Mark Johnston
2026-05-06 11:48:05 +00:00
parent 8223661346
commit 735b16d490
2 changed files with 16 additions and 6 deletions
+14 -4
View File
@@ -198,7 +198,10 @@ _eventhandler_deregister(struct eventhandler_list *list, eventhandler_tag tag,
} else { } else {
CTR3(KTR_EVH, "%s: marking item %p from \"%s\" as dead", __func__, CTR3(KTR_EVH, "%s: marking item %p from \"%s\" as dead", __func__,
ep, list->el_name); ep, list->el_name);
KASSERT(ep->ee_priority != EHE_DEAD_PRIORITY,
("%s: handler for %s is dead", __func__, list->el_name));
ep->ee_priority = EHE_DEAD_PRIORITY; ep->ee_priority = EHE_DEAD_PRIORITY;
list->el_deadcount++;
} }
} else { } else {
/* remove entire list */ /* remove entire list */
@@ -213,11 +216,15 @@ _eventhandler_deregister(struct eventhandler_list *list, eventhandler_tag tag,
} else { } else {
CTR2(KTR_EVH, "%s: marking all items from \"%s\" as dead", CTR2(KTR_EVH, "%s: marking all items from \"%s\" as dead",
__func__, list->el_name); __func__, list->el_name);
TAILQ_FOREACH(ep, &list->el_entries, ee_link) TAILQ_FOREACH(ep, &list->el_entries, ee_link) {
KASSERT(ep->ee_priority != EHE_DEAD_PRIORITY,
("%s: handler for %s is dead", __func__, list->el_name));
ep->ee_priority = EHE_DEAD_PRIORITY; ep->ee_priority = EHE_DEAD_PRIORITY;
list->el_deadcount++;
}
} }
} }
while (wait && list->el_runcount > 0) while (wait && list->el_deadcount > 0)
mtx_sleep(list, &list->el_lock, 0, "evhrm", 0); mtx_sleep(list, &list->el_lock, 0, "evhrm", 0);
EHL_UNLOCK(list); EHL_UNLOCK(list);
} }
@@ -292,8 +299,11 @@ eventhandler_prune_list(struct eventhandler_list *list)
pruned++; pruned++;
} }
} }
if (pruned > 0) KASSERT(pruned == list->el_deadcount,
wakeup(list); ("%s: pruned %u entries from \"%s\" but expected %u",
__func__, pruned, list->el_name, list->el_deadcount));
list->el_deadcount = 0;
wakeup(list);
} }
/* /*
+2 -2
View File
@@ -46,7 +46,7 @@ struct eventhandler_entry_vimage {
struct eventhandler_list { struct eventhandler_list {
char *el_name; char *el_name;
int el_flags; /* Unused. */ u_int el_deadcount;
u_int el_runcount; u_int el_runcount;
struct mtx el_lock; struct mtx el_lock;
TAILQ_ENTRY(eventhandler_list) el_link; TAILQ_ENTRY(eventhandler_list) el_link;
@@ -82,7 +82,7 @@ struct eventhandler_list {
KASSERT((list)->el_runcount > 0, \ KASSERT((list)->el_runcount > 0, \
("eventhandler_invoke: runcount underflow")); \ ("eventhandler_invoke: runcount underflow")); \
(list)->el_runcount--; \ (list)->el_runcount--; \
if ((list)->el_runcount == 0) \ if ((list)->el_runcount == 0 && (list)->el_deadcount != 0) \
eventhandler_prune_list(list); \ eventhandler_prune_list(list); \
EHL_UNLOCK((list)); \ EHL_UNLOCK((list)); \
} while (0) } while (0)