Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

Commit e733dc3

Browse files
committed
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.
1 parent 0187b65 commit e733dc3

10 files changed

Lines changed: 640 additions & 21 deletions

doc/api/cluster.markdown

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,52 @@ Running node will now share port 8000 between the workers:
4141
This feature was introduced recently, and may change in future versions.
4242
Please try it out and provide feedback.
4343

44+
## How It Works
45+
46+
<!--type=misc-->
47+
48+
The worker processes are spawned using the `child_process.fork` method,
49+
so that they can communicate with the parent via IPC and pass server
50+
handles back and forth.
51+
52+
When you call `server.listen(...)` in a worker, it serializes the
53+
arguments and passes the request to the master process. If the master
54+
process already has a listening server matching the worker's
55+
requirements, then it passes the handle to the worker. If it does not
56+
already have a listening server matching that requirement, then it will
57+
create one, and pass the handle to the child.
58+
59+
This causes potentially surprising behavior in three edge cases:
60+
61+
1. `server.listen({fd: 7})` Because the message is passed to the worker,
62+
file descriptor 7 **in the parent** will be listened on, and the
63+
handle passed to the worker, rather than listening to the worker's
64+
idea of what the number 7 file descriptor references.
65+
2. `server.listen(handle)` Listening on handles explicitly will cause
66+
the worker to use the supplied handle, rather than talk to the master
67+
process. If the worker already has the handle, then it's presumed
68+
that you know what you are doing.
69+
3. `server.listen(0)` Normally, this will case servers to listen on a
70+
random port. However, in a cluster, each worker will receive the
71+
same "random" port each time they do `listen(0)`. In essence, the
72+
port is random the first time, but predictable thereafter. If you
73+
want to listen on a unique port, generate a port number based on the
74+
cluster worker ID.
75+
76+
When multiple processes are all `accept()`ing on the same underlying
77+
resource, the operating system load-balances across them very
78+
efficiently. There is no routing logic in Node.js, or in your program,
79+
and no shared state between the workers. Therefore, it is important to
80+
design your program such that it does not rely too heavily on in-memory
81+
data objects for things like sessions and login.
82+
83+
Because workers are all separate processes, they can be killed or
84+
re-spawned depending on your program's needs, without affecting other
85+
workers. As long as there are some workers still alive, the server will
86+
continue to accept connections. Node does not automatically manage the
87+
number of workers for you, however. It is your responsibility to manage
88+
the worker pool for your application's needs.
89+
4490
## cluster.settings
4591

4692
* {Object}

