On Thu, 5 Feb 2026 00:05:02 +0100 Yehor Malikov <[email protected]> wrote:
> From: Yehor Malikov <[email protected]> > > The fdset_event_dispatch thread runs in a loop checking the destroy > flag after each epoll_wait iteration. During process exit, > rte_eal_cleanup() frees hugepages memory while the fdset thread is > still running, causing use-after-free when accessing the fdset > structure. > > Add fdset_deinit() function to stop the dispatch thread by setting > the destroy flag and waiting for thread completion. Resource cleanup > (epoll fd, fdsets array, memory) is intentionally skipped since the > function is only called during process exit when the OS will reclaim > all resources anyway, avoiding potential deadlocks from mutex > operations in destructor context. > > Use symmetric RTE_FINI destructors for both vhost-user and VDUSE fdsets > to ensure proper cleanup before EAL teardown. > > Fixes: e68a6feaa3b3 ("vhost: improve fdset initialization") > > Signed-off-by: Yehor Malikov <[email protected]> I understand why this is important but not sure if it will work as you expect. I asked AI to help give a better explanation (so don't trust it to be right). Good question. Yes, there are potential concerns: **RTE_FINI ordering is undefined.** There's no guarantee about the order destructors run relative to each other or to EAL cleanup. The commit message says the goal is to stop the fdset thread "before EAL teardown" but `RTE_FINI` doesn't actually guarantee that - it just registers an `__attribute__((destructor))` function. **Possible scenarios:** 1. **EAL cleanup runs first** - If `rte_eal_cleanup()` frees hugepages before the `RTE_FINI` destructor runs, we're back to the original bug. The destructor would then call `fdset_deinit()` on already-freed memory. 2. **Destructor runs during active vhost operations** - If the application is still doing vhost work when exit starts, the destructor could stop the dispatch thread while callbacks are expected to run. The `busy` flag helps with `fdset_del()` synchronization, but abrupt thread termination during shutdown could leave things inconsistent. 3. **Fork concerns** - After `fork()`, the child has the same destructor registered but the dispatch thread doesn't exist in the child (threads aren't inherited). `rte_thread_join()` on an invalid/non-existent thread ID could behave unexpectedly. **What would be more robust:** The proper fix would be to integrate with EAL cleanup directly - register a callback via `rte_eal_cleanup_register()` (if that exists) or have vhost explicitly clean up its fdsets in a documented shutdown sequence before `rte_eal_cleanup()` is called, rather than relying on destructor ordering. Worth raising with the author: has this been tested with different linking scenarios (static vs dynamic) and confirmed the destructor actually runs before EAL teardown in practice?

