aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2026-05-06 11:48:05 +0000
committerMark Johnston <markj@FreeBSD.org>2026-05-06 11:48:05 +0000
commit735b16d490aee158beb54c415b716475a0d19cda (patch)
treed8dbfbefc8fb7cef3278ebc7f61fcf489fa898c2
parent822366134658ee59922d7f3e37db7bbb8956aaaf (diff)
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
-rw-r--r--sys/kern/subr_eventhandler.c18
-rw-r--r--sys/sys/eventhandler.h4
2 files changed, 16 insertions, 6 deletions
diff --git a/sys/kern/subr_eventhandler.c b/sys/kern/subr_eventhandler.c
index 7568101dabd2..e91647648995 100644
--- a/sys/kern/subr_eventhandler.c
+++ b/sys/kern/subr_eventhandler.c
@@ -198,7 +198,10 @@ _eventhandler_deregister(struct eventhandler_list *list, eventhandler_tag tag,
} else {
CTR3(KTR_EVH, "%s: marking item %p from \"%s\" as dead", __func__,
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;
+ list->el_deadcount++;
}
} else {
/* remove entire list */
@@ -213,11 +216,15 @@ _eventhandler_deregister(struct eventhandler_list *list, eventhandler_tag tag,
} else {
CTR2(KTR_EVH, "%s: marking all items from \"%s\" as dead",
__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;
+ list->el_deadcount++;
+ }
}
}
- while (wait && list->el_runcount > 0)
+ while (wait && list->el_deadcount > 0)
mtx_sleep(list, &list->el_lock, 0, "evhrm", 0);
EHL_UNLOCK(list);
}
@@ -292,8 +299,11 @@ eventhandler_prune_list(struct eventhandler_list *list)
pruned++;
}
}
- if (pruned > 0)
- wakeup(list);
+ KASSERT(pruned == list->el_deadcount,
+ ("%s: pruned %u entries from \"%s\" but expected %u",
+ __func__, pruned, list->el_name, list->el_deadcount));
+ list->el_deadcount = 0;
+ wakeup(list);
}
/*
diff --git a/sys/sys/eventhandler.h b/sys/sys/eventhandler.h
index 29a16b393b52..f37b1150a7e8 100644
--- a/sys/sys/eventhandler.h
+++ b/sys/sys/eventhandler.h
@@ -46,7 +46,7 @@ struct eventhandler_entry_vimage {
struct eventhandler_list {
char *el_name;
- int el_flags; /* Unused. */
+ u_int el_deadcount;
u_int el_runcount;
struct mtx el_lock;
TAILQ_ENTRY(eventhandler_list) el_link;
@@ -82,7 +82,7 @@ struct eventhandler_list {
KASSERT((list)->el_runcount > 0, \
("eventhandler_invoke: runcount underflow")); \
(list)->el_runcount--; \
- if ((list)->el_runcount == 0) \
+ if ((list)->el_runcount == 0 && (list)->el_deadcount != 0) \
eventhandler_prune_list(list); \
EHL_UNLOCK((list)); \
} while (0)