diff --git a/LICENSE b/LICENSE index a6bc8813ea2..f464affffc1 100644 --- a/LICENSE +++ b/LICENSE @@ -550,3 +550,31 @@ maintained libraries. The externally maintained libraries used by Node are: OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + +- src/ngx-queue.h ngx-queue.h is taken from the nginx source tree. nginx's + license follows + """ + Copyright (C) 2002-2012 Igor Sysoev + Copyright (C) 2011,2012 Nginx, Inc. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + """ diff --git a/configure b/configure index 217d9223338..fe3b5308c26 100755 --- a/configure +++ b/configure @@ -224,18 +224,22 @@ def host_arch(): def target_arch(): return host_arch() - -def gcc_version(): +def cc_version(): try: proc = subprocess.Popen([CC, '-v'], stderr=subprocess.PIPE) except OSError: return None - # TODO parse clang output - version = proc.communicate()[1].split('\n')[-2] - match = re.match('gcc version (\d+)\.(\d+)\.(\d+)', version) - if not match: return None - return ['LLVM' in version] + map(int, match.groups()) - + lines = proc.communicate()[1].split('\n') + version_line = None + for i, line in enumerate(lines): + if 'version' in line: + version_line = line + if not version_line: + return None + version = version_line.split("version")[1].strip().split()[0].split(".") + if not version: + return None + return ['LLVM' in version_line] + version def configure_node(o): # TODO add gdb @@ -250,10 +254,10 @@ def configure_node(o): # see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=45883 # see http://code.google.com/p/v8/issues/detail?id=884 o['variables']['strict_aliasing'] = b( - 'clang' in CC or gcc_version() >= [False, 4, 6, 0]) + 'clang' in CC or cc_version() >= [False, 4, 6, 0]) # clang has always supported -fvisibility=hidden, right? - if 'clang' not in CC and gcc_version() < [False, 4, 0, 0]: + if 'clang' not in CC and cc_version() < [False, 4, 0, 0]: o['variables']['visibility'] = '' # By default, enable DTrace on SunOS systems. Don't allow it on other diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown index c8a1c64c6c0..18be2f66c2e 100644 --- a/doc/api/child_process.markdown +++ b/doc/api/child_process.markdown @@ -52,6 +52,14 @@ in the child. After disconnecting it is no longer possible to send messages. An alternative way to check if you can send messages is to see if the `child.connected` property is `true`. +### Event: 'message' + +* `message` {Object} a parsed JSON object or primitive value +* `sendHandle` {Handle object} a Socket or Server object + +Messages send by `.send(message, [sendHandle])` are obtained using the +`message` event. + ### child.stdin * {Stream object} @@ -116,15 +124,120 @@ process may not actually kill it. `kill` really just sends a signal to a proces See `kill(2)` - ### child.send(message, [sendHandle]) * `message` {Object} * `sendHandle` {Handle object} -Send a message (and, optionally, a handle object) to a child process. +When using `child_process.fork()` you can write to the child using +`child.send(message, [sendHandle])` and messages are received by +a `'message'` event on the child. -See `child_process.fork()` for details. +For example: + + var cp = require('child_process'); + + var n = cp.fork(__dirname + '/sub.js'); + + n.on('message', function(m) { + console.log('PARENT got message:', m); + }); + + n.send({ hello: 'world' }); + +And then the child script, `'sub.js'` might look like this: + + process.on('message', function(m) { + console.log('CHILD got message:', m); + }); + + process.send({ foo: 'bar' }); + +In the child the `process` object will have a `send()` method, and `process` +will emit objects each time it receives a message on its channel. + +There is a special case when sending a `{cmd: 'NODE_foo'}` message. All messages +containing a `NODE_` prefix in its `cmd` property will not be emitted in +the `message` event, since they are internal messages used by node core. +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. + +The `sendHandle` option to `child.send()` is for sending a TCP server or +socket object to another process. The child will receive the object as its +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() + +To close the IPC connection between parent and child use the +`child.disconnect()` method. This allows the child to exit gracefully since +there is no IPC channel keeping it alive. When calling this method the +`disconnect` event will be emitted in both parent and child, and the +`connected` flag will be set to `false`. Please note that you can also call +`process.disconnect()` in the child process. ## child_process.spawn(command, [args], [options]) @@ -333,38 +446,8 @@ leaner than `child_process.exec`. It has the same options. This is a special case of the `spawn()` functionality for spawning Node processes. In addition to having all the methods in a normal ChildProcess -instance, the returned object has a communication channel built-in. The -channel is written to with `child.send(message, [sendHandle])` and messages -are received by a `'message'` event on the child. - -For example: - - var cp = require('child_process'); - - var n = cp.fork(__dirname + '/sub.js'); - - n.on('message', function(m) { - console.log('PARENT got message:', m); - }); - - n.send({ hello: 'world' }); - -And then the child script, `'sub.js'` might look like this: - - process.on('message', function(m) { - console.log('CHILD got message:', m); - }); - - process.send({ foo: 'bar' }); - -In the child the `process` object will have a `send()` method, and `process` -will emit objects each time it receives a message on its channel. - -There is a special case when sending a `{cmd: 'NODE_foo'}` message. All messages -containing a `NODE_` prefix in its `cmd` property will not be emitted in -the `message` event, since they are internal messages used by node core. -Messages containing the prefix are emitted in the `internalMessage` event, you -should by all means avoid using this feature, it may change without warranty. +instance, the returned object has a communication channel built-in. See +`child.send(message, [sendHandle])` for details. By default the spawned Node process will have the stdout, stderr associated with the parent's. To change this behavior set the `silent` property in the @@ -373,31 +456,3 @@ with the parent's. To change this behavior set the `silent` property in the These child Nodes are still whole new instances of V8. Assume at least 30ms startup and 10mb memory for each new Node. That is, you cannot create many thousands of them. - -The `sendHandle` option to `child.send()` is for sending a handle object to -another process. Child will receive the handle as as second argument to the -`message` event. Here is an example of sending a handle: - - var server = require('net').createServer(); - var child = require('child_process').fork(__dirname + '/child.js'); - // Open up the server object and send the handle. - server.listen(1337, function() { - child.send({ server: true }, server._handle); - }); - -Here is an example of receiving the server handle and sharing it between -processes: - - process.on('message', function(m, serverHandle) { - if (serverHandle) { - var server = require('net').createServer(); - server.listen(serverHandle); - } - }); - -To close the IPC connection between parent and child use the -`child.disconnect()` method. This allows the child to exit gracefully since -there is no IPC channel keeping it alive. When calling this method the -`disconnect` event will be emitted in both parent and child, and the -`connected` flag will be set to `false`. Please note that you can also call -`process.disconnect()` in the child process. diff --git a/doc/api/net.markdown b/doc/api/net.markdown index 9ef058cb80f..7fb07df9558 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -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 high. +It is not recommended to use this option once a socket has been sent to a child +with `child_process.fork()`. + ### server.connections 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: diff --git a/lib/buffer.js b/lib/buffer.js index de1efcb5ed6..70fefedad8a 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -33,6 +33,10 @@ function binaryWarn() { exports.INSPECT_MAX_BYTES = 50; +// Make SlowBuffer inherit from Buffer. +// This is an exception to the rule that __proto__ is not allowed in core. +SlowBuffer.prototype.__proto__ = Buffer.prototype; + function toHex(n) { if (n < 16) return '0' + n.toString(16); @@ -40,20 +44,6 @@ function toHex(n) { } -SlowBuffer.prototype.inspect = function() { - var out = [], - len = this.length; - for (var i = 0; i < len; i++) { - out[i] = toHex(this[i]); - if (i == exports.INSPECT_MAX_BYTES) { - out[i + 1] = '...'; - break; - } - } - return ''; -}; - - SlowBuffer.prototype.hexSlice = function(start, end) { var len = this.length; @@ -302,24 +292,25 @@ function allocPool() { // Static methods Buffer.isBuffer = function isBuffer(b) { - return b instanceof Buffer || b instanceof SlowBuffer; + return b instanceof Buffer; }; // Inspect Buffer.prototype.inspect = function inspect() { var out = [], - len = this.length; + len = this.length, + name = this.constructor.name; for (var i = 0; i < len; i++) { - out[i] = toHex(this.parent[i + this.offset]); + out[i] = toHex(this[i]); if (i == exports.INSPECT_MAX_BYTES) { out[i + 1] = '...'; break; } } - return ''; + return '<' + name + ' ' + out.join(' ') + '>'; }; @@ -1143,32 +1134,3 @@ Buffer.prototype.writeDoubleLE = function(value, offset, noAssert) { Buffer.prototype.writeDoubleBE = function(value, offset, noAssert) { writeDouble(this, value, offset, true, noAssert); }; - -SlowBuffer.prototype.readUInt8 = Buffer.prototype.readUInt8; -SlowBuffer.prototype.readUInt16LE = Buffer.prototype.readUInt16LE; -SlowBuffer.prototype.readUInt16BE = Buffer.prototype.readUInt16BE; -SlowBuffer.prototype.readUInt32LE = Buffer.prototype.readUInt32LE; -SlowBuffer.prototype.readUInt32BE = Buffer.prototype.readUInt32BE; -SlowBuffer.prototype.readInt8 = Buffer.prototype.readInt8; -SlowBuffer.prototype.readInt16LE = Buffer.prototype.readInt16LE; -SlowBuffer.prototype.readInt16BE = Buffer.prototype.readInt16BE; -SlowBuffer.prototype.readInt32LE = Buffer.prototype.readInt32LE; -SlowBuffer.prototype.readInt32BE = Buffer.prototype.readInt32BE; -SlowBuffer.prototype.readFloatLE = Buffer.prototype.readFloatLE; -SlowBuffer.prototype.readFloatBE = Buffer.prototype.readFloatBE; -SlowBuffer.prototype.readDoubleLE = Buffer.prototype.readDoubleLE; -SlowBuffer.prototype.readDoubleBE = Buffer.prototype.readDoubleBE; -SlowBuffer.prototype.writeUInt8 = Buffer.prototype.writeUInt8; -SlowBuffer.prototype.writeUInt16LE = Buffer.prototype.writeUInt16LE; -SlowBuffer.prototype.writeUInt16BE = Buffer.prototype.writeUInt16BE; -SlowBuffer.prototype.writeUInt32LE = Buffer.prototype.writeUInt32LE; -SlowBuffer.prototype.writeUInt32BE = Buffer.prototype.writeUInt32BE; -SlowBuffer.prototype.writeInt8 = Buffer.prototype.writeInt8; -SlowBuffer.prototype.writeInt16LE = Buffer.prototype.writeInt16LE; -SlowBuffer.prototype.writeInt16BE = Buffer.prototype.writeInt16BE; -SlowBuffer.prototype.writeInt32LE = Buffer.prototype.writeInt32LE; -SlowBuffer.prototype.writeInt32BE = Buffer.prototype.writeInt32BE; -SlowBuffer.prototype.writeFloatLE = Buffer.prototype.writeFloatLE; -SlowBuffer.prototype.writeFloatBE = Buffer.prototype.writeFloatBE; -SlowBuffer.prototype.writeDoubleLE = Buffer.prototype.writeDoubleLE; -SlowBuffer.prototype.writeDoubleBE = Buffer.prototype.writeDoubleBE; diff --git a/lib/child_process.js b/lib/child_process.js index fdea1440825..2e67ebce1c1 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -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) { target._channel = channel; var jsonBuffer = ''; channel.buffering = false; channel.onread = function(pool, offset, length, recvHandle) { - // Update simultaneous accepts on Windows - net._setSimultaneousAccepts(recvHandle); - if (pool) { jsonBuffer += pool.toString('ascii', offset, offset + length); @@ -73,18 +265,7 @@ function setupChannel(target, channel) { var json = jsonBuffer.slice(start, i); var message = JSON.parse(json); - //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, recvHandle); - } - //Non-internal message - else { - target.emit('message', message, recvHandle); - } + handleMessage(target, message, recvHandle); 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') { throw new TypeError('message cannot be undefined'); } @@ -112,12 +313,43 @@ function setupChannel(target, channel) { return false; } + // 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 + if (obj.simultaneousAccepts) { + net._setSimultaneousAccepts(handle); + } + } + var string = JSON.stringify(message) + '\n'; + var writeReq = channel.writeUtf8String(string, handle); - // Update simultaneous accepts on Windows - net._setSimultaneousAccepts(sendHandle); - - var writeReq = channel.writeUtf8String(string, sendHandle); + // Close the Socket handle after sending it + if (message && message.type === 'net.Socket') { + handle.close(); + } if (!writeReq) { var er = errnoException(errno, 'write', 'cannot write to IPC channel.'); @@ -172,10 +404,10 @@ exports.fork = function(modulePath /*, args, options*/) { var options, args, execArgv; if (Array.isArray(arguments[1])) { args = arguments[1]; - options = arguments[2] || {}; + options = util._extend({}, arguments[2]); } else { args = []; - options = arguments[1] || {}; + options = util._extend({}, arguments[1]); } // Prepare arguments for fork: @@ -264,15 +496,12 @@ exports.execFile = function(file /* args, options, callback */) { if (Array.isArray(arguments[1])) { args = arguments[1]; - if (typeof arguments[2] === 'object') optionArg = arguments[2]; + options = util._extend(options, arguments[2]); } else { args = []; - if (typeof arguments[1] === 'object') optionArg = arguments[1]; + options = util._extend(options, arguments[1]); } - // Merge optionArg into options - util._extend(options, optionArg); - var child = spawn(file, args, { cwd: options.cwd, env: options.env, @@ -398,8 +627,10 @@ function ChildProcess() { this.exitCode = null; this.killed = false; - this._internal = new Process(); - this._internal.onexit = function(exitCode, signalCode) { + this._handle = new Process(); + this._handle.owner = this; + + this._handle.onexit = function(exitCode, signalCode) { // // follow 0.4.x behaviour: // @@ -416,8 +647,8 @@ function ChildProcess() { self.stdin.destroy(); } - self._internal.close(); - self._internal = null; + self._handle.close(); + self._handle = null; self.emit('exit', self.exitCode, self.signalCode); @@ -452,7 +683,7 @@ ChildProcess.prototype.spawn = function(options) { setStreamOption('stdoutStream', 1, options); setStreamOption('stderrStream', 2, options); - var r = this._internal.spawn(options); + var r = this._handle.spawn(options); if (r) { if (options.stdinStream) { @@ -467,12 +698,12 @@ ChildProcess.prototype.spawn = function(options) { options.stderrStream.close(); } - this._internal.close(); - this._internal = null; + this._handle.close(); + this._handle = null; throw errnoException(errno, 'spawn'); } - this.pid = this._internal.pid; + this.pid = this._handle.pid; if (options.stdinStream) { this.stdin = createSocket(options.stdinStream, false); @@ -525,9 +756,9 @@ ChildProcess.prototype.kill = function(sig) { throw new Error('Unknown signal: ' + sig); } - if (this._internal) { + if (this._handle) { this.killed = true; - var r = this._internal.kill(signal); + var r = this._handle.kill(signal); if (r === -1) { this.emit('error', errnoException(errno, 'kill')); return; diff --git a/lib/dgram.js b/lib/dgram.js index 743dc9abcd0..7abd0c32ccd 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -91,7 +91,7 @@ function Socket(type, listener) { events.EventEmitter.call(this); var handle = newHandle(type); - handle.socket = this; + handle.owner = this; this._handle = handle; this._receiving = false; @@ -200,7 +200,7 @@ Socket.prototype.send = function(buffer, function afterSend(status, handle, req, buffer) { - var self = handle.socket; + var self = handle.owner; // CHECKME socket's been closed by user, don't call callback? if (handle !== self._handle) @@ -344,7 +344,7 @@ Socket.prototype._stopReceiving = function() { function onMessage(handle, slab, start, len, rinfo) { - var self = handle.socket; + var self = handle.owner; if (!slab) return self.emit('error', errnoException(errno, 'recvmsg')); rinfo.size = len; // compatibility self.emit('message', slab.slice(start, start + len), rinfo); diff --git a/lib/fs.js b/lib/fs.js index f9437196659..ce9a471556c 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -737,6 +737,7 @@ function FSWatcher() { var self = this; var FSEvent = process.binding('fs_event_wrap').FSEvent; this._handle = new FSEvent(); + this._handle.owner = this; this._handle.onchange = function(status, event, filename) { if (status) { diff --git a/lib/http.js b/lib/http.js index 354ca70c26a..232d52afe0b 100644 --- a/lib/http.js +++ b/lib/http.js @@ -475,6 +475,10 @@ OutgoingMessage.prototype._send = function(data, encoding) { OutgoingMessage.prototype._writeRaw = function(data, encoding) { + if (data.length === 0) { + return true; + } + if (this.connection && this.connection._httpMessage === this && this.connection.writable) { diff --git a/lib/net.js b/lib/net.js index fa6258ba435..b03dcf5114a 100644 --- a/lib/net.js +++ b/lib/net.js @@ -120,7 +120,7 @@ function initSocketHandle(self) { // Handle creation may be deferred to bind() or connect() time. if (self._handle) { - self._handle.socket = self; + self._handle.owner = self; self._handle.onread = onread; } } @@ -291,7 +291,7 @@ Socket.prototype.end = function(data, encoding) { function afterShutdown(status, handle, req) { - var self = handle.socket; + var self = handle.owner; assert.ok(self._flags & FLAG_SHUTDOWN); assert.ok(!self.writable); @@ -352,7 +352,7 @@ Socket.prototype._destroy = function(exception, cb) { timers.unenroll(this); if (this.server) { - this.server.connections--; + this.server._connections--; this.server._emitCloseIfDrained(); } @@ -380,7 +380,7 @@ Socket.prototype.destroy = function(exception) { function onread(buffer, offset, length) { var handle = this; - var self = handle.socket; + var self = handle.owner; assert.equal(handle, self._handle); timers.active(self); @@ -583,7 +583,7 @@ Socket.prototype.__defineGetter__('bytesWritten', function() { function afterWrite(status, handle, req) { - var self = handle.socket; + var self = handle.owner; // callback may come after call to destroy. if (self.destroyed) { @@ -722,7 +722,7 @@ Socket.prototype.connect = function(options, cb) { function afterConnect(status, handle, req, readable, writable) { - var self = handle.socket; + var self = handle.owner; // callback may come after call to destroy if (self.destroyed) { @@ -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._handle = null; @@ -865,7 +881,7 @@ Server.prototype._listen2 = function(address, port, addressType, backlog) { } self._handle.onconnection = onconnection; - self._handle.socket = self; + self._handle.owner = self; // Use a backlog of 512 entries. We pass 511 to the listen() call because // the kernel does: backlogsize = roundup_pow_of_two(backlogsize + 1); @@ -881,6 +897,9 @@ Server.prototype._listen2 = function(address, port, addressType, backlog) { return; } + // generate connection key, this should be unique to the connection + this._connectionKey = addressType + ':' + address + ':' + port; + process.nextTick(function() { self.emit('listening'); }); @@ -961,7 +980,7 @@ Server.prototype.address = function() { function onconnection(clientHandle) { var handle = this; - var self = handle.socket; + var self = handle.owner; debug('onconnection'); @@ -970,7 +989,7 @@ function onconnection(clientHandle) { return; } - if (self.maxConnections && self.connections >= self.maxConnections) { + if (self.maxConnections && self._connections >= self.maxConnections) { clientHandle.close(); return; } @@ -983,7 +1002,7 @@ function onconnection(clientHandle) { socket.resume(); - self.connections++; + self._connections++; socket.server = self; DTRACE_NET_SERVER_CONNECTION(socket); @@ -1005,13 +1024,21 @@ Server.prototype.close = function(cb) { this._handle = null; this._emitCloseIfDrained(); + // fetch new socket lists + if (this._usingSlaves) { + this._slaves.forEach(function(socketList) { + if (socketList.list.length === 0) return; + socketList.update(); + }); + } + return this; }; Server.prototype._emitCloseIfDrained = function() { var self = this; - if (self._handle || self.connections) return; + if (self._handle || self._connections) return; process.nextTick(function() { 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'); }; +// 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 // this is what the legacy system did. @@ -1030,7 +1066,7 @@ Server.prototype.listenFD = function(fd, type) { // and it does not detect more than one double : in a string. exports.isIP = function(input) { if (!input) { - return 4; + return 0; } else if (/^(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)$/.test(input)) { var parts = input.split('.'); for (var i = 0; i < parts.length; i++) { diff --git a/lib/querystring.js b/lib/querystring.js index 7aa6e844b94..3c03cf313e1 100644 --- a/lib/querystring.js +++ b/lib/querystring.js @@ -189,6 +189,11 @@ QueryString.parse = QueryString.decode = function(qs, sep, eq, options) { kstr = x.substring(0, idx), vstr = x.substring(idx + 1), k, v; + if (kstr === '' && vstr !== '') { + kstr = vstr; + vstr = ''; + } + try { k = decodeURIComponent(kstr); v = decodeURIComponent(vstr); diff --git a/lib/util.js b/lib/util.js index ce6aff496a4..50595fd3f7a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -509,7 +509,7 @@ exports.inherits = function(ctor, superCtor) { exports._extend = function(origin, add) { // Don't do anything if add isn't an object - if (!add) return origin; + if (!add || typeof add !== 'object') return origin; var keys = Object.keys(add); var i = keys.length; diff --git a/node.gyp b/node.gyp index edcea981434..824b61e49fa 100644 --- a/node.gyp +++ b/node.gyp @@ -107,6 +107,7 @@ 'src/node_script.h', 'src/node_string.h', 'src/node_version.h', + 'src/ngx-queue.h', 'src/pipe_wrap.h', 'src/req_wrap.h', 'src/slab_allocator.h', diff --git a/src/handle_wrap.cc b/src/handle_wrap.cc index d9621b4c4bf..11777a88c3a 100644 --- a/src/handle_wrap.cc +++ b/src/handle_wrap.cc @@ -20,10 +20,12 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node.h" +#include "ngx-queue.h" #include "handle_wrap.h" namespace node { +using v8::Array; using v8::Object; using v8::Handle; using v8::Local; @@ -52,6 +54,10 @@ using v8::Integer; } +// defined in node.cc +extern ngx_queue_t handle_wrap_queue; + + void HandleWrap::Initialize(Handle target) { /* Doesn't do anything at the moment. */ } @@ -125,6 +131,7 @@ HandleWrap::HandleWrap(Handle object, uv_handle_t* h) { assert(object->InternalFieldCount() > 0); object_ = v8::Persistent::New(object); object_->SetPointerInInternalField(0, this); + ngx_queue_insert_tail(&handle_wrap_queue, &handle_wrap_queue_); } @@ -136,6 +143,7 @@ void HandleWrap::SetHandle(uv_handle_t* h) { HandleWrap::~HandleWrap() { assert(object_.IsEmpty()); + ngx_queue_remove(&handle_wrap_queue_); } diff --git a/src/handle_wrap.h b/src/handle_wrap.h index b9cf31e8eb1..c6dd4c9d6a1 100644 --- a/src/handle_wrap.h +++ b/src/handle_wrap.h @@ -22,6 +22,8 @@ #ifndef HANDLE_WRAP_H_ #define HANDLE_WRAP_H_ +#include "ngx-queue.h" + namespace node { // Rules: @@ -61,7 +63,9 @@ class HandleWrap { v8::Persistent object_; private: + friend v8::Handle GetActiveHandles(const v8::Arguments&); static void OnClose(uv_handle_t* handle); + ngx_queue_t handle_wrap_queue_; // Using double underscore due to handle_ member in tcp_wrap. Probably // tcp_wrap should rename it's member to 'handle'. uv_handle_t* handle__; diff --git a/src/ngx-queue.h b/src/ngx-queue.h new file mode 100644 index 00000000000..7058ce408d8 --- /dev/null +++ b/src/ngx-queue.h @@ -0,0 +1,106 @@ + +/* + * Copyright (C) Igor Sysoev + */ + + +#ifndef NGX_QUEUE_H_INCLUDED_ +#define NGX_QUEUE_H_INCLUDED_ + + +typedef struct ngx_queue_s ngx_queue_t; + +struct ngx_queue_s { + ngx_queue_t *prev; + ngx_queue_t *next; +}; + + +#define ngx_queue_init(q) \ + (q)->prev = q; \ + (q)->next = q + + +#define ngx_queue_empty(h) \ + (h == (h)->prev) + + +#define ngx_queue_insert_head(h, x) \ + (x)->next = (h)->next; \ + (x)->next->prev = x; \ + (x)->prev = h; \ + (h)->next = x + + +#define ngx_queue_insert_after ngx_queue_insert_head + + +#define ngx_queue_insert_tail(h, x) \ + (x)->prev = (h)->prev; \ + (x)->prev->next = x; \ + (x)->next = h; \ + (h)->prev = x + + +#define ngx_queue_head(h) \ + (h)->next + + +#define ngx_queue_last(h) \ + (h)->prev + + +#define ngx_queue_sentinel(h) \ + (h) + + +#define ngx_queue_next(q) \ + (q)->next + + +#define ngx_queue_prev(q) \ + (q)->prev + + +#if (NGX_DEBUG) + +#define ngx_queue_remove(x) \ + (x)->next->prev = (x)->prev; \ + (x)->prev->next = (x)->next; \ + (x)->prev = NULL; \ + (x)->next = NULL + +#else + +#define ngx_queue_remove(x) \ + (x)->next->prev = (x)->prev; \ + (x)->prev->next = (x)->next + +#endif + + +#define ngx_queue_split(h, q, n) \ + (n)->prev = (h)->prev; \ + (n)->prev->next = n; \ + (n)->next = q; \ + (h)->prev = (q)->prev; \ + (h)->prev->next = h; \ + (q)->prev = n; + + +#define ngx_queue_add(h, n) \ + (h)->prev->next = (n)->next; \ + (n)->next->prev = (h)->prev; \ + (h)->prev = (n)->prev; \ + (h)->prev->next = h; + + +#define ngx_queue_data(q, type, link) \ + (type *) ((unsigned char *) q - offsetof(type, link)) + + +#define ngx_queue_foreach(q, h) \ + for ((q) = ngx_queue_head(h); (q) != (h); (q) = ngx_queue_next(q)) + + +#endif /* NGX_QUEUE_H_INCLUDED_ */ diff --git a/src/node.cc b/src/node.cc index 556fd0d8e3a..c5700c2cc4d 100644 --- a/src/node.cc +++ b/src/node.cc @@ -20,6 +20,8 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node.h" +#include "req_wrap.h" +#include "handle_wrap.h" #include "uv.h" @@ -90,6 +92,13 @@ extern char **environ; namespace node { +ngx_queue_t handle_wrap_queue = { &handle_wrap_queue, &handle_wrap_queue }; +ngx_queue_t req_wrap_queue = { &req_wrap_queue, &req_wrap_queue }; + +// declared in req_wrap.h +Persistent process_symbol; +Persistent domain_symbol; + static Persistent process; static Persistent errno_symbol; @@ -105,7 +114,6 @@ static Persistent listeners_symbol; static Persistent uncaught_exception_symbol; static Persistent emit_symbol; -static Persistent domain_symbol; static Persistent enter_symbol; static Persistent exit_symbol; static Persistent disposed_symbol; @@ -1019,8 +1027,7 @@ MakeCallback(const Handle object, TryCatch try_catch; - if (domain_symbol.IsEmpty()) { - domain_symbol = NODE_PSYMBOL("domain"); + if (enter_symbol.IsEmpty()) { enter_symbol = NODE_PSYMBOL("enter"); exit_symbol = NODE_PSYMBOL("exit"); disposed_symbol = NODE_PSYMBOL("_disposed"); @@ -1330,6 +1337,46 @@ Local ExecuteString(Handle source, Handle filename) { } +static Handle GetActiveRequests(const Arguments& args) { + HandleScope scope; + + Local ary = Array::New(); + ngx_queue_t* q = NULL; + int i = 0; + + ngx_queue_foreach(q, &req_wrap_queue) { + ReqWrap* w = container_of(q, ReqWrap, req_wrap_queue_); + if (w->object_.IsEmpty()) continue; + ary->Set(i++, w->object_); + } + + return scope.Close(ary); +} + + +// Non-static, friend of HandleWrap. Could have been a HandleWrap method but +// implemented here for consistency with GetActiveRequests(). +Handle GetActiveHandles(const Arguments& args) { + HandleScope scope; + + Local ary = Array::New(); + ngx_queue_t* q = NULL; + int i = 0; + + Local owner_sym = String::New("owner"); + + ngx_queue_foreach(q, &handle_wrap_queue) { + HandleWrap* w = container_of(q, HandleWrap, handle_wrap_queue_); + if (w->object_.IsEmpty() || w->unref) continue; + Local obj = w->object_->Get(owner_sym); + if (obj->IsUndefined()) obj = *w->object_; + ary->Set(i++, obj); + } + + return scope.Close(ary); +} + + static Handle Abort(const Arguments& args) { abort(); return Undefined(); @@ -2239,6 +2286,8 @@ Handle SetupProcessObject(int argc, char *argv[]) { // define various internal methods + NODE_SET_METHOD(process, "_getActiveRequests", GetActiveRequests); + NODE_SET_METHOD(process, "_getActiveHandles", GetActiveHandles); NODE_SET_METHOD(process, "_needTickCallback", NeedTickCallback); NODE_SET_METHOD(process, "reallyExit", Exit); NODE_SET_METHOD(process, "abort", Abort); @@ -2869,6 +2918,9 @@ int Start(int argc, char *argv[]) { Persistent context = Context::New(); Context::Scope context_scope(context); + process_symbol = NODE_PSYMBOL("process"); + domain_symbol = NODE_PSYMBOL("domain"); + // Use original argv, as we're just copying values out of it. Handle process_l = SetupProcessObject(argc, argv); v8_typed_array::AttachBindings(context->Global()); diff --git a/src/req_wrap.h b/src/req_wrap.h index c478ce0cdb7..ba56821bbef 100644 --- a/src/req_wrap.h +++ b/src/req_wrap.h @@ -22,10 +22,14 @@ #ifndef REQ_WRAP_H_ #define REQ_WRAP_H_ +#include "ngx-queue.h" + namespace node { -static v8::Persistent process_symbol; -static v8::Persistent domain_symbol; +// defined in node.cc +extern v8::Persistent process_symbol; +extern v8::Persistent domain_symbol; +extern ngx_queue_t req_wrap_queue; template class ReqWrap { @@ -34,12 +38,6 @@ class ReqWrap { v8::HandleScope scope; object_ = v8::Persistent::New(v8::Object::New()); - // TODO: grab a handle to the current process.domain - if (process_symbol.IsEmpty()) { - process_symbol = NODE_PSYMBOL("process"); - domain_symbol = NODE_PSYMBOL("domain"); - } - v8::Local domain = v8::Context::GetCurrent() ->Global() ->Get(process_symbol) @@ -50,10 +48,13 @@ class ReqWrap { // fprintf(stderr, "setting domain on ReqWrap\n"); object_->Set(domain_symbol, domain); } + + ngx_queue_insert_tail(&req_wrap_queue, &req_wrap_queue_); } ~ReqWrap() { + ngx_queue_remove(&req_wrap_queue_); // Assert that someone has called Dispatched() assert(req_.data == this); assert(!object_.IsEmpty()); @@ -67,8 +68,9 @@ class ReqWrap { } v8::Persistent object_; - T req_; + ngx_queue_t req_wrap_queue_; void* data_; + T req_; // *must* be last, GetActiveRequests() in node.cc depends on it }; diff --git a/test/pummel/test-net-connect-econnrefused.js b/test/pummel/test-net-connect-econnrefused.js new file mode 100644 index 00000000000..a6fed70dbe0 --- /dev/null +++ b/test/pummel/test-net-connect-econnrefused.js @@ -0,0 +1,58 @@ +// 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. + +// verify that connect reqs are properly cleaned up + +var common = require('../common'); +var assert = require('assert'); +var net = require('net'); + +var ROUNDS = 1024; +var rounds = 0; +var reqs = 0; + +pummel(); + +function pummel() { + net.createConnection(common.PORT).on('error', function(err) { + assert.equal(err.code, 'ECONNREFUSED'); + if (++rounds < ROUNDS) return pummel(); + check(); + }); + reqs++; +} + +function check() { + process.nextTick(function() { + process.nextTick(function() { + assert.equal(process._getActiveRequests().length, 0); + assert.equal(process._getActiveHandles().length, 0); + check_called = true; + }); + }); +} +var check_called = false; + +process.on('exit', function() { + assert.equal(rounds, ROUNDS); + assert.equal(reqs, ROUNDS); + assert(check_called); +}); diff --git a/test/simple/test-child-process-fork-net.js b/test/simple/test-child-process-fork-net.js new file mode 100644 index 00000000000..6dd0e5fde38 --- /dev/null +++ b/test/simple/test-child-process-fork-net.js @@ -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); + }); + +} diff --git a/test/simple/test-child-process-fork-net2.js b/test/simple/test-child-process-fork-net2.js new file mode 100644 index 00000000000..48713566a62 --- /dev/null +++ b/test/simple/test-child-process-fork-net2.js @@ -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'); + }); +} diff --git a/test/simple/test-child-process-kill-throw.js b/test/simple/test-child-process-kill-throw.js index 3f46799b02c..f85bf515497 100644 --- a/test/simple/test-child-process-kill-throw.js +++ b/test/simple/test-child-process-kill-throw.js @@ -30,7 +30,7 @@ if (process.argv[2] === 'child') { var error = {}; child.on('exit', function() { - child._internal = { + child._handle = { kill: function() { global.errno = 42; return -1; diff --git a/test/simple/test-dgram-ref.js b/test/simple/test-dgram-ref.js new file mode 100644 index 00000000000..ebbbac2fc64 --- /dev/null +++ b/test/simple/test-dgram-ref.js @@ -0,0 +1,27 @@ +// 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 dgram = require('dgram'); + +// should not hang, see #1282 +dgram.createSocket('udp4'); +dgram.createSocket('udp6'); diff --git a/test/simple/test-http-client-pipe-end.js b/test/simple/test-http-client-pipe-end.js new file mode 100644 index 00000000000..7cb592e4c59 --- /dev/null +++ b/test/simple/test-http-client-pipe-end.js @@ -0,0 +1,58 @@ +// 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. + +// see https://github.com/joyent/node/issues/3257 + +var common = require('../common'); +var assert = require('assert'); +var http = require('http'); + +var server = http.createServer(function(req, res) { + req.once('end', function() { + res.writeHead(200); + res.end(); + server.close(); + }); +}); + +server.listen(common.PIPE, function() { + var req = http.request({ + socketPath: common.PIPE, + headers: {'Content-Length':'1'}, + method: 'POST', + path: '/' + }); + + req.write('.'); + + sched(function() { req.end() }, 5); +}); + +// schedule a callback after `ticks` event loop ticks +function sched(cb, ticks) { + function fn() { + if (--ticks) + process.nextTick(fn); + else + cb(); + } + process.nextTick(fn); +} diff --git a/test/simple/test-net-isip.js b/test/simple/test-net-isip.js index 75da204c039..4f60f502135 100644 --- a/test/simple/test-net-isip.js +++ b/test/simple/test-net-isip.js @@ -36,12 +36,13 @@ assert.equal(net.isIP('::1'), 6); assert.equal(net.isIP('::'), 6); assert.equal(net.isIP('0000:0000:0000:0000:0000:0000:12345:0000'), 0); assert.equal(net.isIP('0'), 0); +assert.equal(net.isIP(), 0); +assert.equal(net.isIP(""), 0); assert.equal(net.isIPv4('127.0.0.1'), true); assert.equal(net.isIPv4('example.com'), false); assert.equal(net.isIPv4('2001:252:0:1::2008:6'), false); assert.equal(net.isIPv6('127.0.0.1'), false); -assert.equal(net.isIPv4('example.com'), false); -assert.equal(net.isIPv6('2001:252:0:1::2008:6'), true); - +assert.equal(net.isIPv6('example.com'), false); +assert.equal(net.isIPv6('2001:252:0:1::2008:6'), true); \ No newline at end of file diff --git a/test/simple/test-process-active-wraps.js b/test/simple/test-process-active-wraps.js new file mode 100644 index 00000000000..254e0bf63a9 --- /dev/null +++ b/test/simple/test-process-active-wraps.js @@ -0,0 +1,54 @@ +// 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 spawn = require('child_process').spawn; +var net = require('net'); + +function expect(activeHandles, activeRequests) { + assert.equal(process._getActiveHandles().length, activeHandles); + assert.equal(process._getActiveRequests().length, activeRequests); +} + +(function() { + expect(0, 0); + var server = net.createServer().listen(common.PORT); + expect(1, 0); + server.close(); + expect(1, 0); // server handle doesn't shut down until next tick +})(); + +(function() { + expect(1, 0); + var conn = net.createConnection(common.PORT); + conn.on('error', function() { /* ignore */ }); + expect(2, 1); + conn.destroy(); + expect(2, 1); // client handle doesn't shut down until next tick +})(); + +process.nextTick(function() { + process.nextTick(function() { + // the handles should be gone but the connect req could still be alive + assert.equal(process._getActiveHandles().length, 0); + }); +}); diff --git a/test/simple/test-querystring.js b/test/simple/test-querystring.js index 0764481cb82..2d86625f32b 100644 --- a/test/simple/test-querystring.js +++ b/test/simple/test-querystring.js @@ -55,7 +55,9 @@ var qsTestCases = [ { hasOwnProperty: 'x', toString: 'foo', valueOf: 'bar', - __defineGetter__: 'baz' }] + __defineGetter__: 'baz' }], + // See: https://github.com/joyent/node/issues/3058 + ['foo&bar=baz', 'foo=&bar=baz', { foo: '', bar: 'baz' }] ]; // [ wonkyQS, canonicalQS, obj ] diff --git a/test/simple/test-util.js b/test/simple/test-util.js index 87ee77509a9..7c30f5e83e5 100644 --- a/test/simple/test-util.js +++ b/test/simple/test-util.js @@ -69,3 +69,12 @@ assert.equal(false, util.isError({})); assert.equal(false, util.isError({ name: 'Error', message: '' })); assert.equal(false, util.isError([])); assert.equal(false, util.isError(Object.create(Error.prototype))); + +// _extend +assert.deepEqual(util._extend({a:1}), {a:1}); +assert.deepEqual(util._extend({a:1}, []), {a:1}); +assert.deepEqual(util._extend({a:1}, null), {a:1}); +assert.deepEqual(util._extend({a:1}, true), {a:1}); +assert.deepEqual(util._extend({a:1}, false), {a:1}); +assert.deepEqual(util._extend({a:1}, {b:2}), {a:1, b:2}); +assert.deepEqual(util._extend({a:1, b:2}, {b:3}), {a:1, b:3});