Commit 5b47b3ba authored by jan.koester's avatar jan.koester
Browse files

test

parent 588d9a40
Loading
Loading
Loading
Loading
+9 −17
Changes for src/http/HttpProxyServer.cpp: 9 added lines, 17 removed lines.
Original line number Diff line number Diff line
@@ -117,24 +117,16 @@ HttpProxyController::HttpProxyController(std::vector<netplus::socket *> serverSo
                                          http::UpstreamConnectionPool &connectionPool,
                                          tls::AcmeChallengeStore *acmeChallenges,
                                          int idleTimeoutSeconds)
    // h2OffloadThreads/h1OffloadThreads non-zero (see shouldOffloadH2Dispatch/
    // shouldOffloadH1Dispatch overrides below): every RequestEvent here does a real backend
    // round-trip (HttpClient::Get/Post/etc., possibly against a slow/overloaded upstream) and
    // would otherwise run synchronously on this listener's fixed, hardware_concurrency()-sized
    // worker pool -- shared across every domain/route on the same listener, not just the one
    // being proxied. Left at 0 (fully synchronous) this meant a handful of slow backend
    // requests (observed live: a crawler hammering a proxied git server's /users/sign_in and
    // deep commit/blame history) could tie up every worker thread at once, making completely
    // unrelated traffic on the same listener (e.g. a WebSocket upgrade to a different backend)
    // appear to hang -- see [[project_proxyplus_tls_hang_recurrence_20260816]]. H2 streams
    // always went through _h2DispatchPool; H1 requests (including a WebSocket upgrade's
    // blocking connect+handshake read) had no such option until _dispatchH1Request/
    // detachConnection+reattachConnection existed to make it safe for H1's one-request-per-
    // connection model. Both pools sized like h3StreamPool (libhttppp/src/httpd.cpp) and
    // quicDispatchPool() (libnetplus/src/quic.cpp): oversubscribed 2x hardware_concurrency()
    // since this work is dominated by waiting on the network, not CPU.
    // Keep H2 dispatch synchronous for now. Enabling libhttppp's H2 offload pool makes an
    // otherwise healthy proxied response remain queued after RequestEvent returns: the client
    // completes TLS/ALPN and sends its stream, but receives no response frames. HTTP/1.1
    // continues to work against the same route/upstream, which makes this especially damaging
    // for browsers (they normally negotiate h2). H1 offloading is independent and remains
    // enabled so slow backend round-trips and WebSocket handshakes do not occupy the shared
    // listener workers. Re-enable H2 offloading only after its response handoff/flush path has
    // an integration test that observes the response on the client socket.
    : libhttppp::HttpEvent(std::move(serverSockets), /*timeout=*/1000,
                            /*h2OffloadThreads=*/(std::max<size_t>)(4, std::thread::hardware_concurrency() * 2),
                            /*h2OffloadThreads=*/0,
                            idleTimeoutSeconds,
                            /*h1OffloadThreads=*/(std::max<size_t>)(4, std::thread::hardware_concurrency() * 2))
    , _listenerLabel(std::move(listenerLabel))
+3 −13
Changes for src/http/HttpProxyServer.h: 3 added lines, 13 removed lines.
Original line number Diff line number Diff line
@@ -42,19 +42,9 @@ public:

    void RequestEvent(libhttppp::HttpRequest &curreq, const int tid, ULONG_PTR args) override;

    // Always offload: RequestEvent's real work here (see the constructor's h2OffloadThreads
    // comment in HttpProxyServer.cpp) is a backend network round-trip for every route this
    // class ever forwards, so there's no synchronous-vs-offload split to make per-route --
    // unlike libhttppp::HttpEvent's own base default (which assumes most callers have a mix
    // of cheap and slow routes), a reverse proxy doesn't have a "cheap" H2 stream to keep off
    // the offload pool.
    bool shouldOffloadH2Dispatch(libhttppp::HttpRequest &tempreq, uint32_t streamId) const override {
        return true;
    }

    // Same rationale as shouldOffloadH2Dispatch() above, for HTTP/1.x: every route here does a
    // real backend round-trip, and a WebSocket upgrade's initial connect+handshake read (see
    // WebSocketProxy.cpp's handleWebSocketUpgrade) is itself dispatched through this same H1
    // Every route here does a real backend round-trip, and a WebSocket upgrade's initial
    // connect+handshake read (see WebSocketProxy.cpp's handleWebSocketUpgrade) is itself
    // dispatched through this same H1
    // RequestEvent() path -- without this, that blocking work ran directly on one of the
    // process's few shared epoll/kqueue worker threads, the same class of thread-starvation
    // bug tracked in [[project_proxyplus_tls_hang_recurrence_20260816]] (a burst of slow H1/H2