Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Fix #3388 Support listening on file descriptors

This implements server.listen({ fd: <filedescriptor> }).  The fd should
refer to an underlying resource that is already bound and listening, and
causes the new server to also accept connections on it.

Not supported on Windows.  Raises ENOTSUP.
  • Loading branch information...
isaacs committed Jun 12, 2012
1 parent 0187b65 commit e733dc3bc3bcc876a29f2b78c5a29bae9dedb0c3
@@ -41,6 +41,52 @@ Running node will now share port 8000 between the workers:
This feature was introduced recently, and may change in future versions.
Please try it out and provide feedback.

## How It Works

<!--type=misc-->

The worker processes are spawned using the `child_process.fork` method,
so that they can communicate with the parent via IPC and pass server
handles back and forth.

When you call `server.listen(...)` in a worker, it serializes the
arguments and passes the request to the master process. If the master
process already has a listening server matching the worker's
requirements, then it passes the handle to the worker. If it does not
already have a listening server matching that requirement, then it will
create one, and pass the handle to the child.

This causes potentially surprising behavior in three edge cases:

1. `server.listen({fd: 7})` Because the message is passed to the worker,

This comment has been minimized.

Copy link
@koichik

koichik Jun 19, 2012

s/worker/master/ ?

This comment has been minimized.

Copy link
@isaacs

isaacs Jun 19, 2012

Author

Yes, that is correct. Good catch.

file descriptor 7 **in the parent** will be listened on, and the
handle passed to the worker, rather than listening to the worker's
idea of what the number 7 file descriptor references.
2. `server.listen(handle)` Listening on handles explicitly will cause
the worker to use the supplied handle, rather than talk to the master
process. If the worker already has the handle, then it's presumed
that you know what you are doing.
3. `server.listen(0)` Normally, this will case servers to listen on a
random port. However, in a cluster, each worker will receive the
same "random" port each time they do `listen(0)`. In essence, the
port is random the first time, but predictable thereafter. If you
want to listen on a unique port, generate a port number based on the
cluster worker ID.

When multiple processes are all `accept()`ing on the same underlying
resource, the operating system load-balances across them very
efficiently. There is no routing logic in Node.js, or in your program,
and no shared state between the workers. Therefore, it is important to
design your program such that it does not rely too heavily on in-memory
data objects for things like sessions and login.

Because workers are all separate processes, they can be killed or
re-spawned depending on your program's needs, without affecting other
workers. As long as there are some workers still alive, the server will
continue to accept connections. Node does not automatically manage the
number of workers for you, however. It is your responsibility to manage
the worker pool for your application's needs.

## cluster.settings

