Skip to content

http: fix socket re-use races - #32000

Closed
ronag wants to merge 3 commits into
nodejs:masterfrom
nxtedition:agent-freesocket-timeout
Closed

http: fix socket re-use races#32000
ronag wants to merge 3 commits into
nodejs:masterfrom
nxtedition:agent-freesocket-timeout

Conversation

@ronag

@ronagronag commented Feb 28, 2020

Copy link
Copy Markdown
Member

This fixes two race conditions related to socket re-use in keep alive agents.

  • sockets in the free list, that has emitted 'timeout' should not be re-used
  • sockets that has been destroy():d but has not yet emitted 'close' should not be re-used

Refs: #31526 (comment)

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@ronagronag added the http Issues or PRs related to the http subsystem. label Feb 28, 2020
@ronag
ronagforce-pushed the agent-freesocket-timeout branch from ee40778 to 3bd3270CompareFebruary 28, 2020 10:56
@ronag
ronag requested a review from jasnellFebruary 28, 2020 10:59
@ronag

Copy link
Copy Markdown
MemberAuthor

I think this should be able to land as semver-minor bugfix. Though I'd like a second opinion on that.

Comment threadlib/_http_agent.js Outdated

@ronagronagFeb 28, 2020

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.

freeLen used to include the socket that was removed, however I think that was a mistake as well and it's only used for debug logging.

@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
ronagforce-pushed the agent-freesocket-timeout branch from d12a45e to 1ce5152CompareFebruary 28, 2020 11:21
@ronagronag added semver-minor PRs that contain new features and should be released in the next minor version. author ready PRs that have at least one approval, no pending requests for changes, and a CI started. labels Feb 28, 2020
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@mcollina

Copy link
Copy Markdown
Member

I think this should be able to land as semver-minor bugfix. Though I'd like a second opinion on that.

I would consider it a patch, it really fixes a bad bug.

@mcollina
mcollina requested a review from lpincaFebruary 28, 2020 11:25
@mcollinamcollina added author ready PRs that have at least one approval, no pending requests for changes, and a CI started. and removed author ready PRs that have at least one approval, no pending requests for changes, and a CI started. labels Feb 28, 2020
@ronagronag removed the semver-minor PRs that contain new features and should be released in the next minor version. label Feb 28, 2020
Comment threadlib/_http_agent.js Outdated
Comment threadlib/_http_agent.js Outdated
Comment threadtest/parallel/test-http-agent-timeout.js Outdated
Comment threadtest/parallel/test-http-agent-timeout.js Outdated
Comment threadtest/parallel/test-http-agent-timeout.js Outdated
Comment threadtest/parallel/test-http-agent-timeout.js Outdated
Comment threadlib/_http_agent.js Outdated
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@lpinca

lpinca commented Feb 28, 2020

Copy link
Copy Markdown
Member

Is the socket destroyed when the 'timeout' event is emitted? If not why can't it be reused if it is still a working socket?

@ronag

ronag commented Feb 28, 2020

Copy link
Copy Markdown
MemberAuthor

Is the socket destroyed when the 'timeout' event is emitted? If not why can't it be reused if it is still a working socket?

No, it's currently up to the user. Users often set a timeout and assume it is destroyed. The conservative/non-breaking solution is to assume the socket is not working if it emits timeout and simply not re-use it.

As a separate semver-major PR I would propose to actually destroy the socket on 'timeout'.

@lpinca

Copy link
Copy Markdown
Member

If it is added back to the free socket list and only removed from the same list on timeout, who closes it? It is not clear to me.

@ronag

Copy link
Copy Markdown
MemberAuthor

If it is added back to the free socket list and only removed from the same list on timeout, who closes it? It is not clear to me.

You are absolutely right. I missed that. I will sort that out. Thank you.

@ronagronag removed the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Feb 28, 2020
@ronag
ronagforce-pushed the agent-freesocket-timeout branch from 6017e92 to 542860fCompareFebruary 28, 2020 21:28
@ronag

Copy link
Copy Markdown
MemberAuthor

Given #32000 (comment) I had to update this PR and it can no longer be two separate commits. Essentially, if a socket has a timeout while in the free list we destroy it, and then ensure that destroyed sockets are not re-used.

@ronagronag changed the title http: keep alive socket re-use raceshttp: fix socket re-use racesFeb 28, 2020
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Comment threadlib/_http_agent.js Outdated

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.

We only need to do this if re-using the socket. Move it to where it is put into the free list.

