From c0b4ea3fae1f7b09ac27d96a66ccb9dce906bf1a Mon Sep 17 00:00:00 2001 From: "Antonio S. Musumeci" Date: Thu, 9 Apr 2026 19:35:35 -0400 Subject: [PATCH] Fix destructor race with _remove_self during shutdown The destructor and autoscale idle-exit path could race: a worker calling _remove_self would erase itself from _threads and detach while the destructor was iterating _threads to enqueue poison pills and join. This could cause too few poison pills (deadlocking remaining workers) or pthread_join on a detached thread (UB). Two changes: - _remove_self now checks _stop and returns immediately during shutdown, ensuring no thread detaches after destruction begins. - Destructor snapshots _threads under the mutex before enqueueing poison pills and joining, so it operates on a stable copy. Co-Authored-By: Claude Opus 4.6 (1M context) --- vendored/libfuse/include/thread_pool.hpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/vendored/libfuse/include/thread_pool.hpp b/vendored/libfuse/include/thread_pool.hpp index e5cc218a..da4b7fc5 100644 --- a/vendored/libfuse/include/thread_pool.hpp +++ b/vendored/libfuse/include/thread_pool.hpp @@ -291,16 +291,25 @@ ThreadPool::~ThreadPool() pthread_join(_monitor_thread,NULL); } + // Snapshot threads under lock to close the race with _remove_self. + // After _stop is set, _remove_self becomes a no-op, so no thread + // will detach or erase itself from this point forward. + std::vector snapshot; + { + mutex_lockguard(_threads_mutex); + snapshot = _threads; + } + syslog(LOG_DEBUG, "threadpool (%s): destroying %zu threads", _name.c_str(), - _threads.size()); + snapshot.size()); // Enqueue one poison pill per thread - for(std::size_t i = 0; i < _threads.size(); ++i) + for(std::size_t i = 0; i < snapshot.size(); ++i) _queue.enqueue_unbounded(Func{}); - for(auto t : _threads) + for(auto t : snapshot) pthread_join(t,NULL); } @@ -546,6 +555,10 @@ inline void ThreadPool::_remove_self(pthread_t t_) { + // During shutdown the destructor owns all joins; don't detach. + if(_stop.load(std::memory_order_acquire)) + return; + mutex_lockguard(_threads_mutex); auto it = std::find(_threads.begin(),_threads.end(),t_);