* {Object}
@@ -158,6 +158,24 @@ a listener for the ['listening'](net.html#event_listening_) event.
See also [net.Server.listen()](net.html#server.listen).


### server.listen(handle, [listeningListener])

* `handle` {Object}
* `listeningListener` {Function}

The `handle` object can be set to either a server or socket (anything
with an underlying `_handle` member), or a `{fd: <n>}` object.

This will cause the server to accept connections on the specified
handle, but it is presumed that the file descriptor or handle has
already been bound to a port or domain socket.

Listening on a file descriptor is not supported on Windows.

This function is asynchronous. The last parameter `callback` will be added as
a listener for the ['listening'](net.html#event_listening_) event.
See also [net.Server.listen()](net.html#server.listen).

### server.close([cb])

Stops the server from accepting new connections.
@@ -163,6 +163,25 @@ This function is asynchronous. When the server has been bound,
the last parameter `listeningListener` will be added as an listener for the
['listening'](#event_listening_) event.

### server.listen(handle, [listeningListener])

* `handle` {Object}
* `listeningListener` {Function}

The `handle` object can be set to either a server or socket (anything
with an underlying `_handle` member), or a `{fd: <n>}` object.

This will cause the server to accept connections on the specified
handle, but it is presumed that the file descriptor or handle has
already been bound to a port or domain socket.

Listening on a file descriptor is not supported on Windows.

This function is asynchronous. When the server has been bound,
['listening'](#event_listening_) event will be emitted.
the last parameter `listeningListener` will be added as an listener for the
['listening'](#event_listening_) event.

### server.close([cb])

Stops the server from accepting new connections and keeps existing
@@ -193,7 +193,10 @@ if (cluster.isMaster) {

// This sequence of information is unique to the connection
// but not to the worker
var args = [message.address, message.port, message.addressType];
var args = [message.address,
message.port,
message.addressType,
message.fd];
var key = args.join(':');
var handler;

@@ -216,12 +219,14 @@ if (cluster.isMaster) {
worker.emit('listening', {
address: message.address,
port: message.port,
addressType: message.addressType
addressType: message.addressType,
fd: message.fd
});
cluster.emit('listening', worker, {
address: message.address,
port: message.port,
addressType: message.addressType
addressType: message.addressType,
fd: message.fd
});
};

@@ -508,12 +513,12 @@ cluster._setupWorker = function() {
};

// Internal function. Called by lib/net.js when attempting to bind a server.
cluster._getServer = function(tcpSelf, address, port, addressType, cb) {
cluster._getServer = function(tcpSelf, address, port, addressType, fd, cb) {
// This can only be called from a worker.
assert(cluster.isWorker);

// Store tcp instance for later use
var key = [address, port, addressType].join(':');
var key = [address, port, addressType, fd].join(':');
serverListeners[key] = tcpSelf;

// Send a listening message to the master
@@ -523,7 +528,8 @@ cluster._getServer = function(tcpSelf, address, port, addressType, cb) {
cmd: 'listening',
address: address,
port: port,
addressType: addressType
addressType: addressType,
fd: fd
});
});

@@ -532,7 +538,8 @@ cluster._getServer = function(tcpSelf, address, port, addressType, cb) {
cmd: 'queryServer',
address: address,
port: port,
addressType: addressType
addressType: addressType,
fd: fd
};

// The callback will be stored until the master has responded
@@ -359,7 +359,9 @@ Socket.prototype._destroy = function(exception, cb) {

if (this.server) {
this.server._connections--;
this.server._emitCloseIfDrained();
if (this.server._emitCloseIfDrained) {
this.server._emitCloseIfDrained();
}
}
};

@@ -820,12 +822,33 @@ function toNumber(x) { return (x = Number(x)) >= 0 ? x : false; }


var createServerHandle = exports._createServerHandle =
function(address, port, addressType) {
function(address, port, addressType, fd) {
var r = 0;
// assign handle in listen, and clean up if bind or listen fails
var handle;

if (port == -1 && addressType == -1) {
if (typeof fd === 'number' && fd >= 0) {
var tty_wrap = process.binding('tty_wrap');
var type = tty_wrap.guessHandleType(fd);
switch (type) {
case 'PIPE':
debug('listen pipe fd=' + fd);
// create a PipeWrap
handle = createPipe();
handle.open(fd);
handle.readable = true;
handle.writable = true;
break;

default:
// Not a fd we can listen on. This will trigger an error.
debug('listen invalid fd=' + fd + ' type=' + type);
handle = null;
break;
}
return handle;

} else if (port == -1 && addressType == -1) {
handle = createPipe();
if (process.platform === 'win32') {
var instances = parseInt(process.env.NODE_PENDING_PIPE_INSTANCES);
@@ -855,14 +878,14 @@ var createServerHandle = exports._createServerHandle =
};


Server.prototype._listen2 = function(address, port, addressType, backlog) {
Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
var self = this;
var r = 0;

// If there is not yet a handle, we need to create one and bind.
// In the case of a server sent via IPC, we don't need to do this.
if (!self._handle) {
self._handle = createServerHandle(address, port, addressType);
self._handle = createServerHandle(address, port, addressType, fd);
if (!self._handle) {
process.nextTick(function() {
self.emit('error', errnoException(errno, 'listen'));
@@ -897,16 +920,16 @@ Server.prototype._listen2 = function(address, port, addressType, backlog) {
};


function listen(self, address, port, addressType, backlog) {
function listen(self, address, port, addressType, backlog, fd) {
if (!cluster) cluster = require('cluster');

if (cluster.isWorker) {
cluster._getServer(self, address, port, addressType, function(handle) {
cluster._getServer(self, address, port, addressType, fd, function(handle) {
self._handle = handle;
self._listen2(address, port, addressType, backlog);
self._listen2(address, port, addressType, backlog, fd);
});
} else {
self._listen2(address, port, addressType, backlog);
self._listen2(address, port, addressType, backlog, fd);
}
}

@@ -932,10 +955,21 @@ Server.prototype.listen = function() {
// The port can be found with server.address()
listen(self, null, null, backlog);

} else if (arguments[0] instanceof TCP) {
self._handle = arguments[0];
listen(self, null, -1, -1, backlog);

} else if (arguments[0] && typeof arguments[0] === 'object') {
var h = arguments[0];
if (h._handle) {
h = h._handle;
} else if (h.handle) {
h = h.handle;
}
if (h instanceof TCP) {
self._handle = h;
listen(self, null, -1, -1, backlog);
} else if (h.fd && typeof h.fd === 'number' && h.fd >= 0) {
listen(self, null, null, null, backlog, h.fd);
} else {
throw new Error('Invalid listen argument: ' + h);
}
} else if (isPipeName(arguments[0])) {
// UNIX socket or Windows pipe.
var pipeName = self._pipeName = arguments[0];
@@ -135,7 +135,8 @@ else if (cluster.isMaster) {
assert.equal(arguments.length, 1);
var expect = { address: '127.0.0.1',
port: common.PORT,
addressType: 4 };
addressType: 4,
fd: undefined };
assert.deepEqual(arguments[0], expect);
break;

0 comments on commit e733dc3

Please sign in to comment.
You can’t perform that action at this time.