@ronag
ronagforce-pushed the agent-freesocket-timeout branch from 8548113 to aaa4fd5CompareFebruary 28, 2020 21:44
Whether and when a socket is destroyed or not after a timeout is up to
the user. This leaves an edge case where a socket that has emitted
'timeout' might be re-used from the free pool. Even if destroy is called
on the socket, it won't be removed from the freelist until 'close' which
can happen several ticks later.
Sockets are removed from the free list on the 'close' event.
However, there is a delay between calling destroy() and 'close'
being emitted. This means that it possible for a socket that has
been destroyed to be re-used from the free list, causing unexpected
failures.
@ronag
ronagforce-pushed the agent-freesocket-timeout branch from aaa4fd5 to 09e3863CompareFebruary 28, 2020 21:44
@lpinca

lpinca commented Feb 29, 2020

Copy link
Copy Markdown
Member

It seems good but we should update the documentation of the agent specifying that free sockets are destroyed when they time out.

@ronag

Copy link
Copy Markdown
MemberAuthor

@lpinca updated doc

@ronag

ronag commented Mar 2, 2020

Copy link
Copy Markdown
MemberAuthor

@mcollina: There has been some changes here since you reviewed. You still approve?

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Comment threadtest/parallel/test-http-agent-timeout.js
Comment threadtest/parallel/test-http-agent-timeout.js
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@ronag

ronag commented Mar 7, 2020

Copy link
Copy Markdown
MemberAuthor

Landed in 8700d89

@ronagronag closed this Mar 7, 2020
ronag added a commit that referenced this pull request Mar 7, 2020
Whether and when a socket is destroyed or not after a timeout is up to
the user. This leaves an edge case where a socket that has emitted
'timeout' might be re-used from the free pool. Even if destroy is called
on the socket, it won't be removed from the freelist until 'close' which
can happen several ticks later.
Sockets are removed from the free list on the 'close' event.
However, there is a delay between calling destroy() and 'close'
being emitted. This means that it possible for a socket that has
been destroyed to be re-used from the free list, causing unexpected
failures.
PR-URL: #32000
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
MylesBorins pushed a commit that referenced this pull request Mar 9, 2020
Whether and when a socket is destroyed or not after a timeout is up to
the user. This leaves an edge case where a socket that has emitted
'timeout' might be re-used from the free pool. Even if destroy is called
on the socket, it won't be removed from the freelist until 'close' which
can happen several ticks later.
Sockets are removed from the free list on the 'close' event.
However, there is a delay between calling destroy() and 'close'
being emitted. This means that it possible for a socket that has
been destroyed to be re-used from the free list, causing unexpected
failures.
PR-URL: #32000
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
@MylesBorinsMylesBorins mentioned this pull request Mar 10, 2020
targos pushed a commit that referenced this pull request Apr 20, 2020
Whether and when a socket is destroyed or not after a timeout is up to
the user. This leaves an edge case where a socket that has emitted
'timeout' might be re-used from the free pool. Even if destroy is called
on the socket, it won't be removed from the freelist until 'close' which
can happen several ticks later.
Sockets are removed from the free list on the 'close' event.
However, there is a delay between calling destroy() and 'close'
being emitted. This means that it possible for a socket that has
been destroyed to be re-used from the free list, causing unexpected
failures.
PR-URL: #32000
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Denys Otrishko <shishugi@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
@targostargos mentioned this pull request Apr 22, 2020
Comment threadlib/_http_agent.js
const sockets = agent.freeSockets;
for (const name of ObjectKeys(sockets)) {
if (sockets[name].includes(s)) {
return s.destroy();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should remove the destroy socket from freeSockets list immediately to prevent new requests from being sent through this socket.

if(sockets[name].includes(s)){s.destroy();returnagent.removeSocket(s,options);}

killagu added a commit to node-modules/agentkeepalive that referenced this pull request Jun 14, 2020
fengmk2 pushed a commit to node-modules/agentkeepalive that referenced this pull request Jun 15, 2020
Comment threadlib/_http_agent.js
debug('CLIENT socket onTimeout');

// Destroy if in free list.
// TODO(ronag): Always destroy, even if not in free list.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Does this TODO plan to be completed? From the current usage, it may be a breaking change, which makes timeout change from idletimeout to datatimeout.

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

Labels

author readyPRs that have at least one approval, no pending requests for changes, and a CI started.httpIssues or PRs related to the http subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@ronag@nodejs-github-bot@mcollina@lpinca@fengmk2@mariodu@lundibundi