Coming back to this discussion, starting at the head because this has become a 
bit nested, I try to summarise:

Fact: when the client of a proxied http request aborts the connection (as in 
causing a c->aborted somehow), mod_proxy_http only react to this when writing 
(parts of) a response. The time this takes depends on the responsiveness of the 
backend. Long delay can commonly come from invoking an expensive request (long 
time to compute) or from a long-running request (as in server-side-events, 
SSEs).

Even disregarding SSEs and opinions about its design, we could reduce resource 
waste in our server when we can eliminate those delays.

In mod_proxy_wstunnel we have to monitor frontend and backend connection 
simultaneously due to the nature of the protocol and there the delay will not 
happen. This uses a pollset in 2.4.x and in trunk Yann wrapped that into 
ap_proxy_tunnel_* functions.

It seeems that ap_proxy_tunnel_* should be usable in mod_proxy_http as well. 
Especially when waiting for the status line and when streaming the response 
body. When the frontend connection is HTTP/1.1...

Alas, for HTTP/2, this will not do the trick since H2 secondary connections do 
not really have their own socket for polling. But that is a long-open issue 
which needs addressing. Not that easy, but certainly beneficial to have a PIPE 
socket pair where H2 main connection and workers can at least notify each 
other, if not transfer the actual data over.

Another approach could be to "stutter" the blocking backend reads in 
mod_proxy_http with a 5 sec socket timeout or so, only to check 
frontend->aborted and read again. That might be a minimum effort approach for 
the short term.

Yann, did I get this right? Eric had also the suggestion to only do this on 
certain content types, but that would not solve slow responsiveness.

- Stefan


