Loading src/httpd.cpp +26 −8 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ #include <string> #include <thread> #include <chrono> #include <algorithm> #include <netplus/socket.h> #include <netplus/exception.h> Loading Loading @@ -356,6 +357,12 @@ libhttppp::HttpEvent::HttpEvent(std::vector<netplus::socket*> serversocket, int : netplus::event(serversocket, timeout) { if (h2OffloadThreads > 0) _h2DispatchPool = std::make_unique<netplus::ThreadPool>(h2OffloadThreads); // Bounded like quicDispatchPool() in libnetplus (same rationale: this // work is dominated by waiting on upstream/network, not CPU, so a // generous multiple of core count is cheap) -- caps how many OS // threads a burst of concurrent streaming H3 responses can spawn. _h3StreamPool = std::make_unique<netplus::ThreadPool>( (std::max<unsigned>)(4, std::thread::hardware_concurrency() * 2)); // Set the HTTP/3 stream callback on QUIC server sockets so that // child connections created by accept() inherit it. The event loop // never creates a con / calls ConnectEvent for QUIC children, Loading Loading @@ -1687,15 +1694,26 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, return; } // Event-driven streaming: move tempreq to a background // thread (like H2's _resumeH2Streams) so we don't block // the QUIC callback. The thread calls ResponseEvent in // a loop, sending DATA frames as upstream produces data, // with proper sleeps between empty iterations. // Event-driven streaming: move tempreq onto the bounded H3 // stream pool (like H2's _resumeH2Streams / _h2DispatchPool) // so we don't block the QUIC callback -- and, critically, // so a burst of concurrent streaming responses can't spawn // an unbounded pile of raw OS threads all hammering the // shared QUIC listener socket via pumpIncoming() below (that // starved the socket's own drain thread in production). The // task calls ResponseEvent in a loop, sending DATA frames as // upstream produces data, with proper sleeps between empty // iterations. tempreq.SendData.pos = 0; std::thread([this, q, stream_id, h3tid, tr = std::move(tempreq_ptr), // std::function (what ThreadPool::submit takes) requires its // target to be copy-constructible even though it's only ever // moved/invoked here -- see _dispatchH2Stream's identical // trBox workaround for a move-only unique_ptr capture. auto trBox = std::make_shared<std::unique_ptr<HttpRequest>>(std::move(tempreq_ptr)); _h3StreamPool->submit([this, q, stream_id, h3tid, trBox, content_length, total_sent]() mutable { HttpRequest *tr = trBox->get(); size_t sent = total_sent; size_t empty_streak = 0; static constexpr size_t max_empty = 500; Loading Loading @@ -1768,7 +1786,7 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, // handling above. q->resetStream(stream_id, 0x0102); // H3_INTERNAL_ERROR } }).detach(); }); return; } Loading src/httpd.h +7 −0 Original line number Diff line number Diff line Loading @@ -139,6 +139,13 @@ namespace libhttppp { // constructor. See shouldOffloadH2Dispatch / _dispatchH2Stream. std::unique_ptr<netplus::ThreadPool> _h2DispatchPool; // Always constructed (unlike _h2DispatchPool, which is opt-in): // every streaming H3 response needs somewhere to run its // continuation loop (see Http3StreamEvent). Bounded so a burst of // concurrent large/slow responses spawns at most this many OS // threads instead of one raw detached thread per response. std::unique_ptr<netplus::ThreadPool> _h3StreamPool; // Returns true if the stream was handed off to the background // offload pool instead of being finished synchronously — callers // must treat that as "progress" for reprocess-loop purposes even Loading Loading
src/httpd.cpp +26 −8 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ #include <string> #include <thread> #include <chrono> #include <algorithm> #include <netplus/socket.h> #include <netplus/exception.h> Loading Loading @@ -356,6 +357,12 @@ libhttppp::HttpEvent::HttpEvent(std::vector<netplus::socket*> serversocket, int : netplus::event(serversocket, timeout) { if (h2OffloadThreads > 0) _h2DispatchPool = std::make_unique<netplus::ThreadPool>(h2OffloadThreads); // Bounded like quicDispatchPool() in libnetplus (same rationale: this // work is dominated by waiting on upstream/network, not CPU, so a // generous multiple of core count is cheap) -- caps how many OS // threads a burst of concurrent streaming H3 responses can spawn. _h3StreamPool = std::make_unique<netplus::ThreadPool>( (std::max<unsigned>)(4, std::thread::hardware_concurrency() * 2)); // Set the HTTP/3 stream callback on QUIC server sockets so that // child connections created by accept() inherit it. The event loop // never creates a con / calls ConnectEvent for QUIC children, Loading Loading @@ -1687,15 +1694,26 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, return; } // Event-driven streaming: move tempreq to a background // thread (like H2's _resumeH2Streams) so we don't block // the QUIC callback. The thread calls ResponseEvent in // a loop, sending DATA frames as upstream produces data, // with proper sleeps between empty iterations. // Event-driven streaming: move tempreq onto the bounded H3 // stream pool (like H2's _resumeH2Streams / _h2DispatchPool) // so we don't block the QUIC callback -- and, critically, // so a burst of concurrent streaming responses can't spawn // an unbounded pile of raw OS threads all hammering the // shared QUIC listener socket via pumpIncoming() below (that // starved the socket's own drain thread in production). The // task calls ResponseEvent in a loop, sending DATA frames as // upstream produces data, with proper sleeps between empty // iterations. tempreq.SendData.pos = 0; std::thread([this, q, stream_id, h3tid, tr = std::move(tempreq_ptr), // std::function (what ThreadPool::submit takes) requires its // target to be copy-constructible even though it's only ever // moved/invoked here -- see _dispatchH2Stream's identical // trBox workaround for a move-only unique_ptr capture. auto trBox = std::make_shared<std::unique_ptr<HttpRequest>>(std::move(tempreq_ptr)); _h3StreamPool->submit([this, q, stream_id, h3tid, trBox, content_length, total_sent]() mutable { HttpRequest *tr = trBox->get(); size_t sent = total_sent; size_t empty_streak = 0; static constexpr size_t max_empty = 500; Loading Loading @@ -1768,7 +1786,7 @@ void libhttppp::HttpEvent::Http3StreamEvent(netplus::socket *sock, // handling above. q->resetStream(stream_id, 0x0102); // H3_INTERNAL_ERROR } }).detach(); }); return; } Loading
src/httpd.h +7 −0 Original line number Diff line number Diff line Loading @@ -139,6 +139,13 @@ namespace libhttppp { // constructor. See shouldOffloadH2Dispatch / _dispatchH2Stream. std::unique_ptr<netplus::ThreadPool> _h2DispatchPool; // Always constructed (unlike _h2DispatchPool, which is opt-in): // every streaming H3 response needs somewhere to run its // continuation loop (see Http3StreamEvent). Bounded so a burst of // concurrent large/slow responses spawns at most this many OS // threads instead of one raw detached thread per response. std::unique_ptr<netplus::ThreadPool> _h3StreamPool; // Returns true if the stream was handed off to the background // offload pool instead of being finished synchronously — callers // must treat that as "progress" for reprocess-loop purposes even Loading