From 928d28a7b326fb8f47ded5257ede7634fe44578b Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 10 May 2012 16:49:35 +0200 Subject: [PATCH 01/19] util: make _extend() more robust Add a better 'is object?' check, the old one let values like true slip through. --- lib/util.js | 2 +- test/simple/test-util.js | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/util.js b/lib/util.js index ce6aff496a..50595fd3f7 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/test/simple/test-util.js b/test/simple/test-util.js index 87ee77509a..7c30f5e83e 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}); From 68f63fe9ec2b8a7308cefa4a1ae1261cd6d1675f Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Thu, 10 May 2012 16:54:17 +0200 Subject: [PATCH 02/19] child_process: make copy of options arg Make a copy of the options object that the user passes in, we modify it. --- lib/child_process.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/child_process.js b/lib/child_process.js index fdea144082..63a0856bf3 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -172,10 +172,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 +264,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, From 12fc9fa8a7b30b701103e11ef4e76a444a4e9e19 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 11 May 2012 04:39:20 +0200 Subject: [PATCH 03/19] test: add failing dgram refcount test Idle UDP sockets (reading nor writing) should not keep the event loop alive. This will get fixed in v0.8 one way or the other. --- test/simple/test-dgram-ref.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/simple/test-dgram-ref.js diff --git a/test/simple/test-dgram-ref.js b/test/simple/test-dgram-ref.js new file mode 100644 index 0000000000..ebbbac2fc6 --- /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'); From a811a4a13042a4b3ba019fbe1221f4c83b02a699 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 11 May 2012 08:49:03 -0700 Subject: [PATCH 04/19] Fix #3058 querystring: Fix incorrect handling of empty keys --- lib/querystring.js | 5 +++++ test/simple/test-querystring.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/querystring.js b/lib/querystring.js index 7aa6e844b9..3c03cf313e 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/test/simple/test-querystring.js b/test/simple/test-querystring.js index 0764481cb8..2d86625f32 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 ] From 38542f76a97e5c424aeada2e2dc8371797449335 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Sun, 6 May 2012 12:47:25 -0700 Subject: [PATCH 05/19] buffer: make SlowBuffer inherit from Buffer This frees us from manually having to copy over functions to SlowBuffer's prototype (which has bitten us multiple times in the past). As an added bonus, the `inspect()` function is now shared between Buffer and SlowBuffer, removing some duplicate code. Closes #3228. --- lib/buffer.js | 56 +++++++++------------------------------------------ 1 file changed, 9 insertions(+), 47 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index de1efcb5ed..70fefedad8 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; From d40415912f0807201a45d82a86a7f6e316bad5d7 Mon Sep 17 00:00:00 2001 From: rsolomo Date: Sun, 13 May 2012 19:17:51 -0700 Subject: [PATCH 06/19] net: make isIP() return 0 on empty input --- lib/net.js | 2 +- test/simple/test-net-isip.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/net.js b/lib/net.js index fa6258ba43..9705f557a7 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1030,7 +1030,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/test/simple/test-net-isip.js b/test/simple/test-net-isip.js index 75da204c03..4f60f50213 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 From 49f16c4575b122a55d1842518d2395c928a78a41 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Thu, 12 Apr 2012 09:18:12 +0200 Subject: [PATCH 07/19] doc: move child.send details from child_process.fork to child.send --- doc/api/child_process.markdown | 117 +++++++++++++++------------------ 1 file changed, 54 insertions(+), 63 deletions(-) diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown index c8a1c64c6c..ade33bc638 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 handle object + +Messages send by `.send(message, [sendHandle])` are obtained using the +`message` event. + ### child.stdin * {Stream object} @@ -116,15 +124,56 @@ 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 useing `child_process.fork()` an you can write to the child using +`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' }); -See `child_process.fork()` for details. +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 handle object to +another process. The child will receive the object as its second argument to +the `message` event. + +### 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 +382,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. Se +`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 +392,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. From dceebbfa31a2b82bb534ba120ef9354a7d348f65 Mon Sep 17 00:00:00 2001 From: Andreas Madsen Date: Thu, 12 Apr 2012 09:23:07 +0200 Subject: [PATCH 08/19] 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. --- doc/api/child_process.markdown | 76 +++++- doc/api/net.markdown | 4 + lib/child_process.js | 272 ++++++++++++++++++-- lib/net.js | 46 +++- test/simple/test-child-process-fork-net.js | 198 ++++++++++++++ test/simple/test-child-process-fork-net2.js | 131 ++++++++++ 6 files changed, 696 insertions(+), 31 deletions(-) create mode 100644 test/simple/test-child-process-fork-net.js create mode 100644 test/simple/test-child-process-fork-net2.js diff --git a/doc/api/child_process.markdown b/doc/api/child_process.markdown index ade33bc638..18be2f66c2 100644 --- a/doc/api/child_process.markdown +++ b/doc/api/child_process.markdown @@ -55,7 +55,7 @@ An alternative way to check if you can send messages is to see if the ### Event: 'message' * `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 `message` event. @@ -129,7 +129,7 @@ See `kill(2)` * `message` {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 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 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 -another process. The child will receive the object as its second argument to -the `message` event. +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() @@ -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 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. By default the spawned Node process will have the stdout, stderr associated diff --git a/doc/api/net.markdown b/doc/api/net.markdown index 9ef058cb80..7fb07df955 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/child_process.js b/lib/child_process.js index 63a0856bf3..3793701242 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; } - 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; + } - // Update simultaneous accepts on Windows - net._setSimultaneousAccepts(sendHandle); + var obj = handleConversion[message.type]; + + // convert TCP object to native handle object + handle = handleConversion[message.type].send.apply(target, arguments); - var writeReq = channel.writeUtf8String(string, sendHandle); + // Update simultaneous accepts on Windows + if (obj.simultaneousAccepts) { + net._setSimultaneousAccepts(handle); + } + } + + 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) { var er = errnoException(errno, 'write', 'cannot write to IPC channel.'); diff --git a/lib/net.js b/lib/net.js index 9705f557a7..49faf59db6 100644 --- a/lib/net.js +++ b/lib/net.js @@ -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(); } @@ -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; @@ -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'); }); @@ -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. 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 0000000000..6dd0e5fde3 --- /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 0000000000..48713566a6 --- /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'); + }); +} From d2eaabd0dff9fa8d7d11dcba0f13567e1f862ef3 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 27 Apr 2012 04:37:27 +0200 Subject: [PATCH 09/19] dgram: hook up handle wrap to owning object --- lib/dgram.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dgram.js b/lib/dgram.js index 743dc9abcd..7abd0c32cc 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); From 25aea2a072ab75ac766c00aa9ddf3c022aa95d40 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 27 Apr 2012 04:39:05 +0200 Subject: [PATCH 10/19] fs: hook up handle wrap to owning object --- lib/fs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/fs.js b/lib/fs.js index a3e68cb06b..0530344e0b 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) { From f66f793c07ed263938748593bec0eec204291e84 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 27 Apr 2012 04:42:10 +0200 Subject: [PATCH 11/19] net: hook up handle wrap to owning object --- lib/net.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/net.js b/lib/net.js index 49faf59db6..b03dcf5114 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); @@ -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) { @@ -881,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); @@ -980,7 +980,7 @@ Server.prototype.address = function() { function onconnection(clientHandle) { var handle = this; - var self = handle.socket; + var self = handle.owner; debug('onconnection'); From 4ec77e2e2861cdd43c2837d214d1478ebe5a0783 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 27 Apr 2012 16:52:06 +0200 Subject: [PATCH 12/19] child_process: rename field _internal to _handle Consistent with how other classes that are built around HandleWraps call it. --- lib/child_process.js | 20 ++++++++++---------- test/simple/test-child-process-kill-throw.js | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/child_process.js b/lib/child_process.js index 3793701242..67b9b5b9f5 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -627,8 +627,8 @@ function ChildProcess() { this.exitCode = null; this.killed = false; - this._internal = new Process(); - this._internal.onexit = function(exitCode, signalCode) { + this._handle = new Process(); + this._handle.onexit = function(exitCode, signalCode) { // // follow 0.4.x behaviour: // @@ -645,8 +645,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); @@ -681,7 +681,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) { @@ -696,12 +696,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); @@ -754,9 +754,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/test/simple/test-child-process-kill-throw.js b/test/simple/test-child-process-kill-throw.js index 3f46799b02..f85bf51549 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; From a3908f47f1524ca82ddb0ea08dd7d8a5ab4c10e2 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 27 Apr 2012 16:54:28 +0200 Subject: [PATCH 13/19] child_process: hook up handle wrap to owning object --- lib/child_process.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/child_process.js b/lib/child_process.js index 67b9b5b9f5..2e67ebce1c 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -628,6 +628,8 @@ function ChildProcess() { this.killed = false; this._handle = new Process(); + this._handle.owner = this; + this._handle.onexit = function(exitCode, signalCode) { // // follow 0.4.x behaviour: From 636add246ca78be5c374cfd951c76de7f1010fb9 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 27 Apr 2012 18:58:30 +0200 Subject: [PATCH 14/19] req_wrap: share process_symbol, domain_symbol Share persistent strings process_symbol and domain_symbol across compilation units. Avoids redefinition errors when src/node.cc includes src/req_wrap.h. --- src/node.cc | 11 ++++++++--- src/req_wrap.h | 11 +++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/node.cc b/src/node.cc index 5afeb55faf..bca0ea28eb 100644 --- a/src/node.cc +++ b/src/node.cc @@ -20,6 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node.h" +#include "handle_wrap.h" // HandleWrap::GetActiveHandles() #include "uv.h" @@ -90,6 +91,9 @@ extern char **environ; namespace node { +// declared in req_wrap.h +Persistent process_symbol; +Persistent domain_symbol; static Persistent process; @@ -106,7 +110,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 +1022,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"); @@ -2866,6 +2868,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 c478ce0cdb..11c7d12044 100644 --- a/src/req_wrap.h +++ b/src/req_wrap.h @@ -24,8 +24,9 @@ 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; template class ReqWrap { @@ -34,12 +35,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) From 5f0406534ca4a465d11892a747a38c0e5c884cf2 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Sat, 28 Apr 2012 18:45:10 +0200 Subject: [PATCH 15/19] process: add _getActiveHandles(), _getActiveRequests() * process._getActiveHandles() returns a list containing all active handles (timers, sockets, etc.) that have not been unref'd. * process._getActiveRequests() returns a list of active requests (in-flight actions like connecting to a remote host, writing data to a socket, etc.). --- LICENSE | 28 ++++++ node.gyp | 1 + src/handle_wrap.cc | 8 ++ src/handle_wrap.h | 4 + src/ngx-queue.h | 106 +++++++++++++++++++++++ src/node.cc | 44 +++++++++- src/req_wrap.h | 9 +- test/simple/test-process-active-wraps.js | 54 ++++++++++++ 8 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 src/ngx-queue.h create mode 100644 test/simple/test-process-active-wraps.js diff --git a/LICENSE b/LICENSE index a6bc8813ea..f464affffc 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/node.gyp b/node.gyp index edcea98143..824b61e49f 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 d9621b4c4b..11777a88c3 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 b9cf31e8eb..c6dd4c9d6a 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 0000000000..7058ce408d --- /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 bca0ea28eb..41dab06e52 100644 --- a/src/node.cc +++ b/src/node.cc @@ -20,7 +20,8 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node.h" -#include "handle_wrap.h" // HandleWrap::GetActiveHandles() +#include "req_wrap.h" +#include "handle_wrap.h" #include "uv.h" @@ -91,6 +92,9 @@ 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; @@ -1332,6 +1336,42 @@ 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; + + ngx_queue_foreach(q, &handle_wrap_queue) { + HandleWrap* w = container_of(q, HandleWrap, handle_wrap_queue_); + if (w->object_.IsEmpty() || w->unref) continue; + ary->Set(i++, w->object_); + } + + return scope.Close(ary); +} + + static Handle Abort(const Arguments& args) { abort(); return Undefined(); @@ -2237,6 +2277,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); diff --git a/src/req_wrap.h b/src/req_wrap.h index 11c7d12044..ba56821bbe 100644 --- a/src/req_wrap.h +++ b/src/req_wrap.h @@ -22,11 +22,14 @@ #ifndef REQ_WRAP_H_ #define REQ_WRAP_H_ +#include "ngx-queue.h" + namespace node { // defined in node.cc extern v8::Persistent process_symbol; extern v8::Persistent domain_symbol; +extern ngx_queue_t req_wrap_queue; template class ReqWrap { @@ -45,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()); @@ -62,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/simple/test-process-active-wraps.js b/test/simple/test-process-active-wraps.js new file mode 100644 index 0000000000..254e0bf63a --- /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); + }); +}); From 88d7a101283dd4717ae6ccd1d757fdfe3f6d0089 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Mon, 30 Apr 2012 00:58:15 +0200 Subject: [PATCH 16/19] test: verify that connect reqs are cleaned up --- test/pummel/test-net-connect-econnrefused.js | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 test/pummel/test-net-connect-econnrefused.js diff --git a/test/pummel/test-net-connect-econnrefused.js b/test/pummel/test-net-connect-econnrefused.js new file mode 100644 index 0000000000..a6fed70dbe --- /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); +}); From e813e3491e3b9d828ae678391b7c926c4ad1f180 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 15 May 2012 17:24:06 +0200 Subject: [PATCH 17/19] node: make _getActiveHandles() return user objects Before this commit, process._getActiveHandles() returned a list of internal handles. Now, it returns the user objects that handles are attached to. For example, a tcp_wrap handle will now return its parent net.Socket object. It works for all handle types except timers because timer handles are shared across multiple user objects. --- src/node.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/node.cc b/src/node.cc index 41dab06e52..c17eb47098 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1362,10 +1362,14 @@ Handle GetActiveHandles(const Arguments& args) { 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; - ary->Set(i++, w->object_); + Local obj = w->object_->Get(owner_sym); + if (obj->IsUndefined()) obj = *w->object_; + ary->Set(i++, obj); } return scope.Close(ary); From 9ae6d8fee345bdbc6f94e38eee57b60d231288eb Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Tue, 15 May 2012 22:05:33 +0200 Subject: [PATCH 18/19] http: fix client request.end() EPIPE race request.end() would sometimes try to write a zero-length buffer to the socket. Don't do that, it triggers an unnecessary EPIPE when the other end has closed the connection. Fixes #3257. --- lib/http.js | 4 ++ test/simple/test-http-client-pipe-end.js | 58 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 test/simple/test-http-client-pipe-end.js diff --git a/lib/http.js b/lib/http.js index 2019514645..2598a477f4 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/test/simple/test-http-client-pipe-end.js b/test/simple/test-http-client-pipe-end.js new file mode 100644 index 0000000000..7cb592e4c5 --- /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); +} From c9676c9147e088171e60b1977ac239ded4f327df Mon Sep 17 00:00:00 2001 From: Sadique Ali Date: Tue, 1 May 2012 16:03:36 +0530 Subject: [PATCH 19/19] build: improve c compiler detection --- configure | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/configure b/configure index 217d922333..fe3b5308c2 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