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:
Ben Noordhuis 2013-07-28 12:19:34 +02:00
Родитель 2b569deed3
Коммит 04f87de3da
4 изменённых файлов: 117 добавлений и 70 удалений

Просмотреть файл

@ -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();

Просмотреть файл

@ -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();
}));
}