doc/api/http.markdown

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,24 @@ a listener for the ['listening'](net.html#event_listening_) event.
158158
See also [net.Server.listen()](net.html#server.listen).
159159

160160

161+
### server.listen(handle, [listeningListener])
162+
163+
* `handle` {Object}
164+
* `listeningListener` {Function}
165+
166+
The `handle` object can be set to either a server or socket (anything
167+
with an underlying `_handle` member), or a `{fd: <n>}` object.
168+
169+
This will cause the server to accept connections on the specified
170+
handle, but it is presumed that the file descriptor or handle has
171+
already been bound to a port or domain socket.
172+
173+
Listening on a file descriptor is not supported on Windows.
174+
175+
This function is asynchronous. The last parameter `callback` will be added as
176+
a listener for the ['listening'](net.html#event_listening_) event.
177+
See also [net.Server.listen()](net.html#server.listen).
178+
161179
### server.close([cb])
162180

163181
Stops the server from accepting new connections.

doc/api/net.markdown

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,25 @@ This function is asynchronous. When the server has been bound,
163163
the last parameter `listeningListener` will be added as an listener for the
164164
['listening'](#event_listening_) event.
165165

166+
### server.listen(handle, [listeningListener])
167+
168+
* `handle` {Object}
169+
* `listeningListener` {Function}
170+
171+
The `handle` object can be set to either a server or socket (anything
172+
with an underlying `_handle` member), or a `{fd: <n>}` object.
173+
174+
This will cause the server to accept connections on the specified
175+
handle, but it is presumed that the file descriptor or handle has
176+
already been bound to a port or domain socket.
177+
178+
Listening on a file descriptor is not supported on Windows.
179+
180+
This function is asynchronous. When the server has been bound,
181+
['listening'](#event_listening_) event will be emitted.
182+
the last parameter `listeningListener` will be added as an listener for the
183+
['listening'](#event_listening_) event.
184+
166185
### server.close([cb])
167186

168187
Stops the server from accepting new connections and keeps existing

lib/cluster.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,10 @@ if (cluster.isMaster) {
193193

194194
// This sequence of information is unique to the connection
195195
// but not to the worker
196-
var args = [message.address, message.port, message.addressType];
196+
var args = [message.address,
197+
message.port,
198+
message.addressType,
199+
message.fd];
197200
var key = args.join(':');
198201
var handler;
199202

@@ -216,12 +219,14 @@ if (cluster.isMaster) {
216219
worker.emit('listening', {
217220
address: message.address,
218221
port: message.port,
219-
addressType: message.addressType
222+
addressType: message.addressType,
223+
fd: message.fd
220224
});
221225
cluster.emit('listening', worker, {
222226
address: message.address,
223227
port: message.port,
224-
addressType: message.addressType
228+
addressType: message.addressType,
229+
fd: message.fd
225230
});
226231
};
227232

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

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

515520
// Store tcp instance for later use
516-
var key = [address, port, addressType].join(':');
521+
var key = [address, port, addressType, fd].join(':');
517522
serverListeners[key] = tcpSelf;
518523

519524
// Send a listening message to the master
@@ -523,7 +528,8 @@ cluster._getServer = function(tcpSelf, address, port, addressType, cb) {
523528
cmd: 'listening',
524529
address: address,
525530
port: port,
526-
addressType: addressType
531+
addressType: addressType,
532+
fd: fd
527533
});
528534
});
529535

@@ -532,7 +538,8 @@ cluster._getServer = function(tcpSelf, address, port, addressType, cb) {
532538
cmd: 'queryServer',
533539
address: address,
534540
port: port,
535-
addressType: addressType
541+
addressType: addressType,
542+
fd: fd
536543
};
537544

538545
// The callback will be stored until the master has responded

lib/net.js

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,9 @@ Socket.prototype._destroy = function(exception, cb) {
359359

360360
if (this.server) {
361361
this.server._connections--;
362-
this.server._emitCloseIfDrained();
362+
if (this.server._emitCloseIfDrained) {
363+
this.server._emitCloseIfDrained();
364+
}
363365
}
364366
};
365367

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

821823

822824
var createServerHandle = exports._createServerHandle =
823-
function(address, port, addressType) {
825+
function(address, port, addressType, fd) {
824826
var r = 0;
825827
// assign handle in listen, and clean up if bind or listen fails
826828
var handle;
827829

828-
if (port == -1 && addressType == -1) {
830+
if (typeof fd === 'number' && fd >= 0) {
831+
var tty_wrap = process.binding('tty_wrap');
832+
var type = tty_wrap.guessHandleType(fd);
833+
switch (type) {
834+
case 'PIPE':
835+
debug('listen pipe fd=' + fd);
836+
// create a PipeWrap
837+
handle = createPipe();
838+
handle.open(fd);
839+
handle.readable = true;
840+
handle.writable = true;
841+
break;
842+
843+
default:
844+
// Not a fd we can listen on. This will trigger an error.
845+
debug('listen invalid fd=' + fd + ' type=' + type);
846+
handle = null;
847+
break;
848+
}
849+
return handle;
850+
851+
} else if (port == -1 && addressType == -1) {
829852
handle = createPipe();
830853
if (process.platform === 'win32') {
831854
var instances = parseInt(process.env.NODE_PENDING_PIPE_INSTANCES);
@@ -855,14 +878,14 @@ var createServerHandle = exports._createServerHandle =
855878
};
856879

857880

858-
Server.prototype._listen2 = function(address, port, addressType, backlog) {
881+
Server.prototype._listen2 = function(address, port, addressType, backlog, fd) {
859882
var self = this;
860883
var r = 0;
861884

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

899922

900-
function listen(self, address, port, addressType, backlog) {
923+
function listen(self, address, port, addressType, backlog, fd) {
901924
if (!cluster) cluster = require('cluster');
902925

903926
if (cluster.isWorker) {
904-
cluster._getServer(self, address, port, addressType, function(handle) {
927+
cluster._getServer(self, address, port, addressType, fd, function(handle) {
905928
self._handle = handle;
906-
self._listen2(address, port, addressType, backlog);
929+
self._listen2(address, port, addressType, backlog, fd);
907930
});
908931
} else {
909-
self._listen2(address, port, addressType, backlog);
932+
self._listen2(address, port, addressType, backlog, fd);
910933
}
911934
}
912935

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

935-
} else if (arguments[0] instanceof TCP) {
936-
self._handle = arguments[0];
937-
listen(self, null, -1, -1, backlog);
938-
958+
} else if (arguments[0] && typeof arguments[0] === 'object') {
959+
var h = arguments[0];
960+
if (h._handle) {
961+
h = h._handle;
962+
} else if (h.handle) {
963+
h = h.handle;
964+
}
965+
if (h instanceof TCP) {
966+
self._handle = h;
967+
listen(self, null, -1, -1, backlog);
968+
} else if (h.fd && typeof h.fd === 'number' && h.fd >= 0) {
969+
listen(self, null, null, null, backlog, h.fd);
970+
} else {
971+
throw new Error('Invalid listen argument: ' + h);
972+
}
939973
} else if (isPipeName(arguments[0])) {
940974
// UNIX socket or Windows pipe.
941975
var pipeName = self._pipeName = arguments[0];

test/simple/test-cluster-basic.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ else if (cluster.isMaster) {
135135
assert.equal(arguments.length, 1);
136136
var expect = { address: '127.0.0.1',
137137
port: common.PORT,
138-
addressType: 4 };
138+
addressType: 4,
139+
fd: undefined };
139140
assert.deepEqual(arguments[0], expect);
140141
break;
141142

0 commit comments

Comments
 (0)