Skip to content
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

net: multiple listen() events fail silently #8419

Closed
wants to merge 0 commits into from

Conversation

eduardbme
Copy link
Contributor

@eduardbme eduardbme commented Sep 6, 2016

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

net

Description of change

Problem: It's possible to run listen() on a net.Server that's already listening to a port.
The result is silent failure, with the side effect of changing the _connectionKey and or _pipeName.

Solution: emit error if listen method called more than once.
close() method should be called between listen() method calls.

@addaleax addaleax added net Issues and PRs related to the net subsystem. semver-major PRs that contain breaking changes and should be released in the next major version. labels Sep 6, 2016
@addaleax
Copy link
Member

addaleax commented Sep 6, 2016

Related: #8294 … I read that as meaning that the current behaviour is the intended one?

@eduardbme
Copy link
Contributor Author

eduardbme commented Sep 6, 2016

The issue #6190 for this PR still open.
Maybe documentation should be changed ?

Also, it should be more appropriate err code instead of er.code = 'EADDRINUSE';. Or we need to create one.

@addaleax
Copy link
Member

addaleax commented Sep 6, 2016

oh, yeah. /cc @trevnorris @jasnell

lib/net.js Outdated
var er = new Error('listen method has been called more than once.');
// maybe this is should be another code error
er.code = 'EADDRINUSE';
self.emit('error', er);
Copy link
Contributor

Choose a reason for hiding this comment

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

After emitting this error, why does it continue?

@eduardbme
Copy link
Contributor Author

@thefourtheye Thank you for comments.
Have changed PR according to your comments.

@eduardbme
Copy link
Contributor Author

Hi there, @addaleax @thefourtheye @trevnorris @jasnell just a quick reminder.

@jasnell
Copy link
Member

jasnell commented Sep 20, 2016

Thanks for the reminder ping. I'll take another look at this a bit later on today.

@eduardbme
Copy link
Contributor Author

Hi there, @addaleax @thefourtheye @trevnorris @jasnell just a quick reminder. Second one :)

@thefourtheye
Copy link
Contributor

@eduardbcom Can you please rebase, so that we can take another look at the latest code?

@ronkorving
Copy link
Contributor

It would be (have been?) nice if this could('ve) landed in Node 7.

@jasnell
Copy link
Member

jasnell commented Sep 27, 2016

As a semver-major it can't get into v7 without CTC review and ok. We can try. Adding it to the ctc agenda.

@addaleax
Copy link
Member

addaleax commented Oct 4, 2016

ping @eduardbcom … could you rebase this so that it can be ready for merging from your side? :)

@eduardbme
Copy link
Contributor Author

eduardbme commented Oct 4, 2016

@addaleax sorry for the delay, I don't exactly understand what you expect to receive. rebase to master ? thx.

@gibfahn
Copy link
Member

gibfahn commented Oct 4, 2016

@eduardbcom Yep, rebase against master to remove the merge conflicts github is complaining about.

