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
View
@@ -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.

Show comment Hide comment
@koichik

koichik Jun 19, 2012

s/worker/master/ ?

@koichik

koichik Jun 19, 2012

s/worker/master/ ?

This comment has been minimized.

Show comment Hide comment
@isaacs

isaacs Jun 19, 2012

Yes, that is correct. Good catch.

@isaacs

isaacs Jun 19, 2012

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}
View
@@ -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.
View
@@ -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
View
@@ -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
View
@@ -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;
Oops, something went wrong.

0 comments on commit e733dc3

Please sign in to comment.