Skip to content

http: Change free sockets behavior to LIFO from FIFO. - #31526

Closed
rustyconover wants to merge 2 commits into
nodejs:masterfrom
rustyconover:fix-https-agent-oldest-sockets
Closed

http: Change free sockets behavior to LIFO from FIFO.#31526
rustyconover wants to merge 2 commits into
nodejs:masterfrom
rustyconover:fix-https-agent-oldest-sockets

Conversation

@rustyconover

Copy link
Copy Markdown
Contributor

Sockets are added to the free list with .push() but they were
being removed with .shift(). This meant the sockets where being
removed in FIFO order, but this changes it to LIFO. Since older
sockets may be closed based due to inactivity on the server it is
more likely that a socket that is recently used will be able to
successfully process the next request.

  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • commit message follows

@nodejs-github-botnodejs-github-bot added the http Issues or PRs related to the http subsystem. label Jan 26, 2020
@ronag

ronag commented Jan 27, 2020

Copy link
Copy Markdown
Member

On the other hand sockets that have not been used in a while might unnecessarily timeout. I think LIFO is fine but we do need to remove sockets from the free list when they timeout (which we currently don't).

I'm unsure whether LIFO or FIFO is better here...

Also, this might be a good idea to apply when the free list is full, i.e. instead of throwing away the most recent socket (as we currently do), we should throw away the least recently used.

@rustyconover

Copy link
Copy Markdown
ContributorAuthor

Why would we want to remove then until we actually try them? That's the current behavior.

You have a good idea on the second part about what should be thrown away, updated commit pending.

@ronag

Copy link
Copy Markdown
Member

Why would we want to remove then until we actually try them? That's the current behavior.

Because they have timed out. I don't think that's the current behavior?

@rustyconover

Copy link
Copy Markdown
ContributorAuthor

Are there event handlers still reading from the socket once they are placed in the agent pool? I don't believe so. If there aren't any handlers waiting to read how will the code know the socket has timed out?

@ronag

Copy link
Copy Markdown
Member

If there aren't any handlers waiting to read how will the code know the socket has timed out?

By adding a handler. See, #23752.

Comment threadlib/_http_agent.js Outdated
Comment threadlib/_http_agent.js
this.maxFreeSockets > 0 &&
count <= this.maxSockets) {
if (freeLen >= this.maxFreeSockets) {
const oldest = this.freeSockets[name].shift();

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.

nit, freeSockets.shift()

Comment threadlib/_http_agent.js
if (freeLen >= this.maxFreeSockets) {
const oldest = this.freeSockets[name].shift();
oldest.destroy();
} else {

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.

nit, else if (!freeSockets)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm not quite following, could you explain a bit more.

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.

the block inside of the else is for the case when !freeSockets, could be simplified

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 a nit, no biggie

@ronag

Copy link
Copy Markdown
Member

Would be nice if you could make a test for this.

@jasnelljasnell added the semver-major PRs that contain breaking changes and should be released in the next major version. label Jan 27, 2020
@jasnell

Copy link
Copy Markdown
Member

Defensively marking this semver-major for now. It's possible this wouldn't break anyone but we need to verify.

@jasnell

Copy link
Copy Markdown
Member

Ping @nodejs/http

@Trott

Copy link
Copy Markdown
Member

A benchmark would be good. We might already have a relevant benchmark. I'm not sure.

Would be nice if you could make a test for this.

At least in theory, isn't this an implementation detail the user need not worry about? (But I wouldn't oppose a test either. Just not sure if it's necessary.)

@ronag

Copy link
Copy Markdown
Member

At least in theory, isn't this an implementation detail the user need not worry about?

Agreed. Nice to have.

Sockets are added to the free list with .push() but they were
being removed with .shift(). This meant the sockets where being
removed in FIFO order, but this changes it to LIFO. Since older
sockets may be closed based due to inactivity on the server it is
more likely that a socket that is recently used will be able to
successfully process the next request.
Rather than destroying the last used socket destroy
the oldest socket in the free list in push() on the
last recently used socket.
@rustyconover
rustyconoverforce-pushed the fix-https-agent-oldest-sockets branch from 4ef4a2d to 5af8b39CompareFebruary 17, 2020 17:49
@rustyconover

Copy link
Copy Markdown
ContributorAuthor

@ronag@Trott could you please mark this PR as author ready?

@Trott

Copy link
Copy Markdown
Member

@ronag@Trott could you please mark this PR as author ready?

Although there's some ambiguity around the author ready label, I would say this is not yet author ready because it is semver-major and therefore requires two approvals from the TSC.

/ping @nodejs/tsc Please review! (@jasnell described marking this as semver-major as being done "defensively" so it might not be semver-major after all. Opinions one way or the other are welcome.)

@mcollinamcollina left a comment

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 would like to have a test for this behavior.

@mcollina

Copy link
Copy Markdown
Member

Very good work. This is one of the main changes I did in https://github.com/mcollina/undici to improve the handling of keep-alive connections.

@ronag

Copy link
Copy Markdown
Member

There is a related issue to this that we might want to consider before landing this.

We have a problem with sockets in the freelist that timeout are not removed from the list. This change might make that worse, since the least recently used socket is less likely to be used and thus timeout, then when it is actually used the request using it would fail.

@rustyconover

Copy link
Copy Markdown
ContributorAuthor

@ronag Does this mean that the request isn't retried on another connection automatically or is the request simply failed with a connection reset error?

@rustyconover

Copy link
Copy Markdown
ContributorAuthor

@ronag is there a test case that stresses this condition?

@ronag

ronag commented Feb 28, 2020

Copy link
Copy Markdown
Member

@ronag Does this mean that the request isn't retried on another connection automatically or is the request simply failed with a connection reset error?

If the user doesn't abort the request in a 'timeout' event handler (this is optional at the moment), the socket might be incorrectly re-used and the corresponding request would potentially fail, probably with ECONNRESET.

@ronag is there a test case that stresses this condition?

Nope, would be nice to have a tests for it.

@ronag

Copy link
Copy Markdown
Member

Just to clarify, I believe my concern is already a problem, however this PR might make it worse.

@ronagronag mentioned this pull request Feb 28, 2020
4 tasks
@ronag

ronag commented Feb 28, 2020

Copy link
Copy Markdown
Member

@rustyconover: I openend a separate PR to address my concern. I think this PR is good as is once a test is added, though I would prefer to wait for #32000 to land before landing this.

@rustyconover

Copy link
Copy Markdown
ContributorAuthor

@ronag I just wrote a test for the LIFO behavior of this PR. Let me know what you think.

@rustyconover

Copy link
Copy Markdown
ContributorAuthor

I would like to have a test for this behavior.

@mcollina I just added a test.

Comment threadtest/sequential/test-http-keepalive-lifo-sockets.js Outdated
Comment threadtest/sequential/test-http-keepalive-lifo-sockets.js Outdated
Comment threadtest/sequential/test-http-keepalive-lifo-sockets.js Outdated
Add a test that ensures the HTTP agent reuses sockets
in a LIFO fashion rather than FIFO.
@rustyconover
rustyconoverforce-pushed the fix-https-agent-oldest-sockets branch from 9aad728 to b43479fCompareMarch 1, 2020 04:31
@rustyconover

Copy link
Copy Markdown
ContributorAuthor

@mcollina I believe I've addressed your suggestions in the updated commits. Please let me know what you think. Thank you! 🙏

@mcollinamcollina left a comment

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.

lgtm

@ronag

Copy link
Copy Markdown
Member

@rustyconover this has conflicts

@mcollina

Copy link
Copy Markdown
Member

Implemented in #33278

@rustyconover

Copy link
Copy Markdown
ContributorAuthor

Great! 👍

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

httpIssues or PRs related to the http subsystem.semver-majorPRs that contain breaking changes and should be released in the next major version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@rustyconover@ronag@jasnell@Trott@mcollina@nodejs-github-bot