-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
http: fix socket re-use races #32000
Conversation
ee40778
to
3bd3270
Compare
I think this should be able to land as semver-minor bugfix. Though I'd like a second opinion on that. |
delete this.freeSockets[name]; | ||
} | ||
|
||
const freeLen = freeSockets ? freeSockets.length : 0; |
There was a problem hiding this comment.
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.
3bd3270
to
d12a45e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
d12a45e
to
1ce5152
Compare
I would consider it a patch, it really fixes a bad bug. |
Is the socket destroyed when the |
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 |
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. |
6017e92
to
542860f
Compare
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. |
socket.setTimeout(agentTimeout); | ||
} | ||
}); | ||
}); |
There was a problem hiding this comment.
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.
8548113
to
aaa4fd5
Compare
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.
aaa4fd5
to
09e3863
Compare
It seems good but we should update the documentation of the agent specifying that free sockets are destroyed when they time out. |
@lpinca updated doc |
@mcollina: There has been some changes here since you reviewed. You still approve? |
Landed in 8700d89 |
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>
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>
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>
const sockets = agent.freeSockets; | ||
for (const name of ObjectKeys(sockets)) { | ||
if (sockets[name].includes(s)) { | ||
return s.destroy(); |
There was a problem hiding this comment.
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();
return agent.removeSocket(s, options);
}
debug('CLIENT socket onTimeout'); | ||
|
||
// Destroy if in free list. | ||
// TODO(ronag): Always destroy, even if not in free list. |
There was a problem hiding this comment.
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
.
This fixes two race conditions related to socket re-use in keep alive agents.
'timeout'
should not be re-useddestroy()
:d but has not yet emitted'close'
should not be re-usedRefs: #31526 (comment)
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes