From c7d033755c1566f7908e3a66bced6b3c3d062d70 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Fri, 20 Feb 2026 14:43:32 +0100 Subject: added new I/O thread pool implementation the new variant manages a dynamically growing/shrinking set of threads manually instead of relying on the built-in Windows thread pool the benefit of this is that we're in charge of setup and teardown so can make better guarantees about lifetimes of threads which can help with shutdown issues --- src/zenhttp/servers/httpsys.cpp | 53 +++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 20 deletions(-) (limited to 'src/zenhttp/servers/httpsys.cpp') diff --git a/src/zenhttp/servers/httpsys.cpp b/src/zenhttp/servers/httpsys.cpp index c640ba90b..6809c280a 100644 --- a/src/zenhttp/servers/httpsys.cpp +++ b/src/zenhttp/servers/httpsys.cpp @@ -128,6 +128,7 @@ private: bool m_IsAsyncResponseEnabled = true; std::unique_ptr m_IoThreadPool; + bool m_IoThreadPoolIsWinTp = true; RwLock m_AsyncWorkPoolInitLock; WorkerThreadPool* m_AsyncWorkPool = nullptr; @@ -374,7 +375,8 @@ public: void IssueInitialRequest(std::error_code& ErrorCode); bool IssueNextRequest(HttpSysRequestHandler* NewCompletionHandler); - PTP_IO Iocp(); + void StartIo(); + void CancelIo(); HANDLE RequestQueueHandle(); inline OVERLAPPED* Overlapped() { return &m_HttpOverlapped; } inline HttpSysServer& Server() { return m_HttpServer; } @@ -614,9 +616,8 @@ HttpMessageResponseRequest::IssueRequest(std::error_code& ErrorCode) ZEN_TRACE_CPU("httpsys::Response::IssueRequest"); HttpSysTransaction& Tx = Transaction(); HTTP_REQUEST* const HttpReq = Tx.HttpRequest(); - PTP_IO const Iocp = Tx.Iocp(); - StartThreadpoolIo(Iocp); + Tx.StartIo(); // Split payload into batches to play well with the underlying API @@ -822,7 +823,7 @@ HttpMessageResponseRequest::IssueRequest(std::error_code& ErrorCode) // An error occurred, no completion will be posted to IOCP - CancelThreadpoolIo(Iocp); + Tx.CancelIo(); // Emit diagnostics @@ -1001,7 +1002,8 @@ HttpSysServer::HttpSysServer(const HttpSysConfig& InConfig) MaxThreadCount *= 2; } - m_IoThreadPool = std::make_unique(MinThreadCount, MaxThreadCount); + m_IoThreadPoolIsWinTp = !m_InitialConfig.UseExplicitIoThreadPool; + m_IoThreadPool = WinIoThreadPool::Create(!m_IoThreadPoolIsWinTp, MinThreadCount, MaxThreadCount); if (m_InitialConfig.AsyncWorkThreadCount == 0) { @@ -1018,10 +1020,11 @@ HttpSysServer::HttpSysServer(const HttpSysConfig& InConfig) m_IsHttpInitialized = true; m_IsOk = true; - ZEN_INFO("http.sys server started in {} mode, using {}-{} I/O threads and {} async worker threads", + ZEN_INFO("http.sys server started in {} mode, using {}-{} I/O threads ({}) and {} async worker threads", m_InitialConfig.IsDedicatedServer ? "DEDICATED" : "NORMAL", MinThreadCount, MaxThreadCount, + m_InitialConfig.UseExplicitIoThreadPool ? "explicit IOCP" : "Windows Thread Pool", m_InitialConfig.AsyncWorkThreadCount); } @@ -1497,10 +1500,16 @@ HttpSysTransaction::~HttpSysTransaction() { } -PTP_IO -HttpSysTransaction::Iocp() +void +HttpSysTransaction::StartIo() +{ + m_HttpServer.m_IoThreadPool->StartIo(); +} + +void +HttpSysTransaction::CancelIo() { - return m_HttpServer.m_IoThreadPool->Iocp(); + m_HttpServer.m_IoThreadPool->CancelIo(); } HANDLE @@ -1521,7 +1530,6 @@ static std::atomic HttpSysThreadIndex = 0; static void NameCurrentHttpSysThread() { - t_IsHttpSysThreadNamed = true; const int ThreadIndex = ++HttpSysThreadIndex; zen::ExtendableStringBuilder<16> ThreadName; ThreadName << "httpio_" << ThreadIndex; @@ -1538,19 +1546,25 @@ HttpSysTransaction::IoCompletionCallback(PTP_CALLBACK_INSTANCE Instance, { ZEN_UNUSED(Io, Instance); - // Assign names to threads for context - - if (!t_IsHttpSysThreadNamed) - { - NameCurrentHttpSysThread(); - } - // Note that for a given transaction we may be in this completion function on more // than one thread at any given moment. This means we need to be careful about what // happens in here HttpSysTransaction* Transaction = CONTAINING_RECORD(pOverlapped, HttpSysTransaction, m_HttpOverlapped); + // Assign names to threads for context (only needed when using Windows' native + // thread pool) + + if (Transaction->Server().m_IoThreadPoolIsWinTp) + { + if (!t_IsHttpSysThreadNamed) + { + t_IsHttpSysThreadNamed = true; + + NameCurrentHttpSysThread(); + } + } + if (Transaction->HandleCompletion(IoResult, NumberOfBytesTransferred) == HttpSysTransaction::Status::kDone) { delete Transaction; @@ -2020,10 +2034,9 @@ InitialRequestHandler::IssueRequest(std::error_code& ErrorCode) ZEN_TRACE_CPU("httpsys::Request::IssueRequest"); HttpSysTransaction& Tx = Transaction(); - PTP_IO Iocp = Tx.Iocp(); HTTP_REQUEST* HttpReq = Tx.HttpRequest(); - StartThreadpoolIo(Iocp); + Tx.StartIo(); ULONG HttpApiResult; @@ -2057,7 +2070,7 @@ InitialRequestHandler::IssueRequest(std::error_code& ErrorCode) if (HttpApiResult != ERROR_IO_PENDING && HttpApiResult != NO_ERROR) { - CancelThreadpoolIo(Iocp); + Tx.CancelIo(); ErrorCode = MakeErrorCode(HttpApiResult); -- cgit v1.2.3