Loading src/httpd.cpp +28 −3 Original line number Diff line number Diff line Loading @@ -375,12 +375,13 @@ static std::vector<uint8_t> h3BuildResponse(uint16_t status_code, libhttppp::HttpEvent::HttpEvent(std::vector<netplus::socket*> serversocket, int timeout, size_t h2OffloadThreads, int idleTimeoutSeconds, size_t h1OffloadThreads, size_t h2DispatchQueueMax) size_t h1OffloadThreads, size_t h2DispatchQueueMax, size_t h1DispatchQueueMax) : netplus::event(serversocket, timeout, idleTimeoutSeconds) { if (h2OffloadThreads > 0) _h2DispatchPool = std::make_unique<netplus::ThreadPool>(h2OffloadThreads, h2DispatchQueueMax); if (h1OffloadThreads > 0) _h1DispatchPool = std::make_unique<netplus::ThreadPool>(h1OffloadThreads); _h1DispatchPool = std::make_unique<netplus::ThreadPool>(h1OffloadThreads, h1DispatchQueueMax); // 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 Loading Loading @@ -542,8 +543,27 @@ bool libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, // stays effectively unique since nothing else ever sees this // particular shared_ptr. auto trBox = std::make_shared<std::unique_ptr<HttpRequest>>(std::move(tempreq_ptr)); // Marks this connection as having offloaded work in flight for the idle reaper // (see netplus::con::hasPendingOps()) -- the reaper's only other signal, // lasteventime, is never refreshed by this background task's own write below // (_flushSendDataNow doesn't touch it), so without this a connection that's // otherwise quiet while one stream is slowly rendering looks idle and gets // closed out from under the still-running task: proxyplus sends its FIN first // while the peer is still legitimately waiting on the response, and the peer // never sends its own FIN back (FIN_WAIT_2 buildup -- see the 08-16 netstat lead). connOwner->slots[0].PendingOps.fetch_add(1, std::memory_order_relaxed); bool accepted = _h2DispatchPool->submit( [this, connOwner, trBox, sid, tid, args]() mutable { // Always runs, on every exit path below (including the early // "torn down while blocked" return), so PendingOps can't be left // incremented forever if the connection is legitimately reaped // right after this task finishes. struct PendingOpGuard { netplus::con::ConSlot *slot; ~PendingOpGuard() { slot->PendingOps.fetch_sub(1, std::memory_order_relaxed); } } pendingGuard{&connOwner->slots[0]}; // The ONLY blocking call in this task — touches // *trBox (private to this task) only, never // cureq/h2state(). A backend call inside RequestEvent Loading Loading @@ -577,6 +597,11 @@ bool libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, }); if (accepted) return true; // Rejected -- the lambda above (and its PendingOpGuard) will never run, so undo // the increment ourselves or PendingOps would sit above zero forever and this // connection would never be idle-reaped again. connOwner->slots[0].PendingOps.fetch_sub(1, std::memory_order_relaxed); // Pool is already at h2DispatchQueueMax pending tasks -- every worker is presumably // busy with something slow (or stuck) and piling this stream up behind them too // would just be another request nobody notices is waiting forever (the exact Loading Loading @@ -640,7 +665,7 @@ void libhttppp::HttpEvent::_dispatchH1Request(HttpRequest &cureq, size_t consume // _dispatchH2Stream's trBox below. auto sockBox = std::make_shared<std::unique_ptr<netplus::socket>>(std::move(sock)); _h1DispatchPool->submit( bool accepted = _h1DispatchPool->submit( [this, connOwner, sockBox, consumeBodyBytes, tid, args]() mutable { HttpRequest &cureq2 = static_cast<HttpRequest&>(*connOwner); // Safe without event_mutex: nothing else can observe this connection while Loading src/httpd.h +12 −1 Original line number Diff line number Diff line Loading @@ -75,9 +75,20 @@ namespace libhttppp { // way to notice. A positive value makes _dispatchH2Stream respond 503 immediately once // the pool is this full instead of queuing behind it. Has no effect if h2OffloadThreads // is 0 (nothing to bound). // h1DispatchQueueMax: H1 analogue of h2DispatchQueueMax, bounding _h1DispatchPool (see // netplus::ThreadPool's own max_queue_size). 0 (the default) keeps it unbounded for // every existing caller. Before this existed, a saturated _h1DispatchPool left every // subsequent offloaded request's socket detached from the event loop (invisible to the // idle reaper) and un-timed until a worker finally freed up -- if the peer gave up // first, proxyplus discovered it far too late and started its own close against a // peer that might already be gone, producing sockets stuck in LAST_ACK. A positive // value makes _dispatchH1Request answer 503 immediately (without ever calling the // possibly-slow RequestEvent) once the pool is this full, mirroring H2's overload // response. Has no effect if h1OffloadThreads is 0. HttpEvent(std::vector<netplus::socket*> serversocket,int timeout = 1000, size_t h2OffloadThreads = 0, int idleTimeoutSeconds = 0, size_t h1OffloadThreads = 0, size_t h2DispatchQueueMax = 0); size_t h1OffloadThreads = 0, size_t h2DispatchQueueMax = 0, size_t h1DispatchQueueMax = 0); // Return true to have this stream's RequestEvent run on the H2 // offload thread pool instead of inline in the frame-processing Loading Loading
src/httpd.cpp +28 −3 Original line number Diff line number Diff line Loading @@ -375,12 +375,13 @@ static std::vector<uint8_t> h3BuildResponse(uint16_t status_code, libhttppp::HttpEvent::HttpEvent(std::vector<netplus::socket*> serversocket, int timeout, size_t h2OffloadThreads, int idleTimeoutSeconds, size_t h1OffloadThreads, size_t h2DispatchQueueMax) size_t h1OffloadThreads, size_t h2DispatchQueueMax, size_t h1DispatchQueueMax) : netplus::event(serversocket, timeout, idleTimeoutSeconds) { if (h2OffloadThreads > 0) _h2DispatchPool = std::make_unique<netplus::ThreadPool>(h2OffloadThreads, h2DispatchQueueMax); if (h1OffloadThreads > 0) _h1DispatchPool = std::make_unique<netplus::ThreadPool>(h1OffloadThreads); _h1DispatchPool = std::make_unique<netplus::ThreadPool>(h1OffloadThreads, h1DispatchQueueMax); // 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 Loading Loading @@ -542,8 +543,27 @@ bool libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, // stays effectively unique since nothing else ever sees this // particular shared_ptr. auto trBox = std::make_shared<std::unique_ptr<HttpRequest>>(std::move(tempreq_ptr)); // Marks this connection as having offloaded work in flight for the idle reaper // (see netplus::con::hasPendingOps()) -- the reaper's only other signal, // lasteventime, is never refreshed by this background task's own write below // (_flushSendDataNow doesn't touch it), so without this a connection that's // otherwise quiet while one stream is slowly rendering looks idle and gets // closed out from under the still-running task: proxyplus sends its FIN first // while the peer is still legitimately waiting on the response, and the peer // never sends its own FIN back (FIN_WAIT_2 buildup -- see the 08-16 netstat lead). connOwner->slots[0].PendingOps.fetch_add(1, std::memory_order_relaxed); bool accepted = _h2DispatchPool->submit( [this, connOwner, trBox, sid, tid, args]() mutable { // Always runs, on every exit path below (including the early // "torn down while blocked" return), so PendingOps can't be left // incremented forever if the connection is legitimately reaped // right after this task finishes. struct PendingOpGuard { netplus::con::ConSlot *slot; ~PendingOpGuard() { slot->PendingOps.fetch_sub(1, std::memory_order_relaxed); } } pendingGuard{&connOwner->slots[0]}; // The ONLY blocking call in this task — touches // *trBox (private to this task) only, never // cureq/h2state(). A backend call inside RequestEvent Loading Loading @@ -577,6 +597,11 @@ bool libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, }); if (accepted) return true; // Rejected -- the lambda above (and its PendingOpGuard) will never run, so undo // the increment ourselves or PendingOps would sit above zero forever and this // connection would never be idle-reaped again. connOwner->slots[0].PendingOps.fetch_sub(1, std::memory_order_relaxed); // Pool is already at h2DispatchQueueMax pending tasks -- every worker is presumably // busy with something slow (or stuck) and piling this stream up behind them too // would just be another request nobody notices is waiting forever (the exact Loading Loading @@ -640,7 +665,7 @@ void libhttppp::HttpEvent::_dispatchH1Request(HttpRequest &cureq, size_t consume // _dispatchH2Stream's trBox below. auto sockBox = std::make_shared<std::unique_ptr<netplus::socket>>(std::move(sock)); _h1DispatchPool->submit( bool accepted = _h1DispatchPool->submit( [this, connOwner, sockBox, consumeBodyBytes, tid, args]() mutable { HttpRequest &cureq2 = static_cast<HttpRequest&>(*connOwner); // Safe without event_mutex: nothing else can observe this connection while Loading
src/httpd.h +12 −1 Original line number Diff line number Diff line Loading @@ -75,9 +75,20 @@ namespace libhttppp { // way to notice. A positive value makes _dispatchH2Stream respond 503 immediately once // the pool is this full instead of queuing behind it. Has no effect if h2OffloadThreads // is 0 (nothing to bound). // h1DispatchQueueMax: H1 analogue of h2DispatchQueueMax, bounding _h1DispatchPool (see // netplus::ThreadPool's own max_queue_size). 0 (the default) keeps it unbounded for // every existing caller. Before this existed, a saturated _h1DispatchPool left every // subsequent offloaded request's socket detached from the event loop (invisible to the // idle reaper) and un-timed until a worker finally freed up -- if the peer gave up // first, proxyplus discovered it far too late and started its own close against a // peer that might already be gone, producing sockets stuck in LAST_ACK. A positive // value makes _dispatchH1Request answer 503 immediately (without ever calling the // possibly-slow RequestEvent) once the pool is this full, mirroring H2's overload // response. Has no effect if h1OffloadThreads is 0. HttpEvent(std::vector<netplus::socket*> serversocket,int timeout = 1000, size_t h2OffloadThreads = 0, int idleTimeoutSeconds = 0, size_t h1OffloadThreads = 0, size_t h2DispatchQueueMax = 0); size_t h1OffloadThreads = 0, size_t h2DispatchQueueMax = 0, size_t h1DispatchQueueMax = 0); // Return true to have this stream's RequestEvent run on the H2 // offload thread pool instead of inline in the frame-processing Loading