-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
net: do not add multiple connect and finish listeners when multiple Socket.destroySoon's were scheduled
#60469
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
base: main
Are you sure you want to change the base?
Conversation
|
Review requested:
|
524d5c5 to
8473258
Compare
8473258 to
6e33069
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #60469 +/- ##
==========================================
- Coverage 88.58% 88.54% -0.05%
==========================================
Files 704 704
Lines 207826 207886 +60
Branches 40054 40050 -4
==========================================
- Hits 184106 184071 -35
- Misses 15761 15844 +83
- Partials 7959 7971 +12
🚀 New features to boost your workflow:
|
lib/net.js
Outdated
| Socket.prototype._final = function(cb) { | ||
| // If still connecting - defer handling `_final` until 'connect' will happen | ||
| if (this.connecting) { | ||
| if (this.connecting && !this._finalizingOnConnect) { |
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.
I don't think this guard is need, wriable._final() is only called once.
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.
Sorry, maybe I'm missing something here 🤔 but from what I have been debugged it seems that Socket._final() is called for every socket.destroySoon() call in described scenario in the linked issue #60456
P.S. I've updated implementation to return from this function even if _finalizingOnConnect is already true (as it was previously).
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.
const net = require('net');
const socket = net.createConnection({
host: 'example.com',
port: 80,
lookup() {}
});
socket.destroySoon();
socket.destroySoon();
socket.destroySoon();
socket.destroySoon();
socket.destroySoon();
console.log(socket.listenerCount('connect')); // Prints 1There 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.
You are right. My test didn't reproduce mentioned issue well. I reworked it to do it better. So now it looks like this:
const net = require('net');
const socket = new net.Socket();
socket.on('error', () => {
// noop
});
const connectOptions = { host: "example.com", port: 1234 };
socket.connect(connectOptions);
socket.destroySoon(); // Adds "connect" and "finish" event listeners when socket has "writable" state
socket.destroy(); // Makes imideditly socket again "writable"
socket.connect(connectOptions);
socket.destroySoon(); // Should not duplicate "connect" and "finish" event listeners
console.log(socket.listeners('finish').length); // Prints 2
console.log(socket.listeners('connect').length); // Prints also 2and with it we can observe that second call to socket.destroySoon() adds additional event listeners for finish and connect events, which could lead to memory leak.
| socket.destroySoon(); // Adds "connect" and "finish" event listeners when socket has "writable" state | ||
| socket.destroy(); // Makes imideditly socket again "writable" | ||
|
|
||
| socket.connect(connectOptions); |
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.
This is what actually makes the socket writable again as it calls socket._undestroy(). It is not recommended to call socket.connect() on a previously destroyed socket as its state might not be well defined. The issue you are describing can also happen in other cases with this usage pattern, for example:
const net = require('net');
const options = {
host: 'example.com',
port: 80,
lookup() {}
};
const socket = new net.Socket();
socket.connect(options);
socket.read();
socket.destroy();
socket.connect(options);
socket.read();
socket.destroy();
socket.connect(options);
socket.read();
socket.destroy();
console.log(socket.listenerCount('connect')); // Prints 3There 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.
Thank You for Your review! But do You think that mentioned issue should be even addressed? If yes I will analyze it deeper to provide better test that is reproducing this issue.
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.
I think we should fix the issue where multiple 'finish' events are added if socket.destroySoon() is called multiple times on the same socket. A possible fix is to make socket.destroySoon() a noop after the first call.
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.
I've removed changes for connect event listener, and leaved for finish (in destroySoon). I didn't make socket.destroySoon() completely noop after first call, because I still want this function to work back as it should when finish event eventually is emitted, and calling socket.destroySoon() potentially becomes back legitimate thing. But please let me know if You meant something else by writing "a noop after the first call".
test/parallel/test-net-socket-not-duplicates-destroy-soon-listeners.js
Outdated
Show resolved
Hide resolved
test/parallel/test-net-socket-not-duplicates-destroy-soon-listeners.js
Outdated
Show resolved
Hide resolved
…eners.js Co-authored-by: Luigi Pinca <luigipinca@gmail.com>
…eners.js Co-authored-by: Luigi Pinca <luigipinca@gmail.com>
lib/net.js
Outdated
| else | ||
| this.once('finish', this.destroy); | ||
| else { | ||
| this.destroyingOnFinish = true; |
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.
please use a Symbol for this
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.
right 👍 Symbol used ✅
Fixes #60456 by adding new
finishevent listener aftersocket.destroySooncall only if they were not added previously on given socket.