Skip to content

Close inactive connections and requests - #425

Open
WyriHaximus wants to merge 2 commits into
reactphp:1.xfrom
WyriHaximus-labs:close-inactive-requests
Open

Close inactive connections and requests#425
WyriHaximus wants to merge 2 commits into
reactphp:1.xfrom
WyriHaximus-labs:close-inactive-requests

Conversation

@WyriHaximus

@WyriHaximusWyriHaximus commented Sep 1, 2021

Copy link
Copy Markdown
Member

This builds on top of #405 and further builds out #423 by also
close connections with inactive requests.

Closes: #423

@WyriHaximusWyriHaximus added this to the v1.6.0 milestone Sep 1, 2021
@WyriHaximusWyriHaximus changed the title Close inactive connectionsClose inactive requestsSep 1, 2021
@WyriHaximusWyriHaximus mentioned this pull request Sep 1, 2021
@mmoreram

Copy link
Copy Markdown

@WyriHaximus is there any chance you can merge this PR? Seems to be tested, and increases so much the memory performance :)

@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch 2 times, most recently from 7b3f5b6 to 0ddb503CompareNovember 10, 2021 07:13
@WyriHaximus

Copy link
Copy Markdown
MemberAuthor

@WyriHaximus Thanks for the update!

Can you provide a quick update here, what's your plan for this feature with regards to #425? We've briefly discussed (off channel) that this should eventually send a 408 Request Timeout response for the initial connection but not for keep-alive connections that send no following request. I agree breaking this up into smaller PRs can be useful, but it looks like this might also work in a single PR, so I'll leave this up to you. Let me know if there's anything I can help with and/or if you want me to take over from here +1

@clue Right, been thinking about this, and doesn't it make sense to do this with all requests in flight, instead of only on the first?

@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch from 0ddb503 to 4af0447CompareNovember 10, 2021 07:23
@WyriHaximus

Copy link
Copy Markdown
MemberAuthor

@WyriHaximus is there any chance you can merge this PR? Seems to be tested, and increases so much the memory performance :)

@mmoreram There are a few details we need to polish before this PR is done and it can be merged. Like the 408 responses for when an inflight request times out. And ensuring we don't cut off websocket connections with this.

@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch from 4af0447 to 8d4d62cCompareNovember 10, 2021 07:28
@WyriHaximus

Copy link
Copy Markdown
MemberAuthor

@WyriHaximus Thanks for the update!
Can you provide a quick update here, what's your plan for this feature with regards to #425? We've briefly discussed (off channel) that this should eventually send a 408 Request Timeout response for the initial connection but not for keep-alive connections that send no following request. I agree breaking this up into smaller PRs can be useful, but it looks like this might also work in a single PR, so I'll leave this up to you. Let me know if there's anything I can help with and/or if you want me to take over from here +1

@clue Right, been thinking about this, and doesn't it make sense to do this with all requests in flight, instead of only on the first?

Just made this change, the code is cleaner as well. I Will check the relevant RFC's later today, but until that my position is that any request in a keep alive connection can timeout for any number of reasons.

@clueclue removed this from the v1.6.0 milestone Feb 3, 2022
Comment threadsrc/Io/StreamingServer.php Outdated

