From 4b5b102e6f88608e37d1ab9a9072dc6a540b036b Mon Sep 17 00:00:00 2001 From: "Antonio S. Musumeci" Date: Fri, 10 Apr 2026 01:15:21 -0400 Subject: [PATCH] Re-check cooldown under lock in _maybe_grow_on_pressure The pre-lock cooldown check used a stale _last_scale_time_usecs read. If another thread (monitor hill-climb, concurrent enqueue) updated the timestamp between the pre-lock check and the lock acquisition, the function would proceed to acquire _scaling_mutex and contend unnecessarily. The CAS that followed would correctly reject the stale value, so this was not a correctness bug, but it caused avoidable lock contention on the hot enqueue path. Add an authoritative cooldown re-check after acquiring _scaling_mutex with a fresh timestamp read. Replace the CAS with a plain store since the _scaling_mutex already serializes writers. Keep the pre-lock speculative check as a fast-path filter. Co-Authored-By: Claude Opus 4.6 (1M context) --- vendored/libfuse/include/thread_pool.hpp | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/vendored/libfuse/include/thread_pool.hpp b/vendored/libfuse/include/thread_pool.hpp index 65e53ff5..fea4cb54 100644 --- a/vendored/libfuse/include/thread_pool.hpp +++ b/vendored/libfuse/include/thread_pool.hpp @@ -669,16 +669,24 @@ ThreadPool::_maybe_grow_on_pressure() if(!_autoscale_enabled) return; - std::uint64_t now = _now_usecs(); - std::uint64_t last = _last_scale_time_usecs.load(std::memory_order_relaxed); - - if((now - last) < (std::uint64_t)_scaling_config.cooldown_usecs) - return; + // Quick check before contending on the lock. Uses a stale read + // but the authoritative re-check happens under the lock below. + { + std::uint64_t now = _now_usecs(); + std::uint64_t last = _last_scale_time_usecs.load(std::memory_order_relaxed); + if((now - last) < (std::uint64_t)_scaling_config.cooldown_usecs) + return; + } // Hold _scaling_mutex so the thread-count check and add are atomic // with respect to other scaling operations. mutex_lockguard(_scaling_mutex); + std::uint64_t now = _now_usecs(); + std::uint64_t last = _last_scale_time_usecs.load(std::memory_order_relaxed); + if((now - last) < (std::uint64_t)_scaling_config.cooldown_usecs) + return; + std::size_t count; { mutex_lockguard(_threads_mutex); @@ -688,12 +696,7 @@ ThreadPool::_maybe_grow_on_pressure() if(count >= _scaling_config.max_threads) return; - // CAS to claim the cooldown window — only one caller wins. - // Done after the max-threads check so we don't burn the cooldown - // when the pool is already at capacity. - if(!_last_scale_time_usecs.compare_exchange_strong(last,now, - std::memory_order_relaxed)) - return; + _last_scale_time_usecs.store(now,std::memory_order_relaxed); _add_thread_scaling_locked({}); syslog(LOG_DEBUG,