diff --git a/doc/api/cluster.markdown b/doc/api/cluster.markdown index c648f5942b..0464b898cc 100644 --- a/doc/api/cluster.markdown +++ b/doc/api/cluster.markdown @@ -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 + + + +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, + 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} diff --git a/doc/api/http.markdown b/doc/api/http.markdown index 962aa49081..ac8518edc9 100644 --- a/doc/api/http.markdown +++ b/doc/api/http.markdown @@ -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: }` 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. diff --git a/doc/api/net.markdown b/doc/api/net.markdown index 7fb07df955..41923ae67c 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -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: }` 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 diff --git a/lib/cluster.js b/lib/cluster.js index 6245fc8e1c..29ba436452 100644 --- a/lib/cluster.js +++ b/lib/cluster.js @@ -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 diff --git a/lib/net.js b/lib/net.js index 26246a21ce..95c3508d91 100644 --- a/lib/net.js +++ b/lib/net.js @@ -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]; diff --git a/test/simple/test-cluster-basic.js b/test/simple/test-cluster-basic.js index 0d16ee3205..939dbafe02 100644 --- a/test/simple/test-cluster-basic.js +++ b/test/simple/test-cluster-basic.js @@ -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; diff --git a/test/simple/test-listen-fd-cluster.js b/test/simple/test-listen-fd-cluster.js new file mode 100644 index 0000000000..0d6d7748aa --- /dev/null +++ b/test/simple/test-listen-fd-cluster.js @@ -0,0 +1,136 @@ +// 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 http = require('http'); +var net = require('net'); +var PORT = common.PORT; +var spawn = require('child_process').spawn; +var cluster = require('cluster'); + +console.error('Cluster listen fd test', process.argv.slice(2)); + +if (process.platform === 'win32') { + console.error('This test is disabled on windows.'); + return; +} + +switch (process.argv[2]) { + case 'master': return master(); + case 'worker': return worker(); + case 'parent': return parent(); + default: return test(); +} + +// spawn the parent, and listen for it to tell us the pid of the cluster. +// WARNING: This is an example of listening on some arbitrary FD number +// that has already been bound elsewhere in advance. However, binding +// server handles to stdio fd's is NOT a good or reliable way to do +// concurrency in HTTP servers! Use the cluster module, or if you want +// a more low-level approach, use child process IPC manually. +function test() { + var parent = spawn(process.execPath, [__filename, 'parent'], { + stdio: [ 0, 'pipe', 2 ] + }); + var json = ''; + parent.stdout.on('data', function(c) { + json += c.toString(); + if (json.indexOf('\n') !== -1) next(); + }); + function next() { + console.error('output from parent = %s', json); + var cluster = JSON.parse(json); + // now make sure that we can request to the worker, then kill it. + http.get({ + server: 'localhost', + port: PORT, + path: '/', + }).on('response', function (res) { + var s = ''; + res.on('data', function(c) { + s += c.toString(); + }); + res.on('end', function() { + // kill the worker before we start doing asserts. + // it's really annoying when tests leave orphans! + parent.kill(); + process.kill(cluster.master, 'SIGKILL'); + + assert.equal(s, 'hello from worker\n'); + assert.equal(res.statusCode, 200); + console.log('ok'); + }); + }) + } +} + +function parent() { + console.error('about to listen in parent'); + var server = net.createServer(function(conn) { + console.error('connection on parent'); + conn.end('hello from parent\n'); + }).listen(PORT, function() { + console.error('server listening on %d', PORT); + + var spawn = require('child_process').spawn; + var master = spawn(process.execPath, [__filename, 'master'], { + stdio: [ 0, 1, 2, server._handle ], + detached: true + }); + + // Now close the parent, so that the master is the only thing + // referencing that handle. Note that connections will still + // be accepted, because the master has the fd open. + server.close(); + + master.on('exit', function(code) { + console.error('master exited', code); + }); + + master.on('close', function() { + console.error('master closed'); + }); + console.error('master spawned'); + }); +} + +function master() { + console.error('in master, spawning worker'); + cluster.setupMaster({ + args: [ 'worker' ] + }); + var worker = cluster.fork(); + console.log('%j\n', { master: process.pid, worker: worker.pid }); +} + + +function worker() { + console.error('worker, about to create server and listen on fd=3'); + // start a server on fd=3 + http.createServer(function(req, res) { + console.error('request on worker'); + console.error('%s %s', req.method, req.url, req.headers); + res.end('hello from worker\n'); + }).listen({ fd: 3 }, function() { + console.error('worker listening on fd=3'); + }); +} diff --git a/test/simple/test-listen-fd-detached-inherit.js b/test/simple/test-listen-fd-detached-inherit.js new file mode 100644 index 0000000000..dabc46cd31 --- /dev/null +++ b/test/simple/test-listen-fd-detached-inherit.js @@ -0,0 +1,119 @@ +// 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 http = require('http'); +var net = require('net'); +var PORT = common.PORT; +var spawn = require('child_process').spawn; + +if (process.platform === 'win32') { + console.error('This test is disabled on windows.'); + return; +} + +switch (process.argv[2]) { + case 'child': return child(); + case 'parent': return parent(); + default: return test(); +} + +// spawn the parent, and listen for it to tell us the pid of the child. +// WARNING: This is an example of listening on some arbitrary FD number +// that has already been bound elsewhere in advance. However, binding +// server handles to stdio fd's is NOT a good or reliable way to do +// concurrency in HTTP servers! Use the cluster module, or if you want +// a more low-level approach, use child process IPC manually. +function test() { + var parent = spawn(process.execPath, [__filename, 'parent'], { + stdio: [ 0, 'pipe', 2 ] + }); + var json = ''; + parent.stdout.on('data', function(c) { + json += c.toString(); + if (json.indexOf('\n') !== -1) next(); + }); + function next() { + console.error('output from parent = %s', json); + var child = JSON.parse(json); + // now make sure that we can request to the child, then kill it. + http.get({ + server: 'localhost', + port: PORT, + path: '/', + }).on('response', function (res) { + var s = ''; + res.on('data', function(c) { + s += c.toString(); + }); + res.on('end', function() { + // kill the child before we start doing asserts. + // it's really annoying when tests leave orphans! + process.kill(child.pid, 'SIGKILL'); + try { + parent.kill(); + } catch (e) {} + + assert.equal(s, 'hello from child\n'); + assert.equal(res.statusCode, 200); + }); + }) + } +} + +// Listen on PORT, and then pass the handle to the detached child. +// Then output the child's pid, and immediately exit. +function parent() { + var server = net.createServer(function(conn) { + throw new Error('Should not see connections on parent'); + conn.end('HTTP/1.1 403 Forbidden\r\n\r\nI got problems.\r\n'); + }).listen(PORT, function() { + console.error('server listening on %d', PORT); + + var child = spawn(process.execPath, [__filename, 'child'], { + stdio: [ 0, 1, 2, server._handle ], + detached: true + }); + + console.log('%j\n', { pid: child.pid }); + + // Now close the parent, so that the child is the only thing + // referencing that handle. Note that connections will still + // be accepted, because the child has the fd open, but the parent + // will exit gracefully. + server.close(); + child.unref(); + }); +} + +// Run as a child of the parent() mode. +function child() { + // start a server on fd=3 + http.createServer(function(req, res) { + console.error('request on child'); + console.error('%s %s', req.method, req.url, req.headers); + res.end('hello from child\n'); + }).listen({ fd: 3 }, function() { + console.error('child listening on fd=3'); + }); +} + diff --git a/test/simple/test-listen-fd-detached.js b/test/simple/test-listen-fd-detached.js new file mode 100644 index 0000000000..3d98abeacd --- /dev/null +++ b/test/simple/test-listen-fd-detached.js @@ -0,0 +1,117 @@ +// 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 http = require('http'); +var net = require('net'); +var PORT = common.PORT; +var spawn = require('child_process').spawn; + +if (process.platform === 'win32') { + console.error('This test is disabled on windows.'); + return; +} + +switch (process.argv[2]) { + case 'child': return child(); + case 'parent': return parent(); + default: return test(); +} + +// spawn the parent, and listen for it to tell us the pid of the child. +// WARNING: This is an example of listening on some arbitrary FD number +// that has already been bound elsewhere in advance. However, binding +// server handles to stdio fd's is NOT a good or reliable way to do +// concurrency in HTTP servers! Use the cluster module, or if you want +// a more low-level approach, use child process IPC manually. +function test() { + var parent = spawn(process.execPath, [__filename, 'parent'], { + stdio: [ 0, 'pipe', 2 ] + }); + var json = ''; + parent.stdout.on('data', function(c) { + json += c.toString(); + if (json.indexOf('\n') !== -1) next(); + }); + function next() { + console.error('output from parent = %s', json); + var child = JSON.parse(json); + // now make sure that we can request to the child, then kill it. + http.get({ + server: 'localhost', + port: PORT, + path: '/', + }).on('response', function (res) { + var s = ''; + res.on('data', function(c) { + s += c.toString(); + }); + res.on('end', function() { + // kill the child before we start doing asserts. + // it's really annoying when tests leave orphans! + process.kill(child.pid, 'SIGKILL'); + try { + parent.kill(); + } catch (e) {} + + assert.equal(s, 'hello from child\n'); + assert.equal(res.statusCode, 200); + }); + }) + } +} + +function parent() { + var server = net.createServer(function(conn) { + console.error('connection on parent'); + conn.end('hello from parent\n'); + }).listen(PORT, function() { + console.error('server listening on %d', PORT); + + var spawn = require('child_process').spawn; + var child = spawn(process.execPath, [__filename, 'child'], { + stdio: [ 'ignore', 'ignore', 'ignore', server._handle ], + detached: true + }); + + console.log('%j\n', { pid: child.pid }); + + // Now close the parent, so that the child is the only thing + // referencing that handle. Note that connections will still + // be accepted, because the child has the fd open, but the parent + // will exit gracefully. + server.close(); + child.unref(); + }); +} + +function child() { + // start a server on fd=3 + http.createServer(function(req, res) { + console.error('request on child'); + console.error('%s %s', req.method, req.url, req.headers); + res.end('hello from child\n'); + }).listen({ fd: 3 }, function() { + console.error('child listening on fd=3'); + }); +} + diff --git a/test/simple/test-listen-fd-server.js b/test/simple/test-listen-fd-server.js new file mode 100644 index 0000000000..8f3454fbbd --- /dev/null +++ b/test/simple/test-listen-fd-server.js @@ -0,0 +1,122 @@ +// 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 http = require('http'); +var net = require('net'); +var PORT = common.PORT; +var spawn = require('child_process').spawn; + +if (process.platform === 'win32') { + console.error('This test is disabled on windows.'); + return; +} + +switch (process.argv[2]) { + case 'child': return child(); + case 'parent': return parent(); + default: return test(); +} + +// spawn the parent, and listen for it to tell us the pid of the child. +// WARNING: This is an example of listening on some arbitrary FD number +// that has already been bound elsewhere in advance. However, binding +// server handles to stdio fd's is NOT a good or reliable way to do +// concurrency in HTTP servers! Use the cluster module, or if you want +// a more low-level approach, use child process IPC manually. +function test() { + var parent = spawn(process.execPath, [__filename, 'parent'], { + stdio: [ 0, 'pipe', 2 ] + }); + var json = ''; + parent.stdout.on('data', function(c) { + json += c.toString(); + if (json.indexOf('\n') !== -1) next(); + }); + function next() { + console.error('output from parent = %s', json); + var child = JSON.parse(json); + // now make sure that we can request to the child, then kill it. + http.get({ + server: 'localhost', + port: PORT, + path: '/', + }).on('response', function (res) { + var s = ''; + res.on('data', function(c) { + s += c.toString(); + }); + res.on('end', function() { + // kill the child before we start doing asserts. + // it's really annoying when tests leave orphans! + process.kill(child.pid, 'SIGKILL'); + try { + parent.kill(); + } catch (e) {} + + assert.equal(s, 'hello from child\n'); + assert.equal(res.statusCode, 200); + }); + }) + } +} + +function child() { + // start a server on fd=3 + http.createServer(function(req, res) { + console.error('request on child'); + console.error('%s %s', req.method, req.url, req.headers); + res.end('hello from child\n'); + }).listen({ fd: 3 }, function() { + console.error('child listening on fd=3'); + }); +} + +function parent() { + var server = net.createServer(function(conn) { + console.error('connection on parent'); + conn.end('hello from parent\n'); + }).listen(PORT, function() { + console.error('server listening on %d', PORT); + + var spawn = require('child_process').spawn; + var child = spawn(process.execPath, [__filename, 'child'], { + stdio: [ 0, 1, 2, server._handle ] + }); + + console.log('%j\n', { pid: child.pid }); + + // Now close the parent, so that the child is the only thing + // referencing that handle. Note that connections will still + // be accepted, because the child has the fd open. + server.close(); + + child.on('exit', function(code) { + console.error('child exited', code); + }); + + child.on('close', function() { + console.error('child closed'); + }); + console.error('child spawned'); + }); +}