child_process: allow sending a net Socket and Server object using child.send
child_process.fork() support sending native hander object, this patch add support for sending net.Server and net.Socket object by converting the object to a native handle object and back to a useful object again. Note when sending a Socket there was emitted by a net Server object, the server.connections property becomes null, because it is no longer possible to known when it is destroyed.
This commit is contained in:
parent
49f16c4575
commit
dceebbfa31
@ -55,7 +55,7 @@ An alternative way to check if you can send messages is to see if the
|
|||||||
### Event: 'message'
|
### Event: 'message'
|
||||||
|
|
||||||
* `message` {Object} a parsed JSON object or primitive value
|
* `message` {Object} a parsed JSON object or primitive value
|
||||||
* `sendHandle` {Handle object} a handle object
|
* `sendHandle` {Handle object} a Socket or Server object
|
||||||
|
|
||||||
Messages send by `.send(message, [sendHandle])` are obtained using the
|
Messages send by `.send(message, [sendHandle])` are obtained using the
|
||||||
`message` event.
|
`message` event.
|
||||||
@ -129,7 +129,7 @@ See `kill(2)`
|
|||||||
* `message` {Object}
|
* `message` {Object}
|
||||||
* `sendHandle` {Handle object}
|
* `sendHandle` {Handle object}
|
||||||
|
|
||||||
When useing `child_process.fork()` an you can write to the child using
|
When using `child_process.fork()` you can write to the child using
|
||||||
`child.send(message, [sendHandle])` and messages are received by
|
`child.send(message, [sendHandle])` and messages are received by
|
||||||
a `'message'` event on the child.
|
a `'message'` event on the child.
|
||||||
|
|
||||||
@ -162,9 +162,73 @@ the `message` event, since they are internal messages used by node core.
|
|||||||
Messages containing the prefix are emitted in the `internalMessage` event, you
|
Messages containing the prefix are emitted in the `internalMessage` event, you
|
||||||
should by all means avoid using this feature, it is subject to change without notice.
|
should by all means avoid using this feature, it is subject to change without notice.
|
||||||
|
|
||||||
The `sendHandle` option to `child.send()` is for sending a handle object to
|
The `sendHandle` option to `child.send()` is for sending a TCP server or
|
||||||
another process. The child will receive the object as its second argument to
|
socket object to another process. The child will receive the object as its
|
||||||
the `message` event.
|
second argument to the `message` event.
|
||||||
|
|
||||||
|
**send server object**
|
||||||
|
|
||||||
|
Here is an example of sending a server:
|
||||||
|
|
||||||
|
var child = require('child_process').fork('child.js');
|
||||||
|
|
||||||
|
// Open up the server object and send the handle.
|
||||||
|
var server = require('net').createServer();
|
||||||
|
server.on('connection', function (socket) {
|
||||||
|
socket.end('handled by parent');
|
||||||
|
});
|
||||||
|
server.listen(1337, function() {
|
||||||
|
child.send('server', server);
|
||||||
|
});
|
||||||
|
|
||||||
|
And the child would the recive the server object as:
|
||||||
|
|
||||||
|
process.on('message', function(m, server) {
|
||||||
|
if (m === 'server') {
|
||||||
|
server.on('connection', function (socket) {
|
||||||
|
socket.end('handled by child');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Note that the server is now shared between the parent and child, this means
|
||||||
|
that some connections will be handled by the parent and some by the child.
|
||||||
|
|
||||||
|
**send socket object**
|
||||||
|
|
||||||
|
Here is an example of sending a socket. It will spawn two childs and handle
|
||||||
|
connections with the remote address `74.125.127.100` as VIP by sending the
|
||||||
|
socket to a "special" child process. Other sockets will go to a "normal" process.
|
||||||
|
|
||||||
|
var normal = require('child_process').fork('child.js', ['normal']);
|
||||||
|
var special = require('child_process').fork('child.js', ['special']);
|
||||||
|
|
||||||
|
// Open up the server and send sockets to child
|
||||||
|
var server = require('net').createServer();
|
||||||
|
server.on('connection', function (socket) {
|
||||||
|
|
||||||
|
// if this is a VIP
|
||||||
|
if (socket.remoteAddress === '74.125.127.100') {
|
||||||
|
special.send('socket', socket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// just the usual dudes
|
||||||
|
normal.send('socket', socket);
|
||||||
|
});
|
||||||
|
server.listen(1337);
|
||||||
|
|
||||||
|
The `child.js` could look like this:
|
||||||
|
|
||||||
|
process.on('message', function(m, socket) {
|
||||||
|
if (m === 'socket') {
|
||||||
|
socket.end('You where handled as a ' + process.argv[2] + ' person');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Note that once a single socket has been sent to a child the parent can no
|
||||||
|
longer keep track of when the socket is destroyed. To indicate this condition
|
||||||
|
the `.connections` property becomes `null`.
|
||||||
|
It is also recomended not to use `.maxConnections` in this condition.
|
||||||
|
|
||||||
### child.disconnect()
|
### child.disconnect()
|
||||||
|
|
||||||
@ -382,7 +446,7 @@ leaner than `child_process.exec`. It has the same options.
|
|||||||
|
|
||||||
This is a special case of the `spawn()` functionality for spawning Node
|
This is a special case of the `spawn()` functionality for spawning Node
|
||||||
processes. In addition to having all the methods in a normal ChildProcess
|
processes. In addition to having all the methods in a normal ChildProcess
|
||||||
instance, the returned object has a communication channel built-in. Se
|
instance, the returned object has a communication channel built-in. See
|
||||||
`child.send(message, [sendHandle])` for details.
|
`child.send(message, [sendHandle])` for details.
|
||||||
|
|
||||||
By default the spawned Node process will have the stdout, stderr associated
|
By default the spawned Node process will have the stdout, stderr associated
|
||||||
|
@ -198,10 +198,14 @@ Don't call `server.address()` until the `'listening'` event has been emitted.
|
|||||||
Set this property to reject connections when the server's connection count gets
|
Set this property to reject connections when the server's connection count gets
|
||||||
high.
|
high.
|
||||||
|
|
||||||
|
It is not recommended to use this option once a socket has been sent to a child
|
||||||
|
with `child_process.fork()`.
|
||||||
|
|
||||||
### server.connections
|
### server.connections
|
||||||
|
|
||||||
The number of concurrent connections on the server.
|
The number of concurrent connections on the server.
|
||||||
|
|
||||||
|
This becomes `null` when sending a socket to a child with `child_process.fork()`.
|
||||||
|
|
||||||
`net.Server` is an `EventEmitter` with the following events:
|
`net.Server` is an `EventEmitter` with the following events:
|
||||||
|
|
||||||
|
@ -54,15 +54,207 @@ function createSocket(pipe, readable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// this object contain function to convert TCP objects to native handle objects
|
||||||
|
// and back again.
|
||||||
|
var handleConversion = {
|
||||||
|
'net.Native': {
|
||||||
|
simultaneousAccepts: true,
|
||||||
|
|
||||||
|
send: function(message, handle) {
|
||||||
|
return handle;
|
||||||
|
},
|
||||||
|
|
||||||
|
got: function(message, handle, emit) {
|
||||||
|
emit(handle);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'net.Server': {
|
||||||
|
simultaneousAccepts: true,
|
||||||
|
|
||||||
|
send: function(message, server) {
|
||||||
|
return server._handle;
|
||||||
|
},
|
||||||
|
|
||||||
|
got: function(message, handle, emit) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var server = new net.Server();
|
||||||
|
server.listen(handle, function() {
|
||||||
|
emit(server);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
'net.Socket': {
|
||||||
|
send: function(message, socket) {
|
||||||
|
// pause socket so no data is lost, will be resumed later
|
||||||
|
socket.pause();
|
||||||
|
|
||||||
|
// if the socket wsa created by net.Server
|
||||||
|
if (socket.server) {
|
||||||
|
// the slave should keep track of the socket
|
||||||
|
message.key = socket.server._connectionKey;
|
||||||
|
|
||||||
|
var firstTime = !this._channel.sockets.send[message.key];
|
||||||
|
|
||||||
|
// add socket to connections list
|
||||||
|
var socketList = getSocketList('send', this, message.key);
|
||||||
|
socketList.add(socket);
|
||||||
|
|
||||||
|
// the server should no longer expose a .connection property
|
||||||
|
// and when asked to close it should query the socket status from slaves
|
||||||
|
if (firstTime) {
|
||||||
|
socket.server._setupSlave(socketList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove handle from socket object, it will be closed when the socket
|
||||||
|
// has been send
|
||||||
|
var handle = socket._handle;
|
||||||
|
handle.onread = function() {};
|
||||||
|
socket._handle = null;
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
},
|
||||||
|
|
||||||
|
got: function(message, handle, emit) {
|
||||||
|
var socket = new net.Socket({handle: handle});
|
||||||
|
socket.readable = socket.writable = true;
|
||||||
|
socket.pause();
|
||||||
|
|
||||||
|
// if the socket was created by net.Server we will track the socket
|
||||||
|
if (message.key) {
|
||||||
|
|
||||||
|
// add socket to connections list
|
||||||
|
var socketList = getSocketList('got', this, message.key);
|
||||||
|
socketList.add(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(socket);
|
||||||
|
socket.resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This object keep track of the socket there are sended
|
||||||
|
function SocketListSend(slave, key) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.key = key;
|
||||||
|
this.list = [];
|
||||||
|
this.slave = slave;
|
||||||
|
|
||||||
|
slave.once('disconnect', function() {
|
||||||
|
self.flush();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.slave.on('internalMessage', function(msg) {
|
||||||
|
if (msg.cmd !== 'NODE_SOCKET_CLOSED' || msg.key !== self.key) return;
|
||||||
|
self.flush();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
util.inherits(SocketListSend, EventEmitter);
|
||||||
|
|
||||||
|
SocketListSend.prototype.add = function(socket) {
|
||||||
|
this.list.push(socket);
|
||||||
|
};
|
||||||
|
|
||||||
|
SocketListSend.prototype.flush = function() {
|
||||||
|
var list = this.list;
|
||||||
|
this.list = [];
|
||||||
|
|
||||||
|
list.forEach(function(socket) {
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
SocketListSend.prototype.update = function() {
|
||||||
|
if (this.slave.connected === false) return;
|
||||||
|
|
||||||
|
this.slave.send({
|
||||||
|
cmd: 'NODE_SOCKET_FETCH',
|
||||||
|
key: this.key
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// This object keep track of the socket there are received
|
||||||
|
function SocketListReceive(slave, key) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.key = key;
|
||||||
|
this.list = [];
|
||||||
|
this.slave = slave;
|
||||||
|
|
||||||
|
slave.on('internalMessage', function(msg) {
|
||||||
|
if (msg.cmd !== 'NODE_SOCKET_FETCH' || msg.key !== self.key) return;
|
||||||
|
|
||||||
|
if (self.list.length === 0) {
|
||||||
|
self.flush();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.on('itemRemoved', function removeMe() {
|
||||||
|
if (self.list.length !== 0) return;
|
||||||
|
self.removeListener('itemRemoved', removeMe);
|
||||||
|
self.flush();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
util.inherits(SocketListReceive, EventEmitter);
|
||||||
|
|
||||||
|
SocketListReceive.prototype.flush = function() {
|
||||||
|
this.list = [];
|
||||||
|
|
||||||
|
if (this.slave.connected) {
|
||||||
|
this.slave.send({
|
||||||
|
cmd: 'NODE_SOCKET_CLOSED',
|
||||||
|
key: this.key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SocketListReceive.prototype.add = function(socket) {
|
||||||
|
var self = this;
|
||||||
|
this.list.push(socket);
|
||||||
|
|
||||||
|
socket.on('close', function() {
|
||||||
|
self.list.splice(self.list.indexOf(socket), 1);
|
||||||
|
self.emit('itemRemoved');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function getSocketList(type, slave, key) {
|
||||||
|
var sockets = slave._channel.sockets[type];
|
||||||
|
var socketList = sockets[key];
|
||||||
|
if (!socketList) {
|
||||||
|
var Construct = type === 'send' ? SocketListSend : SocketListReceive;
|
||||||
|
socketList = sockets[key] = new Construct(slave, key);
|
||||||
|
}
|
||||||
|
return socketList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMessage(target, message, handle) {
|
||||||
|
//Filter out internal messages
|
||||||
|
//if cmd property begin with "_NODE"
|
||||||
|
if (message !== null &&
|
||||||
|
typeof message === 'object' &&
|
||||||
|
typeof message.cmd === 'string' &&
|
||||||
|
message.cmd.indexOf('NODE_') === 0) {
|
||||||
|
target.emit('internalMessage', message, handle);
|
||||||
|
}
|
||||||
|
//Non-internal message
|
||||||
|
else {
|
||||||
|
target.emit('message', message, handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setupChannel(target, channel) {
|
function setupChannel(target, channel) {
|
||||||
target._channel = channel;
|
target._channel = channel;
|
||||||
|
|
||||||
var jsonBuffer = '';
|
var jsonBuffer = '';
|
||||||
channel.buffering = false;
|
channel.buffering = false;
|
||||||
channel.onread = function(pool, offset, length, recvHandle) {
|
channel.onread = function(pool, offset, length, recvHandle) {
|
||||||
// Update simultaneous accepts on Windows
|
|
||||||
net._setSimultaneousAccepts(recvHandle);
|
|
||||||
|
|
||||||
if (pool) {
|
if (pool) {
|
||||||
jsonBuffer += pool.toString('ascii', offset, offset + length);
|
jsonBuffer += pool.toString('ascii', offset, offset + length);
|
||||||
|
|
||||||
@ -73,18 +265,7 @@ function setupChannel(target, channel) {
|
|||||||
var json = jsonBuffer.slice(start, i);
|
var json = jsonBuffer.slice(start, i);
|
||||||
var message = JSON.parse(json);
|
var message = JSON.parse(json);
|
||||||
|
|
||||||
//Filter out internal messages
|
handleMessage(target, message, recvHandle);
|
||||||
//if cmd property begin with "_NODE"
|
|
||||||
if (message !== null &&
|
|
||||||
typeof message === 'object' &&
|
|
||||||
typeof message.cmd === 'string' &&
|
|
||||||
message.cmd.indexOf('NODE_') === 0) {
|
|
||||||
target.emit('internalMessage', message, recvHandle);
|
|
||||||
}
|
|
||||||
//Non-internal message
|
|
||||||
else {
|
|
||||||
target.emit('message', message, recvHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
start = i + 1;
|
start = i + 1;
|
||||||
}
|
}
|
||||||
@ -97,7 +278,27 @@ function setupChannel(target, channel) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
target.send = function(message, sendHandle) {
|
// object where socket lists will live
|
||||||
|
channel.sockets = { got: {}, send: {} };
|
||||||
|
|
||||||
|
// handlers will go through this
|
||||||
|
target.on('internalMessage', function(message, handle) {
|
||||||
|
if (message.cmd !== 'NODE_HANDLE') return;
|
||||||
|
|
||||||
|
var obj = handleConversion[message.type];
|
||||||
|
|
||||||
|
// Update simultaneous accepts on Windows
|
||||||
|
if (obj.simultaneousAccepts) {
|
||||||
|
net._setSimultaneousAccepts(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert handle object
|
||||||
|
obj.got.call(this, message, handle, function(handle) {
|
||||||
|
handleMessage(target, message.msg, handle);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
target.send = function(message, handle) {
|
||||||
if (typeof message === 'undefined') {
|
if (typeof message === 'undefined') {
|
||||||
throw new TypeError('message cannot be undefined');
|
throw new TypeError('message cannot be undefined');
|
||||||
}
|
}
|
||||||
@ -112,12 +313,43 @@ function setupChannel(target, channel) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var string = JSON.stringify(message) + '\n';
|
// package messages with a handle object
|
||||||
|
if (handle) {
|
||||||
|
// this message will be handled by an internalMessage event handler
|
||||||
|
message = {
|
||||||
|
cmd: 'NODE_HANDLE',
|
||||||
|
type: 'net.',
|
||||||
|
msg: message
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (handle.constructor.name) {
|
||||||
|
case 'Socket':
|
||||||
|
message.type += 'Socket'; break;
|
||||||
|
case 'Server':
|
||||||
|
message.type += 'Server'; break;
|
||||||
|
case 'Pipe':
|
||||||
|
case 'TCP':
|
||||||
|
message.type += 'Native'; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = handleConversion[message.type];
|
||||||
|
|
||||||
|
// convert TCP object to native handle object
|
||||||
|
handle = handleConversion[message.type].send.apply(target, arguments);
|
||||||
|
|
||||||
// Update simultaneous accepts on Windows
|
// Update simultaneous accepts on Windows
|
||||||
net._setSimultaneousAccepts(sendHandle);
|
if (obj.simultaneousAccepts) {
|
||||||
|
net._setSimultaneousAccepts(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var writeReq = channel.writeUtf8String(string, sendHandle);
|
var string = JSON.stringify(message) + '\n';
|
||||||
|
var writeReq = channel.writeUtf8String(string, handle);
|
||||||
|
|
||||||
|
// Close the Socket handle after sending it
|
||||||
|
if (message && message.type === 'net.Socket') {
|
||||||
|
handle.close();
|
||||||
|
}
|
||||||
|
|
||||||
if (!writeReq) {
|
if (!writeReq) {
|
||||||
var er = errnoException(errno, 'write', 'cannot write to IPC channel.');
|
var er = errnoException(errno, 'write', 'cannot write to IPC channel.');
|
||||||
|
46
lib/net.js
46
lib/net.js
@ -352,7 +352,7 @@ Socket.prototype._destroy = function(exception, cb) {
|
|||||||
timers.unenroll(this);
|
timers.unenroll(this);
|
||||||
|
|
||||||
if (this.server) {
|
if (this.server) {
|
||||||
this.server.connections--;
|
this.server._connections--;
|
||||||
this.server._emitCloseIfDrained();
|
this.server._emitCloseIfDrained();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -800,7 +800,23 @@ function Server(/* [ options, ] listener */) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connections = 0;
|
this._connections = 0;
|
||||||
|
|
||||||
|
// when server is using slaves .connections is not reliable
|
||||||
|
// so null will be return if thats the case
|
||||||
|
Object.defineProperty(this, 'connections', {
|
||||||
|
get: function() {
|
||||||
|
if (self._usingSlaves) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return self._connections;
|
||||||
|
},
|
||||||
|
set: function(val) {
|
||||||
|
return (self._connections = val);
|
||||||
|
},
|
||||||
|
configurable: true, enumerable: true
|
||||||
|
});
|
||||||
|
|
||||||
this.allowHalfOpen = options.allowHalfOpen || false;
|
this.allowHalfOpen = options.allowHalfOpen || false;
|
||||||
|
|
||||||
this._handle = null;
|
this._handle = null;
|
||||||
@ -881,6 +897,9 @@ Server.prototype._listen2 = function(address, port, addressType, backlog) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generate connection key, this should be unique to the connection
|
||||||
|
this._connectionKey = addressType + ':' + address + ':' + port;
|
||||||
|
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
self.emit('listening');
|
self.emit('listening');
|
||||||
});
|
});
|
||||||
@ -970,7 +989,7 @@ function onconnection(clientHandle) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.maxConnections && self.connections >= self.maxConnections) {
|
if (self.maxConnections && self._connections >= self.maxConnections) {
|
||||||
clientHandle.close();
|
clientHandle.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -983,7 +1002,7 @@ function onconnection(clientHandle) {
|
|||||||
|
|
||||||
socket.resume();
|
socket.resume();
|
||||||
|
|
||||||
self.connections++;
|
self._connections++;
|
||||||
socket.server = self;
|
socket.server = self;
|
||||||
|
|
||||||
DTRACE_NET_SERVER_CONNECTION(socket);
|
DTRACE_NET_SERVER_CONNECTION(socket);
|
||||||
@ -1005,13 +1024,21 @@ Server.prototype.close = function(cb) {
|
|||||||
this._handle = null;
|
this._handle = null;
|
||||||
this._emitCloseIfDrained();
|
this._emitCloseIfDrained();
|
||||||
|
|
||||||
|
// fetch new socket lists
|
||||||
|
if (this._usingSlaves) {
|
||||||
|
this._slaves.forEach(function(socketList) {
|
||||||
|
if (socketList.list.length === 0) return;
|
||||||
|
socketList.update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Server.prototype._emitCloseIfDrained = function() {
|
Server.prototype._emitCloseIfDrained = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if (self._handle || self.connections) return;
|
if (self._handle || self._connections) return;
|
||||||
|
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
self.emit('close');
|
self.emit('close');
|
||||||
@ -1023,6 +1050,15 @@ Server.prototype.listenFD = function(fd, type) {
|
|||||||
throw new Error('This API is no longer supported. See child_process.fork');
|
throw new Error('This API is no longer supported. See child_process.fork');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// when sending a socket using fork IPC this function is executed
|
||||||
|
Server.prototype._setupSlave = function(socketList) {
|
||||||
|
if (!this._usingSlaves) {
|
||||||
|
this._usingSlaves = true;
|
||||||
|
this._slaves = [];
|
||||||
|
}
|
||||||
|
this._slaves.push(socketList);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// TODO: isIP should be moved to the DNS code. Putting it here now because
|
// TODO: isIP should be moved to the DNS code. Putting it here now because
|
||||||
// this is what the legacy system did.
|
// this is what the legacy system did.
|
||||||
|
198
test/simple/test-child-process-fork-net.js
Normal file
198
test/simple/test-child-process-fork-net.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
// 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 assert = require('assert');
|
||||||
|
var common = require('../common');
|
||||||
|
var fork = require('child_process').fork;
|
||||||
|
var net = require('net');
|
||||||
|
|
||||||
|
// progress tracker
|
||||||
|
function ProgressTracker(missing, callback) {
|
||||||
|
this.missing = missing;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
ProgressTracker.prototype.done = function() {
|
||||||
|
this.missing -= 1;
|
||||||
|
this.check();
|
||||||
|
};
|
||||||
|
ProgressTracker.prototype.check = function() {
|
||||||
|
if (this.missing === 0) this.callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.argv[2] === 'child') {
|
||||||
|
|
||||||
|
var serverScope;
|
||||||
|
|
||||||
|
process.on('message', function onServer(msg, server) {
|
||||||
|
if (msg.what !== 'server') return;
|
||||||
|
process.removeListener('message', onServer);
|
||||||
|
|
||||||
|
serverScope = server;
|
||||||
|
|
||||||
|
server.on('connection', function(socket) {
|
||||||
|
console.log('CHILD: got connection');
|
||||||
|
process.send({what: 'connection'});
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
// start making connection from parent
|
||||||
|
console.log('CHILD: server listening');
|
||||||
|
process.send({what: 'listening'});
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('message', function onClose(msg) {
|
||||||
|
if (msg.what !== 'close') return;
|
||||||
|
process.removeListener('message', onClose);
|
||||||
|
|
||||||
|
serverScope.on('close', function() {
|
||||||
|
process.send({what: 'close'});
|
||||||
|
});
|
||||||
|
serverScope.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('message', function onSocket(msg, socket) {
|
||||||
|
if (msg.what !== 'socket') return;
|
||||||
|
process.removeListener('message', onSocket);
|
||||||
|
socket.end('echo');
|
||||||
|
console.log('CHILD: got socket');
|
||||||
|
});
|
||||||
|
|
||||||
|
process.send({what: 'ready'});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var child = fork(process.argv[1], ['child']);
|
||||||
|
|
||||||
|
child.on('exit', function() {
|
||||||
|
console.log('CHILD: died');
|
||||||
|
});
|
||||||
|
|
||||||
|
// send net.Server to child and test by connecting
|
||||||
|
var testServer = function(callback) {
|
||||||
|
|
||||||
|
// destroy server execute callback when done
|
||||||
|
var progress = new ProgressTracker(2, function() {
|
||||||
|
server.on('close', function() {
|
||||||
|
console.log('PARENT: server closed');
|
||||||
|
child.send({what: 'close'});
|
||||||
|
});
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// we expect 10 connections and close events
|
||||||
|
var connections = new ProgressTracker(10, progress.done.bind(progress));
|
||||||
|
var closed = new ProgressTracker(10, progress.done.bind(progress));
|
||||||
|
|
||||||
|
// create server and send it to child
|
||||||
|
var server = net.createServer();
|
||||||
|
server.on('connection', function(socket) {
|
||||||
|
console.log('PARENT: got connection');
|
||||||
|
socket.destroy();
|
||||||
|
connections.done();
|
||||||
|
});
|
||||||
|
server.on('listening', function() {
|
||||||
|
console.log('PARENT: server listening');
|
||||||
|
child.send({what: 'server'}, server);
|
||||||
|
});
|
||||||
|
server.listen(common.PORT);
|
||||||
|
|
||||||
|
// handle client messages
|
||||||
|
var messageHandlers = function(msg) {
|
||||||
|
|
||||||
|
if (msg.what === 'listening') {
|
||||||
|
// make connections
|
||||||
|
var socket;
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
socket = net.connect(common.PORT, function() {
|
||||||
|
console.log('CLIENT: connected');
|
||||||
|
});
|
||||||
|
socket.on('close', function() {
|
||||||
|
closed.done();
|
||||||
|
console.log('CLIENT: closed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (msg.what === 'connection') {
|
||||||
|
// child got connection
|
||||||
|
connections.done();
|
||||||
|
} else if (msg.what === 'close') {
|
||||||
|
child.removeListener('message', messageHandlers);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
child.on('message', messageHandlers);
|
||||||
|
};
|
||||||
|
|
||||||
|
// send net.Socket to child
|
||||||
|
var testSocket = function(callback) {
|
||||||
|
|
||||||
|
// create a new server and connect to it,
|
||||||
|
// but the socket will be handled by the child
|
||||||
|
var server = net.createServer();
|
||||||
|
server.on('connection', function(socket) {
|
||||||
|
socket.on('close', function() {
|
||||||
|
console.log('CLIENT: socket closed');
|
||||||
|
});
|
||||||
|
child.send({what: 'socket'}, socket);
|
||||||
|
});
|
||||||
|
server.on('close', function() {
|
||||||
|
console.log('PARENT: server closed');
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
server.listen(common.PORT, function() {
|
||||||
|
var connect = net.connect(common.PORT);
|
||||||
|
var store = '';
|
||||||
|
connect.on('data', function(chunk) {
|
||||||
|
store += chunk;
|
||||||
|
console.log('CLIENT: got data');
|
||||||
|
});
|
||||||
|
connect.on('close', function() {
|
||||||
|
console.log('CLIENT: closed');
|
||||||
|
assert.equal(store, 'echo');
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// create server and send it to child
|
||||||
|
var serverSucess = false;
|
||||||
|
var socketSucess = false;
|
||||||
|
child.on('message', function onReady(msg) {
|
||||||
|
if (msg.what !== 'ready') return;
|
||||||
|
child.removeListener('message', onReady);
|
||||||
|
|
||||||
|
testServer(function() {
|
||||||
|
serverSucess = true;
|
||||||
|
|
||||||
|
testSocket(function() {
|
||||||
|
socketSucess = true;
|
||||||
|
child.kill();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('exit', function() {
|
||||||
|
assert.ok(serverSucess);
|
||||||
|
assert.ok(socketSucess);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
131
test/simple/test-child-process-fork-net2.js
Normal file
131
test/simple/test-child-process-fork-net2.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// 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 assert = require('assert');
|
||||||
|
var common = require('../common');
|
||||||
|
var fork = require('child_process').fork;
|
||||||
|
var net = require('net');
|
||||||
|
|
||||||
|
if (process.argv[2] === 'child') {
|
||||||
|
|
||||||
|
var endMe = null;
|
||||||
|
|
||||||
|
process.on('message', function(m, socket) {
|
||||||
|
if (!socket) return;
|
||||||
|
|
||||||
|
// will call .end('end') or .write('write');
|
||||||
|
socket[m](m);
|
||||||
|
|
||||||
|
// store the unfinished socket
|
||||||
|
if (m === 'write') {
|
||||||
|
endMe = socket;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('message', function(m) {
|
||||||
|
if (m !== 'close') return;
|
||||||
|
endMe.end('end');
|
||||||
|
endMe = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('disconnect', function() {
|
||||||
|
endMe.end('end');
|
||||||
|
endMe = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var child1 = fork(process.argv[1], ['child']);
|
||||||
|
var child2 = fork(process.argv[1], ['child']);
|
||||||
|
var child3 = fork(process.argv[1], ['child']);
|
||||||
|
|
||||||
|
var server = net.createServer();
|
||||||
|
|
||||||
|
var connected = 0;
|
||||||
|
server.on('connection', function(socket) {
|
||||||
|
switch (connected) {
|
||||||
|
case 0:
|
||||||
|
child1.send('end', socket); break;
|
||||||
|
case 1:
|
||||||
|
child1.send('write', socket); break;
|
||||||
|
case 2:
|
||||||
|
child2.send('end', socket); break;
|
||||||
|
case 3:
|
||||||
|
child2.send('write', socket); break;
|
||||||
|
case 4:
|
||||||
|
child3.send('end', socket); break;
|
||||||
|
case 5:
|
||||||
|
child3.send('write', socket); break;
|
||||||
|
}
|
||||||
|
connected += 1;
|
||||||
|
|
||||||
|
if (connected === 6) {
|
||||||
|
closeServer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var disconnected = 0;
|
||||||
|
server.on('listening', function() {
|
||||||
|
|
||||||
|
var j = 6, client;
|
||||||
|
while (j--) {
|
||||||
|
client = net.connect(common.PORT, '127.0.0.1');
|
||||||
|
client.on('close', function() {
|
||||||
|
disconnected += 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var closeEmitted = false;
|
||||||
|
server.on('close', function() {
|
||||||
|
closeEmitted = true;
|
||||||
|
|
||||||
|
child1.kill();
|
||||||
|
child2.kill();
|
||||||
|
child3.kill();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(common.PORT, '127.0.0.1');
|
||||||
|
|
||||||
|
var timeElasped = 0;
|
||||||
|
var closeServer = function() {
|
||||||
|
var startTime = Date.now();
|
||||||
|
server.on('close', function() {
|
||||||
|
timeElasped = Date.now() - startTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
child1.send('close');
|
||||||
|
child2.send('close');
|
||||||
|
child3.disconnect();
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('exit', function() {
|
||||||
|
assert.equal(disconnected, 6);
|
||||||
|
assert.equal(connected, 6);
|
||||||
|
assert.ok(closeEmitted);
|
||||||
|
assert.ok(timeElasped >= 190 && timeElasped <= 1000,
|
||||||
|
'timeElasped was not between 190 and 1000 ms');
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user