lib/net.js Outdated
@@ -1327,6 +1329,15 @@ function listen(self, address, port, addressType, backlog, fd, exclusive) {
Server.prototype.listen = function() {
var self = this;

if (self._listenHasBeenCalled) {
var er = new Error('listen method has been called more than once.');
Copy link
Member

Choose a reason for hiding this comment

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

are we using let in newly added code or are the existing var's in here keeping us consistent?

@addaleax
Copy link
Member

addaleax commented Oct 5, 2016

From what I’ve gathered in the CTC meeting it seems like calling .listen() twice is currently problematic under basically all circumstances, so this LGTM and I’m +1 to landing on v7.x.

@rvagg
Copy link
Member

rvagg commented Oct 5, 2016

CTC meeting:

  • @trevnorris raised that _handle is overwritten if you listen() a second time
  • @addaleax raised a possible concern with doc: document what happens when listen is called multiple times #8294 which states in the docs that you can listen() on the same socket multiple times. It should probably say more clearly that you can only listen() on an already closed socket.
  • There's also concern about whether close() is called in every situation when a socket is closed, in this PR it requires that path to unset the flag, is there any way a socket can be closed without that code path?

No negatives from the CTC meeting but it might need to come back next week or be handled async here.

@jasnell
Copy link
Member

jasnell commented Oct 6, 2016

I'm +1 on landing it in v7

@jasnell jasnell added this to the 7.0.0 milestone Oct 6, 2016
@gibfahn
Copy link
Member

gibfahn commented Mar 11, 2017

CI 5: https://ci.nodejs.org/job/node-test-commit/8384/

@nodejs/ctc this needs some review.

lib/net.js Outdated
@@ -1379,6 +1397,14 @@ Server.prototype.listen = function() {
var options = normalized[0];
const cb = normalized[1];

if (this[serverListenHasBeenCalled]) {
var er = new Error('listen method has been called more than once.');
er.code = 'ELISTENCALLEDTWICE';
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we should create new error codes.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What error code should I use ?

Copy link
Contributor

Choose a reason for hiding this comment

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

We don't necessarily need to attach a code. If we really want one, EISCONN, EADDRNOTAVAIL, or maybe EALREADY are options.

let err1 = false;
let err2 = false;

function case1() {
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need to use functions. You can create separate block scopes.

function case1() {

const dummyServer = net.Server();
const server1 = net.Server();
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need server1 and server2 as different names. They are in different scopes. Just use server for both.

const dummyServer = net.Server();
const server1 = net.Server();

// run some server
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you capitalize and punctuate comments. This might fit on a single line too.


// run some server
// in order to simulate EADDRINUSE error
dummyServer.listen(0, 'localhost', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add common.mustCall() around this callback.


server2.on('error', common.mustCall(function(e) {
assert.strictEqual(e.code, 'ELISTENCALLEDTWICE');
err2 = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

err2 isn't needed. This listener should only be called once, and is guaranteed by common.mustCall().

server1.close();
});
} else if (e.code === 'ELISTENCALLEDTWICE') {
err1 = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of having err1, couldn't this just be an assertion?

case1();
case2();

process.on('exit', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this whole block could go away.

doc/api/http.md Outdated
*Note*: The `server.listen()` method may be called multiple times. Each
subsequent call will *re-open* the server using the provided options.
*Note*:
The `server.listen()` method may be called one more time iff there was an error during the first *listen* call or you explicitly called `server.close()`. Otherwise, `ELISTENCALLEDTWICE` error would be emitted.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you break this line at 80 columns please?

doc/api/http.md Outdated
*Note*: The `server.listen()` method may be called multiple times. Each
subsequent call will *re-open* the server using the provided options.
*Note*:
The `server.listen()` method may be called one more time iff there was an error during the first *listen* call or you explicitly called `server.close()`. Otherwise, `ELISTENCALLEDTWICE` error would be emitted.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you break this line at 80 columns please?

doc/api/http.md Outdated
*Note*: The `server.listen()` method may be called multiple times. Each
subsequent call will *re-open* the server using the provided options.
*Note*:
The `server.listen()` method may be called one more time iff there was an error during the first *listen* call or you explicitly called `server.close()`. Otherwise, `ELISTENCALLEDTWICE` error would be emitted.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you break this line at 80 columns please?

lib/net.js Outdated
@@ -1379,6 +1397,14 @@ Server.prototype.listen = function() {
var options = normalized[0];
const cb = normalized[1];

if (this[serverListenHasBeenCalled]) {
var er = new Error('listen method has been called more than once.');
er.code = 'ELISTENCALLEDTWICE';
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree

@eduardbme
Copy link
Contributor Author

@evanlucas @cjihrig @gibfahn @Trott

thanks for comments. updated pr

doc/api/http.md Outdated
*Note*:
The `server.listen()` method may be called one more time iff there was an error
during the first *listen* call or you explicitly called `server.close()`.
Otherwise, `EALREADY` error would be emitted.
Copy link
Member

Choose a reason for hiding this comment

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

"Otherwise, an EALREADY error is emitted." - likewise below.

Also, I'd probably replace "may be called one more time " with "can be called again."

doc/api/net.md Outdated
subsequent call will *re-open* the server using the provided options.
* The `server.listen()` method may be called one more time iff there was an error
during the first *listen* call or you explicitly called `server.close()`.
Otherwise, `EALREADY` error would be emitted.
Copy link
Member

Choose a reason for hiding this comment

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

Can you indent here?

lib/net.js Outdated
return this[serverHandle];
},
enumerable: true
});
Copy link
Member

Choose a reason for hiding this comment

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

I don't know how I feel about turning an internal property into an accessor... Why did you decide on this approach? Is it because of #8419 (comment)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The main reason I decided to do it in this way it's because we need to set serverListenHasBeenCalled back to false when we close connection or some error has happened. And I have noticed that for both cases we call the same code to handle _handle field. Concretely:

this._handle.close(); this._handle = null;

So in order to avoid repetitions and do something like this every time we close this._handle

this._handle.close(); this._handle = null; this.serverListenHasBeenCalled = false

I decided to make this stuff in one place.

A big plus of this, is that if someone in a future will add some logic to handle error, they will not forget to set this.serverListenHasBeenCalled = false.

Copy link
Contributor

Choose a reason for hiding this comment

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

@bnoordhuis what are you thoughts on this now? Still conflicted?

Copy link
Member

Choose a reason for hiding this comment

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

I'd stick with simple properties. Accessors have overhead. If that means duplicating an assignment in two places, that's something I can live with.

lib/net.js Outdated
@@ -1379,6 +1397,14 @@ Server.prototype.listen = function() {
var options = normalized[0];
const cb = normalized[1];

if (this[serverListenHasBeenCalled]) {
var er = new Error('listen method has been called more than once.');
er.code = 'EALREADY';
Copy link
Member

Choose a reason for hiding this comment

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

This is in line with what libuv returns for client sockets but using EALREADY instead of EINVAL (the standard POSIX error for trying to rebind) is one of my little regrets there.

Thoughts, anyone? Consistency or conformity?

Copy link
Contributor

Choose a reason for hiding this comment

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

If you regret it, then we should probably go with EINVAL.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed.

}));

server.listen(0, 'localhost', () => server.close());
server.listen(1, 'localhost');
Copy link
Contributor

Choose a reason for hiding this comment

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

Why 1 here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, no reason for this.

@Trott
Copy link
Member

Trott commented Mar 18, 2017

Since this has received three CTC reviews since application of ctc-review label, I'm going to go ahead and remove that label. If anyone thinks it should stay, by all means, re-apply it.

@Trott Trott removed the ctc-review label Mar 18, 2017
@jasnell
Copy link
Member

jasnell commented Mar 24, 2017

Ping @eduardbcom... some new comments have been added

@eduardbme eduardbme force-pushed the listenhasbeencalled branch 2 times, most recently from e2f6e5e to 1488e56 Compare March 26, 2017 10:32
@eduardbme
Copy link
Contributor Author

eduardbme commented Mar 26, 2017

@jasnell thanks for the reminder ping.

@bnoordhuis please check the comment for your question about the _handle property.

@cjihrig @evanlucas

updated pr. Thank you folks for your comments.

assert.strictEqual(e.code, 'EINVAL');
}));

server.listen(common.mustCall(
Copy link
Contributor

Choose a reason for hiding this comment

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

This can all be one line.

// after first time is fail (without calling close method).

// The second one is about calling a listen method twice.
// It should be EALREADY but the first server listen call is working.
Copy link
Contributor

Choose a reason for hiding this comment

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

Should EALREADY be EINVAL?

lib/net.js Outdated
@@ -1373,6 +1391,14 @@ Server.prototype.listen = function() {
var options = normalized[0];
const cb = normalized[1];

if (this[serverListenHasBeenCalled]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you check === true here.

lib/net.js Outdated
this._handle = null;

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you remove the unnecessary whitespace changes (here and in other places).

@eduardbme eduardbme force-pushed the listenhasbeencalled branch from 1488e56 to 1533c61 Compare March 27, 2017 19:15
@eduardbme
Copy link
Contributor Author

thanks @cjihrig, updated

@eduardbme
Copy link
Contributor Author

@jasnell @bnoordhuis @Trott @cjihrig @evanlucas quick reminder

doc/api/http.md Outdated
*Note*: The `server.listen()` method may be called multiple times. Each
subsequent call will *re-open* the server using the provided options.
*Note*:
The `server.listen()` method can be called again iff there was an error
Copy link
Member

Choose a reason for hiding this comment

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

I forgot what our position was on using 'iff' in the documentation: yay or nay? git grep suggests nay.

Copy link
Member

Choose a reason for hiding this comment

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

My preference is to avoid it. Please use if and only if instead.

lib/net.js Outdated
return this[serverHandle];
},
enumerable: true
});
Copy link
Member

Choose a reason for hiding this comment

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

I'd stick with simple properties. Accessors have overhead. If that means duplicating an assignment in two places, that's something I can live with.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
net Issues and PRs related to the net subsystem. semver-major PRs 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.