-
Notifications
You must be signed in to change notification settings - Fork 29.7k
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
cluster,net: fix random port keying #7043
Conversation
if (!errno) | ||
handles[key] = handle; | ||
data = handle.data; | ||
handle.key = key; |
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.
Shouldn't this be within the !errno
check?
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.
It shouldn't really matter. I just did it there so that it's overwritten no matter if there should happen to be another reference to the handle now or in the future.
9baa080
to
dc76278
Compare
Fixed Windows issue. CI again: https://ci.nodejs.org/job/node-test-pull-request/2845/ |
I'm not sure how to label this one (e.g. What does everyone else think? |
Maybe run CITGM? |
citgm looks green. |
assert.equal(common.PORT + 2, address3.port); | ||
assert.equal(common.PORT + 3, address4.port); | ||
|
||
assert.strictEqual(typeof address2.port, 'number'); |
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.
Could you DRY this by moving the checks into a function.
dc76278
to
21090a0
Compare
@cjihrig Addressed your comments CI again: https://ci.nodejs.org/job/node-test-pull-request/2940/ |
/cc @nodejs/collaborators Any thoughts? |
This sounds like a very welcome change. I never understood why on a cluster, port 0 would just mean each worker would be assigned a random port. This solution works, although imho there is one concern: |
This commit fixes an issue where sockets that are bound to a random port are keyed on port 0 instead of the actual port that was assigned to the socket.
21090a0
to
d87bc5f
Compare
@ronkorving I've fixed that now and included a new test. Please take a look and let me know if that was what you had in mind. |
Conceptually, how would I create both an HTTP server and a separate TCP server that both listen on a random port, and are both clustered? I'm not cluster-savvy enough atm to have a very critical eye to your solution. But humor me please on the conceptual level :) |
@ronkorving So you're asking to put this new functionality behind a new Without this PR, all servers bound in workers that listen on port "0" all refer to the exact same port (whatever it ultimately gets resolved to by the OS) because the master includes the port number in the key name in its handle table. So if you want to spin up another server on a random port within any of the workers, you're SOL as the master will see a server already listening on port "0" and you will either get unexpected behavior (if you have different types of servers sharing the port) or you will get an EADDRINUSE (in the With this PR, workers can listen on as many random ports as they want. This means that port "0" handles are no longer permanently stored by the master (they get re-indexed using the real port after the port is bound) and thus are not automatically shared amongst all workers when they all listen on port "0." However, the port it ultimately resolves to will be shareable (assuming you don't set I hope that makes some sense. |
I don't want the old behavior at all (I think it's useless). But there are 2 valid behaviors imho, that hopefully could co-exist. Scenario 1:
Scenario 2:
I understand from your comments that scenario 1 is possible, and scenario 2 requires some more userland intervention, but is possible? |
With this PR it's the same process no matter the scenario. If a worker listens on a random port, the real port needs to be broadcasted to the other workers in order for them to listen on that same port. This can actually be done a couple of different ways: Worker 1 could do something like: server.listen(0, function() {
process.send({ type: 'rest', port: this.address().port });
});
server2.listen(0, function() {
process.send({ type: 'healthcheck', port: this.address().port });
}); and in the master: worker1.on('message', function(msg) {
if (msg.type && msg.port) {
Object.keys(cluster.workers).forEach(function(worker) {
if (worker === worker1)
return;
worker.send(msg);
});
}
}); then the other workers watch for those messages and then listen on the port(s). OR Use the master's |
First, this is absolutely semver-major, the fact that all cluster workers listen on the same port with an app that listens on port Second, I get that there are multiple use-cases, but I'd suggest that an API that expresses the intent would be better than breaking the current use case, particularly when it might enable other use cases, but not completely, you have to write a bunch more code to get anything useful. Right now, the common case (one listen on 0 in a single workers .js file) works with no additional code. This PR breaks that use, it would make every worker listen on a different port, which means its effectively not clustered. It breaks it in favour of a model where if you listen multiple times in one worker every single listen port is ephemeral... which strikes me as wholly undesireable, so now you have to write complex cluster code to coordinate, code that isn't even possible to write (there is no worker to worker messaging supported directly by the cluster lib) if you don't control the implementation of the cluster master, which you don't with most supervisor-type code bases which auto-cluster applications. If some way could be found so that the listens could be (random bad API example design) on some special token, maybe |
@sam-github I've already proposed putting it behind an option if that's what everyone wants. |
@mscdex where? I'm not finding that proposal in the PR comments. What kind of option? node CLI option? an option to cluster.setup()? |
@mscdex also, what about my suggestion of reworking the API so instead of some global switch, we allow the code doing the listen to say what it wants, true ephemeral, or shared-ephemeral? These global everything one way or everything the other switches I feel eventually run afoul of code that is combined, wherein some code wants one, some the other, and you are stuck.... Also, I'd be interested in what the use case is for having every worker listening on worker-specific/unclustered ports. This is the cluster module... not clustering seems odd. |
Is an additional boolean config option acceptable for this then? If so, does anyone have a good name they want to throw out there? |
Sounds like a good option to me, but don't have any good ideas for the name. |
pulled the dont-land-on-v4.x label since it's a semver-major that wouldn't land there anyway |
@mscdex @Trott @trevnorris ... ping. what's next on this? |
@jasnell If most people are in favor of adding this behavior under a separate option, then I will go ahead and add one. |
seems like a good idea to me. /cc @nodejs/collaborators |
CI with current master: https://ci.nodejs.org/job/node-test-pull-request/4261/ |
Something went wrong with that last CI run ( |
Don't worry about CI right now, I need to change this PR still. |
Added |
c133999
to
83c7a88
Compare
@mscdex ... still want to pursue this? |
@jasnell At some point, yes. |
@@ -591,6 +633,16 @@ function workerInit() { | |||
send(message, function(reply, handle) { | |||
if (obj._setServerData) obj._setServerData(reply.data); | |||
|
|||
if (+options.port === 0 && reply.sockname) { |
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.
Nit: As the +options.port
coercion is done twice you might just move that in a variable above?
I think this PR can be made more general. Proposal:
That opens a path for supporting unshared sockets for more use cases than just the port 0 scenario. EDIT: The unshared behavior should be opt-in for users, though. We can't and shouldn't break the current behavior. |
Ping @mscdex |
@BridgeAR I'm not going to have the time to fix this up unfortunately. If someone else wants to work on it or do it from scratch in the way that @bnoordhuis suggested, that's fine by me. |
Closing due to long inactivity. If someone wants to pick it up, please feel free to open a new PR with the suggested code change. |
Checklist
Affected core subsystem(s)
Description of change
This commit fixes an issue where sockets that are bound to a random port are keyed on port 0 instead of the actual port that was assigned to the socket.