From 5e487e28988f5dec8acb1e3be51afc2ebfdc1374 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Sun, 15 Mar 2026 20:56:12 +0100 Subject: Fix thread vector growth in dynamic thread pools Exiting threads now register their ID before returning, and PruneExitedThreads() joins and removes them on the next scale-up, preventing unbounded vector growth through repeated scale cycles. --- src/zencore/workthreadpool.cpp | 55 +++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 11 deletions(-) (limited to 'src/zencore/workthreadpool.cpp') diff --git a/src/zencore/workthreadpool.cpp b/src/zencore/workthreadpool.cpp index 25a1d982a..b179527d7 100644 --- a/src/zencore/workthreadpool.cpp +++ b/src/zencore/workthreadpool.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -195,17 +196,19 @@ struct WorkerThreadPool::ThreadStartInfo struct ExplicitImpl : WorkerThreadPool::Impl { - const int m_MinThreads; - const int m_MaxThreads; - std::atomic m_TotalThreads{0}; - std::atomic m_ActiveCount{0}; - void WorkerThreadFunction(WorkerThreadPool::ThreadStartInfo Info); - void SpawnWorkerThread(); - std::string m_WorkerThreadBaseName; - RwLock m_ThreadListLock; - std::vector m_WorkerThreads; - BlockingQueue> m_WorkQueue; - std::atomic m_FreeWorkerCount{0}; + const int m_MinThreads; + const int m_MaxThreads; + std::atomic m_TotalThreads{0}; + std::atomic m_ActiveCount{0}; + void WorkerThreadFunction(WorkerThreadPool::ThreadStartInfo Info); + void SpawnWorkerThread(); + void PruneExitedThreads(); + std::string m_WorkerThreadBaseName; + RwLock m_ThreadListLock; + std::vector m_WorkerThreads; + std::vector m_ExitedThreadIds; + BlockingQueue> m_WorkQueue; + std::atomic m_FreeWorkerCount{0}; bool ScalingEnabled() const { return m_MinThreads != m_MaxThreads; } @@ -286,6 +289,31 @@ struct ExplicitImpl : WorkerThreadPool::Impl } }; +void +ExplicitImpl::PruneExitedThreads() +{ + // Must be called under m_ThreadListLock + if (m_ExitedThreadIds.empty()) + { + return; + } + + for (auto It = m_WorkerThreads.begin(); It != m_WorkerThreads.end();) + { + auto IdIt = std::find(m_ExitedThreadIds.begin(), m_ExitedThreadIds.end(), It->get_id()); + if (IdIt != m_ExitedThreadIds.end()) + { + It->join(); + It = m_WorkerThreads.erase(It); + m_ExitedThreadIds.erase(IdIt); + } + else + { + ++It; + } + } +} + void ExplicitImpl::SpawnWorkerThread() { @@ -293,6 +321,7 @@ ExplicitImpl::SpawnWorkerThread() const int ThreadNumber = ++s_DynamicThreadIndex; RwLock::ExclusiveLockScope _(m_ThreadListLock); + PruneExitedThreads(); m_WorkerThreads.emplace_back(&ExplicitImpl::WorkerThreadFunction, this, WorkerThreadPool::ThreadStartInfo{ThreadNumber, nullptr}); } @@ -361,6 +390,10 @@ ExplicitImpl::WorkerThreadFunction(WorkerThreadPool::ThreadStartInfo Info) m_WorkerThreadBaseName, CurrentTotal - 1); m_FreeWorkerCount--; + { + RwLock::ExclusiveLockScope _(m_ThreadListLock); + m_ExitedThreadIds.push_back(std::this_thread::get_id()); + } return; // Thread exits } } -- cgit v1.2.3