aboutsummaryrefslogtreecommitdiff
path: root/src/zenhttp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-15 20:56:12 +0100
committerStefan Boberg <[email protected]>2026-03-15 20:56:12 +0100
commit5e487e28988f5dec8acb1e3be51afc2ebfdc1374 (patch)
tree6880f99ef442e51167e1245b997910d261bf8cc9 /src/zenhttp
parentMerge remote-tracking branch 'origin/main' into sb/threadpool (diff)
downloadzen-5e487e28988f5dec8acb1e3be51afc2ebfdc1374.tar.xz
zen-5e487e28988f5dec8acb1e3be51afc2ebfdc1374.zip
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.
Diffstat (limited to 'src/zenhttp')
-rw-r--r--src/zenhttp/servers/iothreadpool.cpp32
-rw-r--r--src/zenhttp/servers/iothreadpool.h6
2 files changed, 36 insertions, 2 deletions
diff --git a/src/zenhttp/servers/iothreadpool.cpp b/src/zenhttp/servers/iothreadpool.cpp
index d180f17f8..1053f0b0c 100644
--- a/src/zenhttp/servers/iothreadpool.cpp
+++ b/src/zenhttp/servers/iothreadpool.cpp
@@ -8,6 +8,7 @@
#if ZEN_PLATFORM_WINDOWS
+# include <algorithm>
# include <thread>
namespace zen {
@@ -187,10 +188,36 @@ ExplicitIoThreadPool::CancelIo()
}
void
+ExplicitIoThreadPool::PruneExitedThreads()
+{
+ // Must be called under m_ThreadListLock
+ if (m_ExitedThreadIds.empty())
+ {
+ return;
+ }
+
+ for (auto It = m_Threads.begin(); It != m_Threads.end();)
+ {
+ auto IdIt = std::find(m_ExitedThreadIds.begin(), m_ExitedThreadIds.end(), It->get_id());
+ if (IdIt != m_ExitedThreadIds.end())
+ {
+ It->join();
+ It = m_Threads.erase(It);
+ m_ExitedThreadIds.erase(IdIt);
+ }
+ else
+ {
+ ++It;
+ }
+ }
+}
+
+void
ExplicitIoThreadPool::SpawnWorkerThread()
{
RwLock::ExclusiveLockScope _(m_ThreadListLock);
+ PruneExitedThreads();
++m_TotalThreads;
m_Threads.emplace_back([this] { WorkerThreadMain(); });
}
@@ -237,6 +264,10 @@ ExplicitIoThreadPool::WorkerThreadMain()
ZEN_LOG_DEBUG(ExplicitIoPoolLog(),
"scaling down I/O thread (idle timeout), {} threads remaining",
CurrentTotal - 1);
+ {
+ RwLock::ExclusiveLockScope _(m_ThreadListLock);
+ m_ExitedThreadIds.push_back(std::this_thread::get_id());
+ }
return; // Thread exits
}
}
@@ -278,6 +309,7 @@ ExplicitIoThreadPool::WorkerThreadMain()
// We already incremented m_TotalThreads, so do the actual spawn
{
RwLock::ExclusiveLockScope _(m_ThreadListLock);
+ PruneExitedThreads();
m_Threads.emplace_back([this] { WorkerThreadMain(); });
}
}
diff --git a/src/zenhttp/servers/iothreadpool.h b/src/zenhttp/servers/iothreadpool.h
index e2c15ba76..f6bfce450 100644
--- a/src/zenhttp/servers/iothreadpool.h
+++ b/src/zenhttp/servers/iothreadpool.h
@@ -92,6 +92,7 @@ public:
private:
void WorkerThreadMain();
void SpawnWorkerThread();
+ void PruneExitedThreads();
HANDLE m_Iocp = nullptr;
@@ -105,8 +106,9 @@ private:
std::atomic<int> m_ActiveCount{0};
std::atomic<bool> m_ShuttingDown{false};
- RwLock m_ThreadListLock;
- std::vector<std::thread> m_Threads;
+ RwLock m_ThreadListLock;
+ std::vector<std::thread> m_Threads;
+ std::vector<std::thread::id> m_ExitedThreadIds;
};
} // namespace zen