From 05413b86fd2a74c04e24bb0856181d43e1d54a61 Mon Sep 17 00:00:00 2001 From: "Antonio S. Musumeci" Date: Fri, 10 Apr 2026 16:46:01 -0400 Subject: [PATCH] thread_pool: serialize idle self-exit with scaling operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _try_remove_self_if_above only held _threads_mutex, while set_threads and the monitor thread hold _scaling_mutex. An idle worker calling _try_remove_self_if_above could shrink the pool concurrently with set_threads reading _threads.size(), causing set_threads to miscalculate the delta and overshoot (add too many or remove too few threads). Fix by acquiring _scaling_mutex before _threads_mutex in _try_remove_self_if_above. This matches the existing lock ordering used by _maybe_grow_on_pressure and the monitor/set_threads paths (_scaling_mutex always acquired before _threads_mutex), so no deadlock is introduced. Now all paths that change the thread count — add_thread, remove_thread, set_threads, monitor hill-climb, pressure grow, and idle self-exit — are serialized under _scaling_mutex. Co-Authored-By: Claude Opus 4.6 (1M context) --- vendored/libfuse/include/thread_pool.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vendored/libfuse/include/thread_pool.hpp b/vendored/libfuse/include/thread_pool.hpp index d3c1639d..b593bc40 100644 --- a/vendored/libfuse/include/thread_pool.hpp +++ b/vendored/libfuse/include/thread_pool.hpp @@ -721,11 +721,20 @@ ThreadPool::_remove_self(pthread_t t_) // Atomically check thread count and remove self if above min_count_. // Returns true if removed, false if at or below threshold. +// +// Acquires _scaling_mutex then _threads_mutex (matching the lock +// ordering used by _maybe_grow_on_pressure and the monitor) so that +// idle self-exit is serialized with set_threads, _remove_thread, and +// _add_thread. Without _scaling_mutex, an idle self-exit racing with +// set_threads could cause set_threads to overshoot because it reads +// _threads.size() under _threads_mutex but the idle exit shrinks the +// pool outside _scaling_mutex. inline bool ThreadPool::_try_remove_self_if_above(pthread_t t_, std::size_t min_count_) { + mutex_lockguard(_scaling_mutex); mutex_lockguard(_threads_mutex); // Check _stop under the lock to close the TOCTOU race with the