$this->callback = $requestHandler;
$this->parser = new RequestHeaderParser();
$this->parser = new RequestHeaderParser($this->loop, $this->idleConnectionTimeout);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes me wonder if we should expose the RequestHeaderParser instead, given we only use the timeout value to pass it forward. (Refs #359)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in also make it possible to inject a different header parser? We could do that so we can have a C based extension for parser to speed it up?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KISS: The StreamingServer is marked @internal, as is the RequestHeaderParser. This means we can easily inject the RequestHeaderParser to the StreamingServer inside the Server class. Constructor may have to be changed, but doesn't affect BC, because the public Server interface is not affected (at least not as part of this change).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm fully aware, I'm also thinking ahead. But maybe, if we want to go there, we should ship that C based extension to speed it up

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is currently our hot path, but if we have evidence an optional extension will speed things up, I'm all for it 👍 I think we're on the same page here and should probably leave this up for a follow-up PR?

In either case I think your changes are a welcome improvement as is and injecting the RequestHeaderParser into the StreamingServer can help simplify the dependency injection both now and longer-term 👍

@WyriHaximusWyriHaximusMar 21, 2022

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is currently our hot path, but if we have evidence an optional extension will speed things up, I'm all for it 👍 I think we're on the same page here and should probably leave this up for a follow-up PR?

Of course, that would be a follow-up PR. And we have tons of other things to do.

In either case I think your changes are a welcome improvement as is and injecting the RequestHeaderParser into the StreamingServer can help simplify the dependency injection both now and longer-term 👍

Sounds good! 🚢 🇮🇹 !

(Will fix that thing below this now.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's my understanding the HttpServer should be simplified to call something like new StreamingServer($middleware, new RequestHeaderParser($loop, $idleConnectionTimeout)).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took some work, but I think it's worth the refactoring ❤️

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took some working on this, but ended up with new StreamingServer($middleware, $idleConnectionTimeout) because the connection/requests timing out is handled in the StreamingServer.

Comment threadsrc/Io/RequestHeaderParser.php Outdated
@clue

clue commented Feb 5, 2022

Copy link
Copy Markdown
Member

@WyriHaximus Thanks for the update!
Can you provide a quick update here, what's your plan for this feature with regards to #425? We've briefly discussed (off channel) that this should eventually send a 408 Request Timeout response for the initial connection but not for keep-alive connections that send no following request. I agree breaking this up into smaller PRs can be useful, but it looks like this might also work in a single PR, so I'll leave this up to you. Let me know if there's anything I can help with and/or if you want me to take over from here +1

@clue Right, been thinking about this, and doesn't it make sense to do this with all requests in flight, instead of only on the first?

Just made this change, the code is cleaner as well. I Will check the relevant RFC's later today, but until that my position is that any request in a keep alive connection can timeout for any number of reasons.

Did take a look at the relevant RFCs (https://datatracker.ietf.org/doc/html/rfc7230#section-6 and https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.7 come to mind) and it looks like this is indeed under-specified behavior. MDN mentions "Note: some servers merely shut down the connection without sending this message." (https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) and this actually seems to match my observations.

  • If you open a connection github.com:80 or google.com:80 and don't send a complete request, the connection will be closed with no message after a timeout. Same if you send a request using keep-alive and then wait for the timeout. Same if you send a request using keep-alive and then send an incomplete request header.

  • If you open a connection to Apache and don't send any data, the connection will be closed with no message after a timeout. If you open a connection and send an incomplete request header, the connection will be closed with a message after a timeout. In either case, a 408 will be logged. If you send a request using keep-alive and then wait for the timeout, the connection will be closed with no message after a timeout and no log message. If you send a request using keep-alive and then send an incomplete request header, the connection will be closed with an message after a timeout and a log message.

  • If you open a connection to nginx, it will close the connection with no message after after a timeout in either case. Even though I'm using default settings (http://nginx.org/en/docs/http/ngx_http_core_module.html#client_header_timeout), I've never seen an 408 response in my tests.

  • Note there's also https://www.haproxy.com/blog/haproxy-and-http-errors-408-in-chrome/ which suggests sending a 408 used to be(?) a problem in chrome to its inherent race condition. See also https://stackoverflow.com/questions/42631273/how-do-browsers-handle-http-keepalive-race-condition for some background.

Neither behavior appears to be against specs, so I wonder what behavior we should use here.

@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch 4 times, most recently from 2684f7d to ec39c37CompareMarch 22, 2022 14:32
@WyriHaximus

Copy link
Copy Markdown
MemberAuthor

Neither behavior appears to be against specs, so I wonder what behavior we should use here.

@clue For now, merge this as is and I'll do a follow up PR to make the behavior configurable.

@WyriHaximus
WyriHaximus requested review from clue and jsorMarch 23, 2022 13:35
Comment threadsrc/Io/RequestHeaderParser.php Outdated
Comment threadsrc/Io/RequestHeaderParser.php Outdated
Comment threadsrc/Middleware/InactiveConnectionTimeoutMiddleware.php
Comment threadsrc/Io/StreamingServer.php Outdated
Comment threadsrc/Io/StreamingServer.php Outdated
Comment threadsrc/Io/StreamingServer.php Outdated
Comment threadsrc/Io/StreamingServer.php
Comment threadsrc/Io/StreamingServer.php Outdated
Comment threadsrc/Io/StreamingServer.php Outdated

$this->callback = $requestHandler;
$this->parser = new RequestHeaderParser();
$this->parser = new RequestHeaderParser($this->loop, $this->idleConnectionTimeout);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's my understanding the HttpServer should be simplified to call something like new StreamingServer($middleware, new RequestHeaderParser($loop, $idleConnectionTimeout)).

@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch 3 times, most recently from 3db8005 to 4c481faCompareMarch 30, 2022 07:05
@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch 2 times, most recently from 1de8cd4 to 4f47478CompareAugust 17, 2022 06:56
@WyriHaximusWyriHaximus changed the title Close inactive requestsClose inactive connections and requestsSep 15, 2022
@clueclue modified the milestones: v1.8.0, v1.9.0Sep 29, 2022
@WyriHaximusWyriHaximus modified the milestones: v1.9.0, v1.10.0Apr 17, 2023
@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch 7 times, most recently from 655ca5e to 56057dcCompareMarch 26, 2024 22:43
@SimonFringsSimonFrings modified the milestones: v1.10.0, v1.11.0Mar 27, 2024
@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch from 56057dc to d0a0442CompareMarch 30, 2024 21:19
@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch 4 times, most recently from e447b34 to 6b78aa5CompareApril 6, 2024 19:49
@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch from 6b78aa5 to 6491fabCompareApril 10, 2024 05:11
This new middleware introduces a timeout of closing inactive
connections between requests after a configured amount of seconds.
This builds on top of reactphp#405 and partially on reactphp#422
@WyriHaximus
WyriHaximusforce-pushed the close-inactive-requests branch from 6491fab to a270175CompareApril 10, 2024 05:15
@WyriHaximus
WyriHaximus removed the request for review from jsorApril 10, 2024 05:22
@clueclue removed this from the v1.11.0 milestone Oct 14, 2024
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@WyriHaximus@mmoreram@clue@SimonFrings