> Am 01.07.2021 um 16:06 schrieb Stefan Eissing <stefan.eiss...@greenbytes.de>:
> 
> 
> 
>> Am 01.07.2021 um 14:16 schrieb Yann Ylavic <ylavic....@gmail.com>:
>> 
>> On Thu, Jul 1, 2021 at 10:15 AM Stefan Eissing
>> <stefan.eiss...@greenbytes.de> wrote:
>>> 
>>>> Am 30.06.2021 um 18:01 schrieb Eric Covener <cove...@gmail.com>:
>>>> 
>>>> On Wed, Jun 30, 2021 at 11:46 AM Stefan Eissing
>>>> <stefan.eiss...@greenbytes.de> wrote:
>>>>> 
>>>>> It looks like we stumbled upon an issue in 
>>>>> https://bz.apache.org/bugzilla/show_bug.cgi?id=65402 which concerns the 
>>>>> life times of our backend connections.
>>>>> 
>>>>> When a frontend connection causes a backend request and drops, our 
>>>>> backend connection only notifies the loss when it attempts to pass some 
>>>>> data. In normal http response processing, this is not an issue since 
>>>>> response chunks are usually coming in quite frequently. Then the proxied 
>>>>> connection will fail to pass it to an aborted frontend connection and 
>>>>> cleanup will occur.
>>>>> 
>>>>> However, with such modern shenanigans such as Server Side Events (SSE), 
>>>>> the request is supposed to be long running and will produce body chunks 
>>>>> quite infrequently, like every 30 seconds or so. This leaves our proxy 
>>>>> workers hanging in recv for quite a while and may lead to worker 
>>>>> exhaustion.
>>>>> 
>>>>> We can say SSE is a bad idea anyway, but that will probably not stop 
>>>>> people from doing such crazy things.
>>>>> 
>>>>> What other mitigations do we have?
>>>>> - pthread_kill() will interrupt the recv and probably make it fail
>>>>> - we can use shorter socket timeouts on backend and check r->connection 
>>>>> status in between
>>>>> - ???
>> 
>> Can mod_proxy_http2 do better here? I suppose we lose all
>> relationships in h2->h1 and then h1->h2, asking just in case..
> 
> I have not tested, but my guess is that it goes into a blocking read on the 
> backend as well, since there is nothing it wants to send when a response body 
> is incoming.
> 
>>>> 
>>>> 
>>>> In trunk the tunnelling side of mod_proxy_http can go async and get
>>>> called back for activity on either side by asking Event to watch both
>>>> sockets.
>>> 
>>> 
>>> How does that work, actually? Do we have an example somewhere?
>> 
>> This is the ap_proxy_tunnel_create() and ap_proxy_tunnel_run()
>> called/used by mod_proxy_http for Upgrade(d) protocols.
>> 
>> I'm thinking to improve this interface to have a hook called in
>> ap_proxy_transfer_between_connections() with the data being forwarded
>> from one side to the other (in/out connection), and the hook could
>> decide to let the data pass, or retain them, and/or switch to
>> speculative mode, and/or remove/add one side/sense from the pollset,
>> or abort, or.. The REALLY_LAST hook would be something like the
>> existing ap_proxy_buckets_lifetime_transform().
>> 
>> The hook(s) would be responsible (each) of their connections' states,
>> mod_proxy_http could then be implemented fully async in a callback,
>> but I suppose mod_h2 could hook itself there too if it has something
>> to care about.
> 
> Just had a glimpse and it looks interesting, not only for "real" backends but 
> maybe also for handling h2 workers from a main connection. See below.
> 
>> 
>>> 
>>>> I'm not sure how browsers treat the SSE connection, can it ever have a
>>>> subsequent request?  If not, maybe we could see the SSE Content-Type
>>>> and shoehorn it into the tunneling (figuring out what to do with
>>>> writes from the client, backport the event and async tunnel stuff?)
>>> 
>>> I don't think they will do a subsequent request in the HTTP/1.1 sense,
>>> meaning they'll close their H1 connection and open a new one. In H2 land,
>>> the request connection is a virtual "secondary" one away.
>> 
>> The issue I see here for inbound h2 is that the tunneling loop needs
>> something to poll() on both sides, and there is no socket on the h2
>> slave connections to do that.. How to poll a h2 stream, pipe or
>> something?
> 
> More "something". The plan to switch the current polling to something
> pipe based has long stalled. Mainly due to lack of time, but also missing
> a bright idea how the server in general should handle such constructs.
> 
>> 
>>> 
>>> But changing behaviour based on the content type seems inadequate. When
>>> the server proxies applications (like uwsgi), the problem may also happen
>>> to requests that are slow producing responses.
>>> 
>>> To DoS such a setup, where a proxied response takes n seconds, you'd need
>>> total_workers / n aborted requests per second. In HTTP/1.1 that would
>>> all be connections and maybe noticeable from a supervisor, but in H2 this
>>> could happen all on the same tcp connection (although our h2 implementation
>>> has some protection against abusive client behaviour).
>>> 
>>> A general solution to the problem would therefore be valuable, imo.
>> 
>> The general/generic solution for anything proxy could be the tunneling
>> loop, a bit like a proxy_tcp (or proxy_transport) module to hook to.
>> 
>>> 
>>> We should think about solving this in the context of mpm_event, which
>>> I believe is the production recommended setup that merits our efforts.
>> 
>> Yes, the tunneling loop stops and the poll()ing is deferred to MPM
>> event (the ap_hook_mpm_register_poll_callback*() API) when nothing
>> comes from either side for an AsyncDelay.
>> 
>>> 
>>> If mpm_event could make the link between one connection to another,
>>> like frontend to backend, it could wake up backends on a frontend
>>> termination. Do you agree, Yann?
>> 
>> Absolutely, but there's more work to be done to get there :)
>> 
>> Also, is this kind of architecture what we really want?
>> Ideas, criticisms and discussions welcome!
>> 
>>> 
>>> Could this be as easy as adding another "conn_rec *context" field
>>> in conn_rec that tracks this?
>> 
>> Tracking some connection close (at transport level) on the client side
>> to "abort" the transaction is not enough, a connection can be
>> half-closed and still want some response back for instance.
>> 
>> We want something that says abort (like h2 RST_STREAM), no such thing
>> in transactional HTTP/1 (AFAICT), that's why mod_h2 would need to hook
>> in the tunnel if it wanted to abort the loop (IIUC).
> 
> Makes sense. There is also the H2 stream state which goes into 
> HALF_CLOSED_REMOTE when the request is fully sent and a response 
> is expected to come until the server closes its side of the stream.
> 
> The TLS CLOSE_NOTIFY would be an equivalent signal available on a http/1.1
> https: connection, I assume.
> 
> Cheers, Stefan
>> 
>> 
>> Cheers;
>> Yann.

Reply via email to