cluster: fix shared handle bind error propagation
A failed bind() was already being correctly reported in round-robin mode. This commit fixes bind() error reporting in shared handle mode. Fixes #5774.
This commit is contained in:
Родитель
2b569deed3
Коммит
04f87de3da
|
@ -138,7 +138,11 @@ RoundRobinHandle.prototype.add = function(worker, send) {
|
|||
// Still busy binding.
|
||||
this.server.once('listening', done);
|
||||
this.server.once('error', function(err) {
|
||||
send(err.errno, null);
|
||||
// Hack: translate 'EADDRINUSE' error string back to numeric error code.
|
||||
// It works but ideally we'd have some backchannel between the net and
|
||||
// cluster modules for stuff like this.
|
||||
var errno = process.binding('uv')['UV_' + err.errno];
|
||||
send(errno, null);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -388,14 +392,12 @@ function masterInit() {
|
|||
// Set custom server data
|
||||
handle.add(worker, function(errno, reply, handle) {
|
||||
reply = util._extend({
|
||||
ack: message.seq,
|
||||
errno: errno,
|
||||
key: key,
|
||||
ack: message.seq,
|
||||
data: handles[key].data
|
||||
}, reply);
|
||||
if (errno) {
|
||||
reply.errno = errno;
|
||||
delete handles[key]; // Gives other workers a chance to retry.
|
||||
}
|
||||
if (errno) delete handles[key]; // Gives other workers a chance to retry.
|
||||
send(worker, reply, handle);
|
||||
});
|
||||
}
|
||||
|
@ -487,28 +489,14 @@ function workerInit() {
|
|||
};
|
||||
assert(IS_UNDEFINED(handles[key]));
|
||||
handles[key] = handle;
|
||||
cb(handle);
|
||||
cb(message.errno, handle);
|
||||
}
|
||||
|
||||
// Round-robin. Master distributes handles across workers.
|
||||
function rr(message, cb) {
|
||||
if (message.errno)
|
||||
onerror(message, cb);
|
||||
else
|
||||
onsuccess(message, cb);
|
||||
return cb(message.errno, null);
|
||||
|
||||
function onerror(message, cb) {
|
||||
function listen(backlog) {
|
||||
// Translate 'EADDRINUSE' error back to numeric value. This function
|
||||
// is called as sock._handle.listen().
|
||||
return process.binding('uv')['UV_' + message.errno];
|
||||
}
|
||||
function close() {
|
||||
}
|
||||
cb({ close: close, listen: listen });
|
||||
}
|
||||
|
||||
function onsuccess(message, cb) {
|
||||
var key = message.key;
|
||||
function listen(backlog) {
|
||||
// TODO(bnoordhuis) Send a message to the master that tells it to
|
||||
|
@ -516,6 +504,7 @@ function workerInit() {
|
|||
// the largest requested size by any worker.
|
||||
return 0;
|
||||
}
|
||||
|
||||
function close() {
|
||||
// lib/net.js treats server._handle.close() as effectively synchronous.
|
||||
// That means there is a time window between the call to close() and
|
||||
|
@ -527,9 +516,12 @@ function workerInit() {
|
|||
delete handles[key];
|
||||
key = undefined;
|
||||
}
|
||||
|
||||
function getsockname(out) {
|
||||
if (key) util._extend(out, message.sockname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Faux handle. Mimics a TCPWrap with just enough fidelity to get away
|
||||
// with it. Fools net.Server into thinking that it's backed by a real
|
||||
// handle.
|
||||
|
@ -542,8 +534,7 @@ function workerInit() {
|
|||
}
|
||||
assert(IS_UNDEFINED(handles[key]));
|
||||
handles[key] = handle;
|
||||
cb(handle);
|
||||
}
|
||||
cb(0, handle);
|
||||
}
|
||||
|
||||
// Round-robin connection.
|
||||
|
|
|
@ -191,7 +191,13 @@ Socket.prototype.bind = function(/*port, address, callback*/) {
|
|||
cluster = require('cluster');
|
||||
|
||||
if (cluster.isWorker) {
|
||||
cluster._getServer(self, ip, port, self.type, -1, function(handle) {
|
||||
cluster._getServer(self, ip, port, self.type, -1, function(err, handle) {
|
||||
if (err) {
|
||||
self.emit('error', errnoException(err, 'bind'));
|
||||
self._bindState = BIND_STATE_UNBOUND;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self._handle)
|
||||
// handle has been closed in the mean time.
|
||||
return handle.close();
|
||||
|
|
31
lib/net.js
31
lib/net.js
|
@ -1094,27 +1094,30 @@ function listen(self, address, port, addressType, backlog, fd) {
|
|||
return;
|
||||
}
|
||||
|
||||
cluster._getServer(self, address, port, addressType, fd, function(handle) {
|
||||
// Some operating systems (notably OS X and Solaris) don't report EADDRINUSE
|
||||
// errors right away. libuv mimics that behavior for the sake of platform
|
||||
// consistency but that means we have have a socket on our hands that is
|
||||
// not actually bound. That's why we check if the actual port matches what
|
||||
// we requested and if not, raise an error. The exception is when port == 0
|
||||
// because that means "any random port".
|
||||
if (port && handle.getsockname) {
|
||||
cluster._getServer(self, address, port, addressType, fd, cb);
|
||||
|
||||
function cb(err, handle) {
|
||||
// EADDRINUSE may not be reported until we call listen(). To complicate
|
||||
// matters, a failed bind() followed by listen() will implicitly bind to
|
||||
// a random port. Ergo, check that the socket is bound to the expected
|
||||
// port before calling listen().
|
||||
//
|
||||
// FIXME(bnoordhuis) Doesn't work for pipe handles, they don't have a
|
||||
// getsockname() method. Non-issue for now, the cluster module doesn't
|
||||
// really support pipes anyway.
|
||||
if (err === 0 && port > 0 && handle.getsockname) {
|
||||
var out = {};
|
||||
var err = handle.getsockname(out);
|
||||
if (err === 0 && port !== out.port) {
|
||||
err = handle.getsockname(out);
|
||||
if (err === 0 && port !== out.port)
|
||||
err = uv.UV_EADDRINUSE;
|
||||
}
|
||||
if (err) {
|
||||
|
||||
if (err)
|
||||
return self.emit('error', errnoException(err, 'bind'));
|
||||
}
|
||||
}
|
||||
|
||||
self._handle = handle;
|
||||
self._listen2(address, port, addressType, backlog, fd);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
var common = require('../common');
|
||||
var assert = require('assert');
|
||||
var cluster = require('cluster');
|
||||
var net = require('net');
|
||||
|
||||
if (cluster.isMaster) {
|
||||
// Master opens and binds the socket and shares it with the worker.
|
||||
cluster.schedulingPolicy = cluster.SCHED_NONE;
|
||||
// Hog the TCP port so that when the worker tries to bind, it'll fail.
|
||||
net.createServer(assert.fail).listen(common.PORT, function() {
|
||||
var server = this;
|
||||
var worker = cluster.fork();
|
||||
worker.on('exit', common.mustCall(function(exitCode) {
|
||||
assert.equal(exitCode, 0);
|
||||
server.close();
|
||||
}));
|
||||
});
|
||||
}
|
||||
else {
|
||||
var s = net.createServer(assert.fail);
|
||||
s.listen(common.PORT, assert.fail.bind(null, 'listen should have failed'));
|
||||
s.on('error', common.mustCall(function(err) {
|
||||
assert.equal(err.code, 'EADDRINUSE');
|
||||
process.disconnect();
|
||||
}));
|
||||
}
|
Загрузка…
Ссылка в новой задаче