From 0bef28d1543b419300d4034d6f98d144ecae3fa2 Mon Sep 17 00:00:00 2001 From: "Antonio S. Musumeci" Date: Thu, 9 Apr 2026 21:51:50 -0400 Subject: [PATCH] Bound _remove_thread_locked wait to avoid indefinite _scaling_mutex hold MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _remove_thread_locked enqueues a self-exit task and blocks on the future while holding _scaling_mutex. If all workers are busy with long-running tasks, this blocks indefinitely — starving the monitor thread (and any concurrent add_thread/set_threads) since they all contend on _scaling_mutex. Replace the unbounded wait with a 5-second timed wait. On timeout, return -EBUSY so callers can retry or report the condition. The enqueued self-exit task will still be picked up eventually and the thread will remove itself when a worker becomes free. Co-Authored-By: Claude Opus 4.6 (1M context) --- vendored/libfuse/include/thread_pool.hpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/vendored/libfuse/include/thread_pool.hpp b/vendored/libfuse/include/thread_pool.hpp index 5d29265d..99992a60 100644 --- a/vendored/libfuse/include/thread_pool.hpp +++ b/vendored/libfuse/include/thread_pool.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -731,6 +732,11 @@ ThreadPool::_remove_thread_locked() // via promise. The worker loop checks _tl_should_exit after // running the func and exits cleanly - no separate poison pill // needed, so exactly one thread is removed per call. + // + // The future wait is bounded: if no worker picks up the task + // within 5 seconds (e.g. all workers running long tasks), bail + // with -EBUSY rather than blocking indefinitely while holding + // _scaling_mutex and starving the monitor thread. auto promise = std::make_shared>(); auto func = @@ -742,7 +748,10 @@ ThreadPool::_remove_thread_locked() }; _queue.enqueue_unbounded(std::move(func)); - promise->get_future().wait(); + + auto status = promise->get_future().wait_for(std::chrono::seconds(5)); + if(status == std::future_status::timeout) + return -EBUSY; return 0; }