Loading src/httpd.cpp +28 −4 Original line number Diff line number Diff line Loading @@ -375,10 +375,10 @@ 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 h1OffloadThreads, size_t h2DispatchQueueMax) : netplus::event(serversocket, timeout, idleTimeoutSeconds) { if (h2OffloadThreads > 0) _h2DispatchPool = std::make_unique<netplus::ThreadPool>(h2OffloadThreads); _h2DispatchPool = std::make_unique<netplus::ThreadPool>(h2OffloadThreads, h2DispatchQueueMax); if (h1OffloadThreads > 0) _h1DispatchPool = std::make_unique<netplus::ThreadPool>(h1OffloadThreads); // Bounded like quicDispatchPool() in libnetplus (same rationale: this Loading Loading @@ -542,7 +542,7 @@ 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)); _h2DispatchPool->submit( bool accepted = _h2DispatchPool->submit( [this, connOwner, trBox, sid, tid, args]() mutable { // The ONLY blocking call in this task — touches // *trBox (private to this task) only, never Loading Loading @@ -575,7 +575,31 @@ bool libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, if (cureq2.SendData.pos < cureq2.SendData.size()) netplus::requestWritablePoll(cureq2); }); return true; if (accepted) return true; // 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 // production incident this bound exists to prevent). Answer 503 right here instead, // synchronously, without ever calling RequestEvent -- reclaim tempreq_ptr from // trBox (submit() never queued the lambda that captured it, so trBox's refcount // dropped back to this scope's own copy) and reuse the same // HttpResponse::send()-sets-:res-*-pseudo-headers mechanism sendError() callers rely // on, then finish exactly like the normal synchronous path below. tempreq_ptr = std::move(*trBox); try { HttpResponse busy; busy.setState(HTTP503); busy.setContentType("text/plain"); static const std::string kBusyMsg = "server overloaded, try again shortly"; busy.send(*tempreq_ptr, kBusyMsg, static_cast<int>(kBusyMsg.size())); } catch (const HTTPException &) { // Same "no response produced" fallback _finishH2Dispatch already has for a // RequestEvent that threw above -- sends RST_STREAM(INTERNAL_ERROR) instead of // hanging the stream. } _finishH2Dispatch(cureq, out, sid, std::move(tempreq_ptr), tid, args); return false; } // Connection lookup failed (shouldn't happen while we're still // holding event_mutex for this call) — fall through to the Loading src/httpd.h +8 −1 Original line number Diff line number Diff line Loading @@ -68,9 +68,16 @@ namespace libhttppp { // safely needs a detach/reattach round trip through netplus::detachConnection()/ // reattachConnection() rather than H2's "hand off a throwaway tempreq" trick -- see // _dispatchH1Request's doc comment for the full mechanics. // h2DispatchQueueMax: caps how many H2 streams may be queued in _h2DispatchPool waiting // for a worker (see netplus::ThreadPool's own max_queue_size). 0 (the default) keeps the // pool unbounded for every existing caller -- exactly today's behavior, where a stuck or // saturated pool means every subsequent stream on every connection waits forever with no // 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). HttpEvent(std::vector<netplus::socket*> serversocket,int timeout = 1000, size_t h2OffloadThreads = 0, int idleTimeoutSeconds = 0, size_t h1OffloadThreads = 0); size_t h1OffloadThreads = 0, size_t h2DispatchQueueMax = 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 −4 Original line number Diff line number Diff line Loading @@ -375,10 +375,10 @@ 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 h1OffloadThreads, size_t h2DispatchQueueMax) : netplus::event(serversocket, timeout, idleTimeoutSeconds) { if (h2OffloadThreads > 0) _h2DispatchPool = std::make_unique<netplus::ThreadPool>(h2OffloadThreads); _h2DispatchPool = std::make_unique<netplus::ThreadPool>(h2OffloadThreads, h2DispatchQueueMax); if (h1OffloadThreads > 0) _h1DispatchPool = std::make_unique<netplus::ThreadPool>(h1OffloadThreads); // Bounded like quicDispatchPool() in libnetplus (same rationale: this Loading Loading @@ -542,7 +542,7 @@ 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)); _h2DispatchPool->submit( bool accepted = _h2DispatchPool->submit( [this, connOwner, trBox, sid, tid, args]() mutable { // The ONLY blocking call in this task — touches // *trBox (private to this task) only, never Loading Loading @@ -575,7 +575,31 @@ bool libhttppp::HttpEvent::_dispatchH2Stream(HttpRequest &cureq, if (cureq2.SendData.pos < cureq2.SendData.size()) netplus::requestWritablePoll(cureq2); }); return true; if (accepted) return true; // 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 // production incident this bound exists to prevent). Answer 503 right here instead, // synchronously, without ever calling RequestEvent -- reclaim tempreq_ptr from // trBox (submit() never queued the lambda that captured it, so trBox's refcount // dropped back to this scope's own copy) and reuse the same // HttpResponse::send()-sets-:res-*-pseudo-headers mechanism sendError() callers rely // on, then finish exactly like the normal synchronous path below. tempreq_ptr = std::move(*trBox); try { HttpResponse busy; busy.setState(HTTP503); busy.setContentType("text/plain"); static const std::string kBusyMsg = "server overloaded, try again shortly"; busy.send(*tempreq_ptr, kBusyMsg, static_cast<int>(kBusyMsg.size())); } catch (const HTTPException &) { // Same "no response produced" fallback _finishH2Dispatch already has for a // RequestEvent that threw above -- sends RST_STREAM(INTERNAL_ERROR) instead of // hanging the stream. } _finishH2Dispatch(cureq, out, sid, std::move(tempreq_ptr), tid, args); return false; } // Connection lookup failed (shouldn't happen while we're still // holding event_mutex for this call) — fall through to the Loading
src/httpd.h +8 −1 Original line number Diff line number Diff line Loading @@ -68,9 +68,16 @@ namespace libhttppp { // safely needs a detach/reattach round trip through netplus::detachConnection()/ // reattachConnection() rather than H2's "hand off a throwaway tempreq" trick -- see // _dispatchH1Request's doc comment for the full mechanics. // h2DispatchQueueMax: caps how many H2 streams may be queued in _h2DispatchPool waiting // for a worker (see netplus::ThreadPool's own max_queue_size). 0 (the default) keeps the // pool unbounded for every existing caller -- exactly today's behavior, where a stuck or // saturated pool means every subsequent stream on every connection waits forever with no // 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). HttpEvent(std::vector<netplus::socket*> serversocket,int timeout = 1000, size_t h2OffloadThreads = 0, int idleTimeoutSeconds = 0, size_t h1OffloadThreads = 0); size_t h1OffloadThreads = 0, size_t h2DispatchQueueMax = 0); // Return true to have this stream's RequestEvent run on the H2 // offload thread pool instead of inline in the frame-processing Loading