diff --git a/doc/api/net.markdown b/doc/api/net.markdown index c44b06d8d379..3f42f55583d2 100644 --- a/doc/api/net.markdown +++ b/doc/api/net.markdown @@ -174,9 +174,9 @@ already been bound to a port or domain socket. Listening on a file descriptor is not supported on Windows. This function is asynchronous. When the server has been bound, -['listening'](#event_listening_) event will be emitted. +['listening'][] event will be emitted. the last parameter `callback` will be added as an listener for the -['listening'](#event_listening_) event. +['listening'][] event. ### server.close([callback]) diff --git a/doc/api/process.markdown b/doc/api/process.markdown index ddb9c5233643..8ce01dde09a2 100644 --- a/doc/api/process.markdown +++ b/doc/api/process.markdown @@ -647,4 +647,218 @@ a diff reading, useful for benchmarks and measuring intervals: // benchmark took 1000000527 nanoseconds }, 1000); + +## Async Listeners + + + + Stability: 1 - Experimental + +The `AsyncListener` API is the JavaScript interface for the `AsyncWrap` +class which allows developers to be notified about key events in the +lifetime of an asynchronous event. Node performs a lot of asynchronous +events internally, and significant use of this API will have a **dramatic +performance impact** on your application. + + +## process.createAsyncListener(asyncListener[, callbacksObj[, storageValue]]) + +* `asyncListener` {Function} callback fired when an asynchronous event is +instantiated. +* `callbacksObj` {Object} optional callbacks that will fire at specific +times in the lifetime of the asynchronous event. +* `storageValue` {Value} a value that will be passed as the first argument +when the `asyncListener` callback is run, and to all subsequent callback. + +Returns a constructed `AsyncListener` object. + +To begin capturing asynchronous events pass the object to +[`process.addAsyncListener()`][]. The same `AsyncListener` instance can +only be added once to the active queue, and subsequent attempts to add the +instance will be ignored. + +To stop capturing pass the object to [`process.removeAsyncListener()`][]. +This does _not_ mean the `AsyncListener` previously added will stop +triggering callbacks. Once attached to an asynchronous event it will +persist with the lifetime of the asynchronous call stack. + +Explanation of function parameters: + +`asyncListener(storageValue)`: A `Function` called when an asynchronous +event is instantiated. If a `Value` is returned then it will be attached +to the event and overwrite any value that had been passed to +`process.createAsyncListener()`'s `storageValue` argument. If an initial +`storageValue` was passed when created, then `asyncListener()` will +receive that as a function argument. + +`callbacksObj`: An `Object` which may contain three optional fields: + +* `before(context, storageValue)`: A `Function` that is called immediately +before the asynchronous callback is about to run. It will be passed both +the `context` (i.e. `this`) of the calling function and the `storageValue` +either returned from `asyncListener` or passed during construction (if +either occurred). + +* `after(context, storageValue)`: A `Function` called immediately after +the asynchronous event's callback has run. Note this will not be called +if the callback throws and the error is not handled. + +* `error(storageValue, error)`: A `Function` called if the event's +callback threw. If `error` returns `true` then Node will assume the error +has been properly handled and resume execution normally. When multiple +`error()` callbacks have been registered, only **one** of those callbacks +needs to return `true` for `AsyncListener` to accept that the error has +been handled. + +`storageValue`: A `Value` (i.e. anything) that will be, by default, +attached to all new event instances. This will be overwritten if a `Value` +is returned by `asyncListener()`. + +Here is an example of overwriting the `storageValue`: + + process.createAsyncListener(function listener(value) { + // value === true + return false; + }, { + before: function before(context, value) { + // value === false + } + }, true); + +**Note:** The [EventEmitter][], while used to emit status of an asynchronous +event, is not itself asynchronous. So `asyncListener()` will not fire when +an event is added, and `before`/`after` will not fire when emitted +callbacks are called. + + +## process.addAsyncListener(asyncListener[, callbacksObj[, storageValue]]) +## process.addAsyncListener(asyncListener) + +Returns a constructed `AsyncListener` object and immediately adds it to +the listening queue to begin capturing asynchronous events. + +Function parameters can either be the same as +[`process.createAsyncListener()`][], or a constructed `AsyncListener` +object. + +Example usage for capturing errors: + + var cntr = 0; + var key = process.addAsyncListener(function() { + return { uid: cntr++ }; + }, { + before: function onBefore(context, storage) { + // Need to remove the listener while logging or will end up + // with an infinite call loop. + process.removeAsyncListener(key); + console.log('uid: %s is about to run', storage.uid); + process.addAsyncListener(key); + }, + after: function onAfter(context, storage) { + process.removeAsyncListener(key); + console.log('uid: %s is about to run', storage.uid); + process.addAsyncListener(key); + }, + error: function onError(storage, err) { + // Handle known errors + if (err.message === 'really, it\'s ok') { + process.removeAsyncListener(key); + console.log('handled error just threw:'); + console.log(err.stack); + process.addAsyncListener(key); + return true; + } + } + }); + + process.nextTick(function() { + throw new Error('really, it\'s ok'); + }); + + // Output: + // uid: 0 is about to run + // handled error just threw: + // Error: really, it's ok + // at /tmp/test2.js:27:9 + // at process._tickCallback (node.js:583:11) + // at Function.Module.runMain (module.js:492:11) + // at startup (node.js:123:16) + // at node.js:1012:3 + +## process.removeAsyncListener(asyncListener) + +Removes the `AsyncListener` from the listening queue. + +Removing the `AsyncListener` from the queue does _not_ mean asynchronous +events called during its execution scope will stop firing callbacks. Once +attached to an event it will persist for the entire asynchronous call +stack. For example: + + var key = process.createAsyncListener(function asyncListener() { + // To log we must stop listening or we'll enter infinite recursion. + process.removeAsyncListener(key); + console.log('You summoned me?'); + process.addAsyncListener(key); + }); + + // We want to begin capturing async events some time in the future. + setTimeout(function() { + process.addAsyncListener(key); + + // Perform a few additional async events. + setTimeout(function() { + setImmediate(function() { + process.nextTick(function() { }); + }); + }); + + // Removing the listener doesn't mean to stop capturing events that + // have already been added. + process.removeAsyncListener(key); + }, 100); + + // Output: + // You summoned me? + // You summoned me? + // You summoned me? + // You summoned me? + +The fact that we logged 4 asynchronous events is an implementation detail +of Node's [Timers][]. + +To stop capturing from a specific asynchronous event stack +`process.removeAsyncListener()` must be called from within the call +stack itself. For example: + + var key = process.createAsyncListener(function asyncListener() { + // To log we must stop listening or we'll enter infinite recursion. + process.removeAsyncListener(key); + console.log('You summoned me?'); + process.addAsyncListener(key); + }); + + // We want to begin capturing async events some time in the future. + setTimeout(function() { + process.addAsyncListener(key); + + // Perform a few additional async events. + setImmediate(function() { + // Stop capturing from this call stack. + process.removeAsyncListener(key); + + process.nextTick(function() { }); + }); + }, 100); + + // Output: + // You summoned me? + +The user must be explicit and always pass the `AsyncListener` they wish +to remove. It is not possible to simply remove all listeners at once. + + [EventEmitter]: events.html#events_class_events_eventemitter +[Timers]: timers.html +[`process.createAsyncListener()`]: #process_process_createasynclistener_asynclistener_callbacksobj_storagevalue +[`process.addAsyncListener()`]: #process_process_addasynclistener_asynclistener +[`process.removeAsyncListener()`]: #process_process_removeasynclistener_asynclistener diff --git a/lib/_http_client.js b/lib/_http_client.js index 10bbac8f8129..6669249b7c5e 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -417,38 +417,45 @@ function responseOnEnd() { } } +function tickOnSocket(req, socket) { + var parser = parsers.alloc(); + req.socket = socket; + req.connection = socket; + parser.reinitialize(HTTPParser.RESPONSE); + parser.socket = socket; + parser.incoming = null; + req.parser = parser; + + socket.parser = parser; + socket._httpMessage = req; + + // Setup "drain" propogation. + httpSocketSetup(socket); + + // Propagate headers limit from request object to parser + if (util.isNumber(req.maxHeadersCount)) { + parser.maxHeaderPairs = req.maxHeadersCount << 1; + } else { + // Set default value because parser may be reused from FreeList + parser.maxHeaderPairs = 2000; + } + + parser.onIncoming = parserOnIncomingClient; + socket.on('error', socketErrorListener); + socket.on('data', socketOnData); + socket.on('end', socketOnEnd); + socket.on('close', socketCloseListener); + req.emit('socket', socket); +} + ClientRequest.prototype.onSocket = function(socket) { var req = this; process.nextTick(function() { - var parser = parsers.alloc(); - req.socket = socket; - req.connection = socket; - parser.reinitialize(HTTPParser.RESPONSE); - parser.socket = socket; - parser.incoming = null; - req.parser = parser; - - socket.parser = parser; - socket._httpMessage = req; - - // Setup "drain" propogation. - httpSocketSetup(socket); - - // Propagate headers limit from request object to parser - if (util.isNumber(req.maxHeadersCount)) { - parser.maxHeaderPairs = req.maxHeadersCount << 1; - } else { - // Set default value because parser may be reused from FreeList - parser.maxHeaderPairs = 2000; - } - - parser.onIncoming = parserOnIncomingClient; - socket.on('error', socketErrorListener); - socket.on('data', socketOnData); - socket.on('end', socketOnEnd); - socket.on('close', socketCloseListener); - req.emit('socket', socket); + // If a domain was added to the request, attach it to the socket. + if (req.domain) + socket._handle.addAsyncListener(req.domain._listener); + tickOnSocket(req, socket); }); }; diff --git a/lib/domain.js b/lib/domain.js index a9f6084b6f19..2a20a984d345 100644 --- a/lib/domain.js +++ b/lib/domain.js @@ -20,34 +20,13 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. var util = require('util'); -var events = require('events'); -var EventEmitter = events.EventEmitter; +var EventEmitter = require('events'); var inherits = util.inherits; // communicate with events module, but don't require that // module to have to load this one, since this module has // a few side effects. -events.usingDomains = true; - -// overwrite process.domain with a getter/setter that will allow for more -// effective optimizations -var _domain = [null]; -Object.defineProperty(process, 'domain', { - enumerable: true, - get: function() { - return _domain[0]; - }, - set: function(arg) { - return _domain[0] = arg; - } -}); - -// objects with external array data are excellent ways to communicate state -// between js and c++ w/o much overhead -var _domain_flag = {}; - -// let the process know we're using domains -process._setupDomainUse(_domain, _domain_flag); +EventEmitter.usingDomains = true; exports.Domain = Domain; @@ -63,65 +42,75 @@ exports._stack = stack; exports.active = null; +function noop() { } + + +var listenerObj = { + error: function errorHandler(domain, er) { + var caught = false; + // ignore errors on disposed domains. + // + // XXX This is a bit stupid. We should probably get rid of + // domain.dispose() altogether. It's almost always a terrible + // idea. --isaacs + if (domain._disposed) + return true; + + er.domain = domain; + er.domainThrown = true; + // wrap this in a try/catch so we don't get infinite throwing + try { + // One of three things will happen here. + // + // 1. There is a handler, caught = true + // 2. There is no handler, caught = false + // 3. It throws, caught = false + // + // If caught is false after this, then there's no need to exit() + // the domain, because we're going to crash the process anyway. + caught = domain.emit('error', er); + + if (stack.length === 0) + process.removeAsyncListener(domain._listener); + + // Exit all domains on the stack. Uncaught exceptions end the + // current tick and no domains should be left on the stack + // between ticks. + stack.length = 0; + exports.active = process.domain = null; + } catch (er2) { + // The domain error handler threw! oh no! + // See if another domain can catch THIS error, + // or else crash on the original one. + // If the user already exited it, then don't double-exit. + if (domain === exports.active) { + stack.pop(); + } + if (stack.length) { + exports.active = process.domain = stack[stack.length - 1]; + caught = process._fatalException(er2); + } else { + caught = false; + } + return caught; + } + return caught; + } +}; + + inherits(Domain, EventEmitter); function Domain() { EventEmitter.call(this); - this.members = []; + this._listener = process.createAsyncListener(noop, listenerObj, this); } Domain.prototype.members = undefined; Domain.prototype._disposed = undefined; +Domain.prototype._listener = undefined; -// Called by process._fatalException in case an error was thrown. -Domain.prototype._errorHandler = function errorHandler(er) { - var caught = false; - // ignore errors on disposed domains. - // - // XXX This is a bit stupid. We should probably get rid of - // domain.dispose() altogether. It's almost always a terrible - // idea. --isaacs - if (this._disposed) - return true; - - er.domain = this; - er.domainThrown = true; - // wrap this in a try/catch so we don't get infinite throwing - try { - // One of three things will happen here. - // - // 1. There is a handler, caught = true - // 2. There is no handler, caught = false - // 3. It throws, caught = false - // - // If caught is false after this, then there's no need to exit() - // the domain, because we're going to crash the process anyway. - caught = this.emit('error', er); - - // Exit all domains on the stack. Uncaught exceptions end the - // current tick and no domains should be left on the stack - // between ticks. - stack.length = 0; - exports.active = process.domain = null; - } catch (er2) { - // The domain error handler threw! oh no! - // See if another domain can catch THIS error, - // or else crash on the original one. - // If the user already exited it, then don't double-exit. - if (this === exports.active) { - stack.pop(); - } - if (stack.length) { - exports.active = process.domain = stack[stack.length - 1]; - caught = process._fatalException(er2); - } else { - caught = false; - } - return caught; - } - return caught; -}; Domain.prototype.enter = function() { if (this._disposed) return; @@ -130,35 +119,37 @@ Domain.prototype.enter = function() { // to push it onto the stack so that we can pop it later. exports.active = process.domain = this; stack.push(this); - _domain_flag[0] = stack.length; + + process.addAsyncListener(this._listener); }; + Domain.prototype.exit = function() { if (this._disposed) return; + process.removeAsyncListener(this._listener); + // exit all domains until this one. - var d; - do { - d = stack.pop(); - } while (d && d !== this); - _domain_flag[0] = stack.length; + var index = stack.lastIndexOf(this); + if (index !== -1) + stack.splice(index + 1); + else + stack.length = 0; exports.active = stack[stack.length - 1]; process.domain = exports.active; }; + // note: this works for timers as well. Domain.prototype.add = function(ee) { - // disposed domains can't be used for new things. - if (this._disposed) return; - - // already added to this domain. - if (ee.domain === this) return; + // If the domain is disposed or already added, then nothing left to do. + if (this._disposed || ee.domain === this) + return; // has a domain already - remove it first. - if (ee.domain) { + if (ee.domain) ee.domain.remove(ee); - } // check for circular Domain->Domain links. // This causes bad insanity! @@ -177,85 +168,128 @@ Domain.prototype.add = function(ee) { ee.domain = this; this.members.push(ee); + + // Adding the domain._listener to the Wrap associated with the event + // emitter instance will be done automatically either on class + // instantiation or manually, like in cases of net listen(). + // The reason it cannot be done here is because in specific cases the + // _handle is not created on EE instantiation, so there's no place to + // add the listener. }; + Domain.prototype.remove = function(ee) { ee.domain = null; var index = this.members.indexOf(ee); - if (index !== -1) { + if (index !== -1) this.members.splice(index, 1); + + // First check if the ee is a handle itself. + if (ee.removeAsyncListener) + ee.removeAsyncListener(this._listener); + + // Manually remove the asyncListener from the handle, if possible. + if (ee._handle && ee._handle.removeAsyncListener) + ee._handle.removeAsyncListener(this._listener); + + // TODO(trevnorris): Are there cases where the handle doesn't live on + // the ee or the _handle. + + // TODO(trevnorris): For debugging that we've missed adding AsyncWrap's + // methods to a handle somewhere on the native side. + if (ee._handle && !ee._handle.removeAsyncListener) { + process._rawDebug('Wrap handle is missing AsyncWrap methods'); + process.abort(); } }; + Domain.prototype.run = function(fn) { - return this.bind(fn)(); + if (this._disposed) + return; + this.enter(); + var ret = fn.call(this); + this.exit(); + return ret; }; + +function intercepted(_this, self, cb, fnargs) { + if (self._disposed) + return; + + if (fnargs[0] && fnargs[0] instanceof Error) { + var er = fnargs[0]; + util._extend(er, { + domainBound: cb, + domainThrown: false, + domain: self + }); + self.emit('error', er); + return; + } + + var len = fnargs.length; + var args = []; + var i, ret; + + self.enter(); + if (fnargs.length > 1) { + for (i = 1; i < fnargs.length; i++) + args.push(fnargs[i]); + ret = cb.apply(_this, args); + } else { + ret = cb.call(_this); + } + self.exit(); + + return ret; +} + + Domain.prototype.intercept = function(cb) { - return this.bind(cb, true); + var self = this; + + function runIntercepted() { + return intercepted(this, self, cb, arguments); + } + + return runIntercepted; }; -Domain.prototype.bind = function(cb, interceptError) { - // if cb throws, catch it here. - var self = this; - var b = function() { - // disposing turns functions into no-ops - if (self._disposed) return; - if (this instanceof Domain) { - return cb.apply(this, arguments); - } +function bound(_this, self, cb, fnargs) { + if (self._disposed) + return; - // only intercept first-arg errors if explicitly requested. - if (interceptError && arguments[0] && - (arguments[0] instanceof Error)) { - var er = arguments[0]; - util._extend(er, { - domainBound: cb, - domainThrown: false, - domain: self - }); - self.emit('error', er); - return; - } + var len = fnargs.length; + var args = []; + var i, ret; - // remove first-arg if intercept as assumed to be the error-arg - if (interceptError) { - var len = arguments.length; - var args; - switch (len) { - case 0: - case 1: - // no args that we care about. - args = []; - break; - case 2: - // optimization for most common case: cb(er, data) - args = [arguments[1]]; - break; - default: - // slower for less common case: cb(er, foo, bar, baz, ...) - args = new Array(len - 1); - for (var i = 1; i < len; i++) { - args[i - 1] = arguments[i]; - } - break; - } - self.enter(); - var ret = cb.apply(this, args); - self.exit(); - return ret; - } + self.enter(); + if (fnargs.length > 0) + ret = cb.apply(_this, fnargs); + else + ret = cb.call(_this); + self.exit(); - self.enter(); - var ret = cb.apply(this, arguments); - self.exit(); - return ret; - }; - b.domain = this; - return b; + return ret; +} + + +Domain.prototype.bind = function(cb) { + var self = this; + + function runBound() { + return bound(this, self, cb, arguments); + } + + runBound.domain = this; + + return runBound; }; + Domain.prototype.dispose = util.deprecate(function() { if (this._disposed) return; diff --git a/lib/events.js b/lib/events.js index e2e39fd6e574..88ac31a086a7 100644 --- a/lib/events.js +++ b/lib/events.js @@ -66,23 +66,21 @@ EventEmitter.prototype.emit = function(type) { this._events = {}; // If there is no 'error' event listener then throw. - if (type === 'error') { - if (!this._events.error || - (util.isObject(this._events.error) && !this._events.error.length)) { - er = arguments[1]; - if (this.domain) { - if (!er) er = new TypeError('Uncaught, unspecified "error" event.'); - er.domainEmitter = this; - er.domain = this.domain; - er.domainThrown = false; - this.domain.emit('error', er); - } else if (er instanceof Error) { - throw er; // Unhandled 'error' event - } else { - throw TypeError('Uncaught, unspecified "error" event.'); - } - return false; + if (type === 'error' && !this._events.error) { + er = arguments[1]; + if (this.domain) { + if (!er) + er = new Error('Uncaught, unspecified "error" event.'); + er.domainEmitter = this; + er.domain = this.domain; + er.domainThrown = false; + this.domain.emit('error', er); + } else if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + throw Error('Uncaught, unspecified "error" event.'); } + return false; } handler = this._events[type]; @@ -90,9 +88,6 @@ EventEmitter.prototype.emit = function(type) { if (util.isUndefined(handler)) return false; - if (this.domain && this !== process) - this.domain.enter(); - if (util.isFunction(handler)) { switch (arguments.length) { // fast cases @@ -125,9 +120,6 @@ EventEmitter.prototype.emit = function(type) { listeners[i].apply(this, args); } - if (this.domain && this !== process) - this.domain.exit(); - return true; }; diff --git a/lib/net.js b/lib/net.js index 3804d622b99a..afb2d0deceb0 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1089,9 +1089,20 @@ Server.prototype._listen2 = function(address, port, addressType, backlog, fd) { // generate connection key, this should be unique to the connection this._connectionKey = addressType + ':' + address + ':' + port; + // If a domain is attached to the event emitter then we need to add + // the listener to the handle. + if (this.domain) { + this._handle.addAsyncListener(this.domain._listener); + process.addAsyncListener(this.domain._listener); + } + process.nextTick(function() { self.emit('listening'); }); + + if (this.domain) { + process.removeAsyncListener(this.domain._listener); + } }; diff --git a/lib/timers.js b/lib/timers.js index 93dd65e3756e..00bbdc939170 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -30,6 +30,17 @@ var TIMEOUT_MAX = 2147483647; // 2^31-1 var debug = require('util').debuglog('timer'); +var asyncFlags = process._asyncFlags; +var runAsyncQueue = process._runAsyncQueue; +var loadAsyncQueue = process._loadAsyncQueue; +var unloadAsyncQueue = process._unloadAsyncQueue; + +// Do a little housekeeping. +delete process._asyncFlags; +delete process._runAsyncQueue; +delete process._loadAsyncQueue; +delete process._unloadAsyncQueue; + // IDLE TIMEOUTS // @@ -44,6 +55,9 @@ var debug = require('util').debuglog('timer'); // value = list var lists = {}; +// Make Timer as monomorphic as possible. +Timer.prototype._asyncQueue = undefined; + // the main function - creates lists on demand and the watchers associated // with them. function insert(item, msecs) { @@ -80,9 +94,9 @@ function listOnTimeout() { var now = Timer.now(); debug('now: %s', now); - var first; + var diff, first, hasQueue, threw; while (first = L.peek(list)) { - var diff = now - first._idleStart; + diff = now - first._idleStart; if (diff < msecs) { list.start(msecs - diff, 0); debug('%d list wait because diff is %d', msecs, diff); @@ -98,15 +112,18 @@ function listOnTimeout() { // other timers that expire on this tick should still run. // // https://github.com/joyent/node/issues/2631 - var domain = first.domain; - if (domain && domain._disposed) continue; + if (first.domain && first.domain._disposed) + continue; + + hasQueue = !!first._asyncQueue; + try { - if (domain) - domain.enter(); - var threw = true; + if (hasQueue) + loadAsyncQueue(first); + threw = true; first._onTimeout(); - if (domain) - domain.exit(); + if (hasQueue) + unloadAsyncQueue(first); threw = false; } finally { if (threw) { @@ -162,7 +179,6 @@ exports.enroll = function(item, msecs) { exports.active = function(item) { var msecs = item._idleTimeout; if (msecs >= 0) { - var list = lists[msecs]; if (!list || L.isEmpty(list)) { insert(item, msecs); @@ -171,9 +187,41 @@ exports.active = function(item) { L.append(list, item); } } + // Whether or not a new TimerWrap needed to be created, this should run + // for each item. This way each "item" (i.e. timer) can properly have + // their own domain assigned. + if (asyncFlags[0] > 0) + runAsyncQueue(item); }; +function timerAddAsyncListener(obj) { + if (!this._asyncQueue) + this._asyncQueue = []; + var queue = this._asyncQueue; + // This queue will be small. Probably always <= 3 items. + for (var i = 0; i < queue.length; i++) { + if (queue[i].uid === obj.uid) + return; + } + this._asyncQueue.push(obj); +} + + +function timerRemoveAsyncListener(obj) { + if (!this._asyncQueue) + return; + var queue = this._asyncQueue; + // This queue will be small. Probably always <= 3 items. + for (var i = 0; i < queue.length; i++) { + if (queue[i].uid === obj.uid) { + queue.splice(i, 1); + return; + } + } +} + + /* * DOM-style timers */ @@ -309,6 +357,10 @@ Timeout.prototype.close = function() { } }; +// For domain compatibility need to attach this API. +Timeout.prototype.addAsyncListener = timerAddAsyncListener; +Timeout.prototype.removeAsyncListener = timerRemoveAsyncListener; + var immediateQueue = {}; L.init(immediateQueue); @@ -316,16 +368,38 @@ L.init(immediateQueue); function processImmediate() { var queue = immediateQueue; + var hasQueue, immediate; immediateQueue = {}; L.init(immediateQueue); while (L.isEmpty(queue) === false) { - var immediate = L.shift(queue); - var domain = immediate.domain; - if (domain) domain.enter(); - immediate._onImmediate(); - if (domain) domain.exit(); + immediate = L.shift(queue); + hasQueue = !!immediate._asyncQueue; + + if (hasQueue) + loadAsyncQueue(immediate); + + var threw = true; + try { + immediate._onImmediate(); + threw = false; + } finally { + if (threw) { + if (!L.isEmpty(queue)) { + // Handle any remaining on next tick, assuming we're still + // alive to do so. + while (!L.isEmpty(immediateQueue)) { + L.append(queue, L.shift(immediateQueue)); + } + immediateQueue = queue; + process.nextTick(processImmediate); + } + } + } + + if (hasQueue) + unloadAsyncQueue(immediate); } // Only round-trip to C++ land if we have to. Calling clearImmediate() on an @@ -337,8 +411,20 @@ function processImmediate() { } +function Immediate() { } + +Immediate.prototype.addAsyncListener = timerAddAsyncListener; +Immediate.prototype.removeAsyncListener = timerRemoveAsyncListener; +Immediate.prototype.domain = undefined; +Immediate.prototype._onImmediate = undefined; +Immediate.prototype._asyncQueue = undefined; +Immediate.prototype._idleNext = undefined; +Immediate.prototype._idlePrev = undefined; + + exports.setImmediate = function(callback) { - var immediate = {}, args; + var immediate = new Immediate(); + var args; L.init(immediate); @@ -357,7 +443,11 @@ exports.setImmediate = function(callback) { process._immediateCallback = processImmediate; } - if (process.domain) immediate.domain = process.domain; + // setImmediates are handled more like nextTicks. + if (asyncFlags[0] > 0) + runAsyncQueue(immediate); + if (process.domain) + immediate.domain = process.domain; L.append(immediateQueue, immediate); @@ -389,9 +479,10 @@ function unrefTimeout() { debug('unrefTimer fired'); - var first; + var diff, first, hasQueue, threw; while (first = L.peek(unrefList)) { - var diff = now - first._idleStart; + diff = now - first._idleStart; + hasQueue = !!first._asyncQueue; if (diff < first._idleTimeout) { diff = first._idleTimeout - diff; @@ -403,18 +494,18 @@ function unrefTimeout() { L.remove(first); - var domain = first.domain; - if (!first._onTimeout) continue; - if (domain && domain._disposed) continue; + if (first.domain && first.domain._disposed) continue; try { - if (domain) domain.enter(); - var threw = true; + if (hasQueue) + loadAsyncQueue(first); + threw = true; debug('unreftimer firing timeout'); first._onTimeout(); threw = false; - if (domain) domain.exit(); + if (hasQueue) + unloadAsyncQueue(first); } finally { if (threw) process.nextTick(unrefTimeout); } diff --git a/node.gyp b/node.gyp index 50c1394719e1..4bb73980d7ce 100644 --- a/node.gyp +++ b/node.gyp @@ -115,6 +115,8 @@ 'src/udp_wrap.cc', 'src/uv.cc', # headers to make for a more pleasant IDE experience + 'src/async-wrap.h', + 'src/async-wrap-inl.h', 'src/env.h', 'src/env-inl.h', 'src/handle_wrap.h', diff --git a/src/async-wrap-inl.h b/src/async-wrap-inl.h new file mode 100644 index 000000000000..b342cabfe675 --- /dev/null +++ b/src/async-wrap-inl.h @@ -0,0 +1,235 @@ +// 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. + +#ifndef SRC_ASYNC_WRAP_INL_H_ +#define SRC_ASYNC_WRAP_INL_H_ + +#include "async-wrap.h" +#include "env.h" +#include "env-inl.h" +#include "util.h" +#include "util-inl.h" +#include "v8.h" +#include + +namespace node { + +inline AsyncWrap::AsyncWrap(Environment* env, v8::Handle object) + : object_(env->isolate(), object), + env_(env), + async_flags_(NO_OPTIONS) { + assert(!object.IsEmpty()); + + if (!env->has_async_listeners()) + return; + + // TODO(trevnorris): Do we really need to TryCatch this call? + v8::TryCatch try_catch; + try_catch.SetVerbose(true); + + v8::Local val = object.As(); + env->async_listener_run_function()->Call(env->process_object(), 1, &val); + + if (!try_catch.HasCaught()) + async_flags_ |= ASYNC_LISTENERS; +} + + +inline AsyncWrap::~AsyncWrap() { + assert(persistent().IsEmpty()); +} + + +template +inline void AsyncWrap::AddMethods(v8::Handle t) { + NODE_SET_PROTOTYPE_METHOD(t, + "addAsyncListener", + AddAsyncListener); + NODE_SET_PROTOTYPE_METHOD(t, + "removeAsyncListener", + RemoveAsyncListener); +} + + +inline uint32_t AsyncWrap::async_flags() const { + return async_flags_; +} + + +inline void AsyncWrap::set_flag(unsigned int flag) { + async_flags_ |= flag; +} + + +inline void AsyncWrap::remove_flag(unsigned int flag) { + async_flags_ &= ~flag; +} + + +inline bool AsyncWrap::has_async_queue() { + return async_flags() & ASYNC_LISTENERS; +} + + +inline Environment* AsyncWrap::env() const { + return env_; +} + + +inline v8::Local AsyncWrap::object() { + return PersistentToLocal(env()->isolate(), persistent()); +} + + +inline v8::Persistent& AsyncWrap::persistent() { + return object_; +} + + +inline v8::Handle AsyncWrap::MakeCallback( + const v8::Handle cb, + int argc, + v8::Handle* argv) { + assert(env()->context() == env()->isolate()->GetCurrentContext()); + + v8::Local context = object(); + v8::Local process = env()->process_object(); + + v8::TryCatch try_catch; + try_catch.SetVerbose(true); + + if (has_async_queue()) { + v8::Local val = context.As(); + env()->async_listener_load_function()->Call(process, 1, &val); + + if (try_catch.HasCaught()) + return v8::Undefined(env()->isolate()); + } + + v8::Local ret = cb->Call(context, argc, argv); + + if (try_catch.HasCaught()) { + return Undefined(env()->isolate()); + } + + if (has_async_queue()) { + v8::Local val = context.As(); + env()->async_listener_unload_function()->Call(process, 1, &val); + } + + Environment::TickInfo* tick_info = env()->tick_info(); + + if (tick_info->in_tick()) { + return ret; + } + + if (tick_info->length() == 0) { + tick_info->set_index(0); + return ret; + } + + tick_info->set_in_tick(true); + + // TODO(trevnorris): Consider passing "context" to _tickCallback so it + // can then be passed as the first argument to the nextTick callback. + // That should greatly help needing to create closures. + env()->tick_callback_function()->Call(process, 0, NULL); + + tick_info->set_in_tick(false); + + if (try_catch.HasCaught()) { + tick_info->set_last_threw(true); + return Undefined(env()->isolate()); + } + + return ret; +} + + +inline v8::Handle AsyncWrap::MakeCallback( + const v8::Handle symbol, + int argc, + v8::Handle* argv) { + v8::Local cb_v = object()->Get(symbol); + v8::Local cb = cb_v.As(); + assert(cb->IsFunction()); + + return MakeCallback(cb, argc, argv); +} + + +inline v8::Handle AsyncWrap::MakeCallback( + uint32_t index, + int argc, + v8::Handle* argv) { + v8::Local cb_v = object()->Get(index); + v8::Local cb = cb_v.As(); + assert(cb->IsFunction()); + + return MakeCallback(cb, argc, argv); +} + + +template +inline void AsyncWrap::AddAsyncListener( + const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args.GetIsolate()); + v8::HandleScope handle_scope(args.GetIsolate()); + + v8::Local handle = args.This(); + v8::Local listener = args[0]; + assert(listener->IsObject()); + assert(handle->InternalFieldCount() > 0); + + env->async_listener_push_function()->Call(handle, 1, &listener); + + TYPE* wrap = static_cast( + handle->GetAlignedPointerFromInternalField(0)); + assert(wrap != NULL); + wrap->set_flag(ASYNC_LISTENERS); +} + + +template +inline void AsyncWrap::RemoveAsyncListener( + const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args.GetIsolate()); + v8::HandleScope handle_scope(args.GetIsolate()); + + v8::Local handle = args.This(); + v8::Local listener = args[0]; + assert(listener->IsObject()); + assert(handle->InternalFieldCount() > 0); + + v8::Local ret = + env->async_listener_strip_function()->Call(handle, 1, &listener); + + if (ret->IsFalse()) { + TYPE* wrap = static_cast( + handle->GetAlignedPointerFromInternalField(0)); + assert(wrap != NULL); + wrap->remove_flag(ASYNC_LISTENERS); + } +} + +} // namespace node + +#endif // SRC_ASYNC_WRAP_INL_H_ diff --git a/src/async-wrap.h b/src/async-wrap.h new file mode 100644 index 000000000000..5bf38aae03d1 --- /dev/null +++ b/src/async-wrap.h @@ -0,0 +1,90 @@ +// 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. + +#ifndef SRC_ASYNC_WRAP_H_ +#define SRC_ASYNC_WRAP_H_ + +#include "env.h" +#include "v8.h" + +namespace node { + +class AsyncWrap { + public: + enum AsyncFlags { + NO_OPTIONS = 0, + ASYNC_LISTENERS = 1 + }; + + inline AsyncWrap(Environment* env, v8::Handle object); + + inline ~AsyncWrap(); + + template + static inline void AddMethods(v8::Handle t); + + inline uint32_t async_flags() const; + + inline void set_flag(unsigned int flag); + + inline void remove_flag(unsigned int flag); + + inline bool has_async_queue(); + + inline Environment* env() const; + + // Returns the wrapped object. Illegal to call in your destructor. + inline v8::Local object(); + + // Parent class is responsible to Dispose. + inline v8::Persistent& persistent(); + + // Only call these within a valid HandleScope. + inline v8::Handle MakeCallback(const v8::Handle cb, + int argc, + v8::Handle* argv); + inline v8::Handle MakeCallback(const v8::Handle symbol, + int argc, + v8::Handle* argv); + inline v8::Handle MakeCallback(uint32_t index, + int argc, + v8::Handle* argv); + + private: + // Add an async listener to an existing handle. + template + static inline void AddAsyncListener( + const v8::FunctionCallbackInfo& args); + + // Remove an async listener to an existing handle. + template + static inline void RemoveAsyncListener( + const v8::FunctionCallbackInfo& args); + + v8::Persistent object_; + Environment* const env_; + uint32_t async_flags_; +}; + +} // namespace node + + +#endif // SRC_ASYNC_WRAP_H_ diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index d86d8c4758c3..382516f26dd2 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -21,6 +21,8 @@ #define CARES_STATICLIB #include "ares.h" +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "env.h" #include "env-inl.h" #include "node.h" @@ -220,11 +222,10 @@ static Local HostentToNames(struct hostent* host) { } -class QueryWrap { +class QueryWrap : public AsyncWrap { public: - QueryWrap(Environment* env, Local req_wrap_obj) : env_(env) { - HandleScope scope(node_isolate); - persistent().Reset(node_isolate, req_wrap_obj); + QueryWrap(Environment* env, Local req_wrap_obj) + : AsyncWrap(env, req_wrap_obj) { } virtual ~QueryWrap() { @@ -243,14 +244,6 @@ class QueryWrap { return 0; } - inline Persistent& persistent() { - return object_; - } - - inline Local object() { - return PersistentToLocal(node_isolate, persistent()); - } - protected: void* GetQueryArg() { return static_cast(this); @@ -289,11 +282,7 @@ class QueryWrap { Integer::New(0, env()->isolate()), answer }; - MakeCallback(env(), - object(), - env()->oncomplete_string(), - ARRAY_SIZE(argv), - argv); + MakeCallback(env()->oncomplete_string(), ARRAY_SIZE(argv), argv); } void CallOnComplete(Local answer, Local family) { @@ -304,11 +293,7 @@ class QueryWrap { answer, family }; - MakeCallback(env(), - object(), - env()->oncomplete_string(), - ARRAY_SIZE(argv), - argv); + MakeCallback(env()->oncomplete_string(), ARRAY_SIZE(argv), argv); } void ParseError(int status) { @@ -350,7 +335,7 @@ class QueryWrap { arg = FIXED_ONE_BYTE_STRING(env()->isolate(), "UNKNOWN_ARES_ERROR"); break; } - MakeCallback(env(), object(), env()->oncomplete_string(), 1, &arg); + MakeCallback(env()->oncomplete_string(), 1, &arg); } // Subclasses should implement the appropriate Parse method. @@ -361,14 +346,6 @@ class QueryWrap { virtual void Parse(struct hostent* host) { assert(0); }; - - inline Environment* env() const { - return env_; - } - - private: - Persistent object_; - Environment* const env_; }; @@ -911,11 +888,7 @@ void AfterGetAddrInfo(uv_getaddrinfo_t* req, int status, struct addrinfo* res) { uv_freeaddrinfo(res); // Make the callback into JavaScript - MakeCallback(env, - req_wrap->object(), - env->oncomplete_string(), - ARRAY_SIZE(argv), - argv); + req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv); delete req_wrap; } diff --git a/src/env-inl.h b/src/env-inl.h index b520e0712926..d9aed32419f8 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -69,24 +69,26 @@ inline v8::Isolate* Environment::IsolateData::isolate() const { return isolate_; } -inline Environment::DomainFlag::DomainFlag() { - for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0; +inline Environment::AsyncListener::AsyncListener() { + for (int i = 0; i < kFieldsCount; ++i) + fields_[i] = 0; } -inline uint32_t* Environment::DomainFlag::fields() { +inline uint32_t* Environment::AsyncListener::fields() { return fields_; } -inline int Environment::DomainFlag::fields_count() const { +inline int Environment::AsyncListener::fields_count() const { return kFieldsCount; } -inline uint32_t Environment::DomainFlag::count() const { +inline uint32_t Environment::AsyncListener::count() const { return fields_[kCount]; } inline Environment::TickInfo::TickInfo() : in_tick_(false), last_threw_(false) { - for (int i = 0; i < kFieldsCount; ++i) fields_[i] = 0; + for (int i = 0; i < kFieldsCount; ++i) + fields_[i] = 0; } inline uint32_t* Environment::TickInfo::fields() { @@ -161,7 +163,6 @@ inline Environment::Environment(v8::Local context) : isolate_(context->GetIsolate()), isolate_data_(IsolateData::GetOrCreate(context->GetIsolate())), using_smalloc_alloc_cb_(false), - using_domains_(false), context_(context->GetIsolate(), context) { // We'll be creating new objects so make sure we've entered the context. v8::Context::Scope context_scope(context); @@ -187,10 +188,9 @@ inline v8::Isolate* Environment::isolate() const { return isolate_; } -inline bool Environment::in_domain() const { +inline bool Environment::has_async_listeners() const { // The const_cast is okay, it doesn't violate conceptual const-ness. - return using_domains() && - const_cast(this)->domain_flag()->count() > 0; + return const_cast(this)->async_listener()->count() > 0; } inline Environment* Environment::from_immediate_check_handle( @@ -227,8 +227,8 @@ inline uv_loop_t* Environment::event_loop() const { return isolate_data()->event_loop(); } -inline Environment::DomainFlag* Environment::domain_flag() { - return &domain_flag_; +inline Environment::AsyncListener* Environment::async_listener() { + return &async_listener_count_; } inline Environment::TickInfo* Environment::tick_info() { @@ -243,14 +243,6 @@ inline void Environment::set_using_smalloc_alloc_cb(bool value) { using_smalloc_alloc_cb_ = value; } -inline bool Environment::using_domains() const { - return using_domains_; -} - -inline void Environment::set_using_domains(bool value) { - using_domains_ = value; -} - inline Environment* Environment::from_cares_timer_handle(uv_timer_t* handle) { return CONTAINER_OF(handle, Environment, cares_timer_handle_); } diff --git a/src/env.h b/src/env.h index b45d250f060c..81286842ac52 100644 --- a/src/env.h +++ b/src/env.h @@ -53,6 +53,7 @@ namespace node { #define PER_ISOLATE_STRING_PROPERTIES(V) \ V(address_string, "address") \ V(atime_string, "atime") \ + V(async_queue_string, "_asyncQueue") \ V(birthtime_string, "birthtime") \ V(blksize_string, "blksize") \ V(blocks_string, "blocks") \ @@ -65,7 +66,6 @@ namespace node { V(ctime_string, "ctime") \ V(dev_string, "dev") \ V(disposed_string, "_disposed") \ - V(domain_string, "domain") \ V(enter_string, "enter") \ V(errno_string, "errno") \ V(exit_string, "exit") \ @@ -95,6 +95,7 @@ namespace node { V(onclienthello_string, "onclienthello") \ V(oncomplete_string, "oncomplete") \ V(onconnection_string, "onconnection") \ + V(ondone_string, "ondone") \ V(onerror_string, "onerror") \ V(onexit_string, "onexit") \ V(onhandshakedone_string, "onhandshakedone") \ @@ -102,6 +103,7 @@ namespace node { V(onmessage_string, "onmessage") \ V(onnewsession_string, "onnewsession") \ V(onread_string, "onread") \ + V(onselect_string, "onselect") \ V(onsignal_string, "onsignal") \ V(onstop_string, "onstop") \ V(path_string, "path") \ @@ -131,10 +133,14 @@ namespace node { V(write_queue_size_string, "writeQueueSize") \ #define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \ + V(async_listener_load_function, v8::Function) \ + V(async_listener_push_function, v8::Function) \ + V(async_listener_run_function, v8::Function) \ + V(async_listener_strip_function, v8::Function) \ + V(async_listener_unload_function, v8::Function) \ V(binding_cache_object, v8::Object) \ V(buffer_constructor_function, v8::Function) \ V(context, v8::Context) \ - V(domain_array, v8::Array) \ V(module_load_list_array, v8::Array) \ V(pipe_constructor_template, v8::FunctionTemplate) \ V(process_object, v8::Object) \ @@ -163,7 +169,7 @@ RB_HEAD(ares_task_list, ares_task_t); class Environment { public: - class DomainFlag { + class AsyncListener { public: inline uint32_t* fields(); inline int fields_count() const; @@ -171,7 +177,7 @@ class Environment { private: friend class Environment; // So we can call the constructor. - inline DomainFlag(); + inline AsyncListener(); enum Fields { kCount, @@ -180,7 +186,7 @@ class Environment { uint32_t fields_[kFieldsCount]; - DISALLOW_COPY_AND_ASSIGN(DomainFlag); + DISALLOW_COPY_AND_ASSIGN(AsyncListener); }; class TickInfo { @@ -223,7 +229,7 @@ class Environment { inline v8::Isolate* isolate() const; inline uv_loop_t* event_loop() const; - inline bool in_domain() const; + inline bool has_async_listeners() const; static inline Environment* from_immediate_check_handle(uv_check_t* handle); inline uv_check_t* immediate_check_handle(); @@ -235,7 +241,7 @@ class Environment { static inline Environment* from_idle_check_handle(uv_check_t* handle); inline uv_check_t* idle_check_handle(); - inline DomainFlag* domain_flag(); + inline AsyncListener* async_listener(); inline TickInfo* tick_info(); static inline Environment* from_cares_timer_handle(uv_timer_t* handle); @@ -247,9 +253,6 @@ class Environment { inline bool using_smalloc_alloc_cb() const; inline void set_using_smalloc_alloc_cb(bool value); - inline bool using_domains() const; - inline void set_using_domains(bool value); - // Strings are shared across shared contexts. The getters simply proxy to // the per-isolate primitive. #define V(PropertyName, StringValue) \ @@ -279,13 +282,12 @@ class Environment { uv_idle_t immediate_idle_handle_; uv_prepare_t idle_prepare_handle_; uv_check_t idle_check_handle_; - DomainFlag domain_flag_; + AsyncListener async_listener_count_; TickInfo tick_info_; uv_timer_t cares_timer_handle_; ares_channel cares_channel_; ares_task_list cares_task_list_; bool using_smalloc_alloc_cb_; - bool using_domains_; #define V(PropertyName, TypeName) \ v8::Persistent PropertyName ## _; diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc index 1fc901e2facb..076a224b39af 100644 --- a/src/fs_event_wrap.cc +++ b/src/fs_event_wrap.cc @@ -19,6 +19,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "env.h" #include "env-inl.h" #include "util.h" @@ -175,11 +177,7 @@ void FSEventWrap::OnEvent(uv_fs_event_t* handle, const char* filename, argv[2] = OneByteString(node_isolate, filename); } - MakeCallback(env, - wrap->object(), - env->onchange_string(), - ARRAY_SIZE(argv), - argv); + wrap->MakeCallback(env->onchange_string(), ARRAY_SIZE(argv), argv); } diff --git a/src/handle_wrap.cc b/src/handle_wrap.cc index d930bd355bd9..be37d63b6da6 100644 --- a/src/handle_wrap.cc +++ b/src/handle_wrap.cc @@ -20,6 +20,8 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "handle_wrap.h" +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "env.h" #include "env-inl.h" #include "util.h" @@ -89,12 +91,11 @@ void HandleWrap::Close(const FunctionCallbackInfo& args) { HandleWrap::HandleWrap(Environment* env, Handle object, uv_handle_t* handle) - : env_(env), + : AsyncWrap(env, object), flags_(0), handle__(handle) { handle__->data = this; HandleScope scope(node_isolate); - persistent().Reset(node_isolate, object); Wrap(object, this); QUEUE_INSERT_TAIL(&handle_wrap_queue, &handle_wrap_queue_); } @@ -121,7 +122,7 @@ void HandleWrap::OnClose(uv_handle_t* handle) { Local object = wrap->object(); if (wrap->flags_ & kCloseCallback) { - MakeCallback(env, object, env->close_string()); + wrap->MakeCallback(env->close_string(), 0, NULL); } object->SetAlignedPointerInInternalField(0, NULL); diff --git a/src/handle_wrap.h b/src/handle_wrap.h index 73a43c3a3f23..47cc44f91070 100644 --- a/src/handle_wrap.h +++ b/src/handle_wrap.h @@ -22,6 +22,7 @@ #ifndef SRC_HANDLE_WRAP_H_ #define SRC_HANDLE_WRAP_H_ +#include "async-wrap.h" #include "env.h" #include "node.h" #include "queue.h" @@ -50,7 +51,7 @@ namespace node { // js/c++ boundary crossing. At the javascript layer that should all be // taken care of. -class HandleWrap { +class HandleWrap : public AsyncWrap { public: static void Close(const v8::FunctionCallbackInfo& args); static void Ref(const v8::FunctionCallbackInfo& args); @@ -64,24 +65,10 @@ class HandleWrap { uv_handle_t* handle); virtual ~HandleWrap(); - inline Environment* env() const { - return env_; - } - - inline v8::Local object() { - return PersistentToLocal(env()->isolate(), persistent()); - } - - inline v8::Persistent& persistent() { - return object_; - } - private: friend void GetActiveHandles(const v8::FunctionCallbackInfo&); static void OnClose(uv_handle_t* handle); - v8::Persistent object_; QUEUE handle_wrap_queue_; - Environment* const env_; unsigned int flags_; // Using double underscore due to handle_ member in tcp_wrap. Probably // tcp_wrap should rename it's member to 'handle'. diff --git a/src/node.cc b/src/node.cc index 4e30269e0d78..f6efbd00436b 100644 --- a/src/node.cc +++ b/src/node.cc @@ -40,6 +40,8 @@ #endif #include "ares.h" +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "env.h" #include "env-inl.h" #include "handle_wrap.h" @@ -841,83 +843,78 @@ Local WinapiErrnoException(int errorno, #endif -void SetupDomainUse(const FunctionCallbackInfo& args) { +void SetupAsyncListener(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args.GetIsolate()); + HandleScope handle_scope(args.GetIsolate()); - if (env->using_domains()) - return; - env->set_using_domains(true); + assert(args[0]->IsObject()); + assert(args[1]->IsFunction()); + assert(args[2]->IsFunction()); + assert(args[3]->IsFunction()); + assert(args[4]->IsFunction()); + assert(args[5]->IsFunction()); + + env->set_async_listener_run_function(args[1].As()); + env->set_async_listener_load_function(args[2].As()); + env->set_async_listener_unload_function(args[3].As()); + env->set_async_listener_push_function(args[4].As()); + env->set_async_listener_strip_function(args[5].As()); + + Local async_listener_flag_obj = args[0].As(); + Environment::AsyncListener* async_listener = env->async_listener(); + async_listener_flag_obj->SetIndexedPropertiesToExternalArrayData( + async_listener->fields(), + kExternalUnsignedIntArray, + async_listener->fields_count()); - HandleScope scope(node_isolate); - Local process_object = env->process_object(); + // Do a little housekeeping. + env->process_object()->Delete( + FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupAsyncListener")); +} - Local tick_callback_function_key = - FIXED_ONE_BYTE_STRING(node_isolate, "_tickDomainCallback"); - Local tick_callback_function = - process_object->Get(tick_callback_function_key).As(); - if (!tick_callback_function->IsFunction()) { - fprintf(stderr, "process._tickDomainCallback assigned to non-function\n"); - abort(); - } +void SetupNextTick(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args.GetIsolate()); + HandleScope handle_scope(args.GetIsolate()); - process_object->Set(FIXED_ONE_BYTE_STRING(node_isolate, "_tickCallback"), - tick_callback_function); - env->set_tick_callback_function(tick_callback_function); + assert(args[0]->IsObject() && args[1]->IsFunction()); - if (!args[0]->IsArray()) { - fprintf(stderr, "_setupDomainUse first argument must be an array\n"); - abort(); - } - env->set_domain_array(args[0].As()); + // Values use to cross communicate with processNextTick. + Local tick_info_obj = args[0].As(); + tick_info_obj->SetIndexedPropertiesToExternalArrayData( + env->tick_info()->fields(), + kExternalUnsignedIntArray, + env->tick_info()->fields_count()); - if (!args[1]->IsObject()) { - fprintf(stderr, "_setupDomainUse second argument must be an object\n"); - abort(); - } + env->set_tick_callback_function(args[1].As()); - Local domain_flag_obj = args[1].As(); - Environment::DomainFlag* domain_flag = env->domain_flag(); - domain_flag_obj->SetIndexedPropertiesToExternalArrayData( - domain_flag->fields(), - kExternalUnsignedIntArray, - domain_flag->fields_count()); + // Do a little housekeeping. + env->process_object()->Delete( + FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupNextTick")); } -Handle MakeDomainCallback(Environment* env, - const Handle object, - const Handle callback, - int argc, - Handle argv[]) { +Handle MakeCallback(Environment* env, + Handle object, + const Handle callback, + int argc, + Handle argv[]) { // If you hit this assertion, you forgot to enter the v8::Context first. assert(env->context() == env->isolate()->GetCurrentContext()); - // TODO(trevnorris) Hook for long stack traces to be made here. - - Local domain_v = object->Get(env->domain_string()); - Local domain; + Local process = env->process_object(); TryCatch try_catch; try_catch.SetVerbose(true); - bool has_domain = domain_v->IsObject(); - if (has_domain) { - domain = domain_v.As(); + // TODO(trevnorris): This is sucky for performance. Fix it. + bool has_async_queue = object->Has(env->async_queue_string()); + if (has_async_queue) { + Local argv[] = { object }; + env->async_listener_load_function()->Call(process, ARRAY_SIZE(argv), argv); - if (domain->Get(env->disposed_string())->IsTrue()) { - // domain has been disposed of. + if (try_catch.HasCaught()) return Undefined(node_isolate); - } - - Local enter = - domain->Get(env->enter_string()).As(); - assert(enter->IsFunction()); - enter->Call(domain, 0, NULL); - - if (try_catch.HasCaught()) { - return Undefined(node_isolate); - } } Local ret = callback->Call(object, argc, argv); @@ -926,24 +923,16 @@ Handle MakeDomainCallback(Environment* env, return Undefined(node_isolate); } - if (has_domain) { - Local exit = - domain->Get(env->exit_string()).As(); - assert(exit->IsFunction()); - exit->Call(domain, 0, NULL); + if (has_async_queue) { + Local val = object.As(); + env->async_listener_unload_function()->Call(process, 1, &val); - if (try_catch.HasCaught()) { + if (try_catch.HasCaught()) return Undefined(node_isolate); - } } Environment::TickInfo* tick_info = env->tick_info(); - if (tick_info->last_threw() == 1) { - tick_info->set_last_threw(0); - return ret; - } - if (tick_info->in_tick()) { return ret; } @@ -956,73 +945,7 @@ Handle MakeDomainCallback(Environment* env, tick_info->set_in_tick(true); // process nextTicks after call - Local process_object = env->process_object(); - Local tick_callback_function = env->tick_callback_function(); - tick_callback_function->Call(process_object, 0, NULL); - - tick_info->set_in_tick(false); - - if (try_catch.HasCaught()) { - tick_info->set_last_threw(true); - return Undefined(node_isolate); - } - - return ret; -} - - -Handle MakeCallback(Environment* env, - const Handle object, - const Handle callback, - int argc, - Handle argv[]) { - // If you hit this assertion, you forgot to enter the v8::Context first. - assert(env->context() == env->isolate()->GetCurrentContext()); - - // TODO(trevnorris) Hook for long stack traces to be made here. - Local process_object = env->process_object(); - - if (env->using_domains()) - return MakeDomainCallback(env, object, callback, argc, argv); - - TryCatch try_catch; - try_catch.SetVerbose(true); - - Local ret = callback->Call(object, argc, argv); - - if (try_catch.HasCaught()) { - return Undefined(node_isolate); - } - - Environment::TickInfo* tick_info = env->tick_info(); - - if (tick_info->in_tick() == 1) { - return ret; - } - - if (tick_info->length() == 0) { - tick_info->set_index(0); - return ret; - } - - tick_info->set_in_tick(true); - - // lazy load no domain next tick callbacks - Local tick_callback_function = env->tick_callback_function(); - if (tick_callback_function.IsEmpty()) { - Local tick_callback_function_key = - FIXED_ONE_BYTE_STRING(node_isolate, "_tickCallback"); - tick_callback_function = - process_object->Get(tick_callback_function_key).As(); - if (!tick_callback_function->IsFunction()) { - fprintf(stderr, "process._tickCallback assigned to non-function\n"); - abort(); - } - env->set_tick_callback_function(tick_callback_function); - } - - // process nextTicks after call - tick_callback_function->Call(process_object, 0, NULL); + env->tick_callback_function()->Call(process, 0, NULL); tick_info->set_in_tick(false); @@ -1041,16 +964,9 @@ Handle MakeCallback(Environment* env, uint32_t index, int argc, Handle argv[]) { - // If you hit this assertion, you forgot to enter the v8::Context first. - assert(env->context() == env->isolate()->GetCurrentContext()); - Local callback = object->Get(index).As(); assert(callback->IsFunction()); - if (env->using_domains()) { - return MakeDomainCallback(env, object, callback, argc, argv); - } - return MakeCallback(env, object, callback, argc, argv); } @@ -1060,16 +976,8 @@ Handle MakeCallback(Environment* env, const Handle symbol, int argc, Handle argv[]) { - // If you hit this assertion, you forgot to enter the v8::Context first. - assert(env->context() == env->isolate()->GetCurrentContext()); - Local callback = object->Get(symbol).As(); assert(callback->IsFunction()); - - if (env->using_domains()) { - return MakeDomainCallback(env, object, callback, argc, argv); - } - return MakeCallback(env, object, callback, argc, argv); } @@ -1079,8 +987,6 @@ Handle MakeCallback(Environment* env, const char* method, int argc, Handle argv[]) { - // If you hit this assertion, you forgot to enter the v8::Context first. - assert(env->context() == env->isolate()->GetCurrentContext()); Local method_string = OneByteString(node_isolate, method); return MakeCallback(env, object, method_string, argc, argv); } @@ -1122,19 +1028,6 @@ Handle MakeCallback(const Handle object, } -Handle MakeDomainCallback(const Handle object, - const Handle callback, - int argc, - Handle argv[]) { - Local context = object->CreationContext(); - Environment* env = Environment::GetCurrent(context); - Context::Scope context_scope(context); - HandleScope handle_scope(env->isolate()); - return handle_scope.Close( - MakeDomainCallback(env, object, callback, argc, argv)); -} - - enum encoding ParseEncoding(Handle encoding_v, enum encoding _default) { HandleScope scope(node_isolate); @@ -2570,7 +2463,8 @@ void SetupProcessObject(Environment* env, NODE_SET_METHOD(process, "binding", Binding); - NODE_SET_METHOD(process, "_setupDomainUse", SetupDomainUse); + NODE_SET_METHOD(process, "_setupAsyncListener", SetupAsyncListener); + NODE_SET_METHOD(process, "_setupNextTick", SetupNextTick); // values use to cross communicate with processNextTick Local tick_info_obj = Object::New(); diff --git a/src/node.js b/src/node.js index 8692a2c95ed7..7290d5a852c8 100644 --- a/src/node.js +++ b/src/node.js @@ -26,6 +26,7 @@ // of the startup process, so many dependencies are invoked lazily. (function(process) { this.global = this; + var _errorHandler; function startup() { var EventEmitter = NativeModule.require('events').EventEmitter; @@ -46,6 +47,7 @@ startup.globalTimeouts(); startup.globalConsole(); + startup.processAsyncListener(); startup.processAssert(); startup.processConfig(); startup.processNextTick(); @@ -219,16 +221,14 @@ startup.processFatal = function() { process._fatalException = function(er) { - var caught = false; + // First run through error handlers from asyncListener. + var caught = _errorHandler(er); - if (process.domain && process.domain._errorHandler) { - caught = process.domain._errorHandler(er); - } else { + if (!caught) caught = process.emit('uncaughtException', er); - } - // if someone handled it, then great. otherwise, die in C++ land - // since that means that we'll exit the process, emit the 'exit' event + // If someone handled it, then great. Otherwise die in C++ since + // that means we'll exit the process, emit the 'exit' event. if (!caught) { try { if (!process._exiting) { @@ -241,13 +241,288 @@ // if we handled an error, then make sure any ticks get processed } else { - setImmediate(process._tickCallback); + var t = setImmediate(process._tickCallback); + // Complete hack to make sure any errors thrown from async + // listeners don't cause an infinite loop. + if (t._asyncQueue) + t._asyncQueue = []; } return caught; }; }; + startup.processAsyncListener = function() { + var asyncStack = []; + var asyncQueue = []; + var uid = 0; + + // Stateful flags shared with Environment for quick JS/C++ + // communication. + var asyncFlags = {}; + + // Prevent accidentally suppressed thrown errors from before/after. + var inAsyncTick = false; + + // To prevent infinite recursion when an error handler also throws + // flag when an error is currenly being handled. + var inErrorTick = false; + + // Needs to be the same as src/env.h + var kCount = 0; + + // _errorHandler is scoped so it's also accessible by _fatalException. + _errorHandler = errorHandler; + + // Needs to be accessible from lib/timers.js so they know when async + // listeners are currently in queue. They'll be cleaned up once + // references there are made. + process._asyncFlags = asyncFlags; + process._runAsyncQueue = runAsyncQueue; + process._loadAsyncQueue = loadAsyncQueue; + process._unloadAsyncQueue = unloadAsyncQueue; + + // Public API. + process.createAsyncListener = createAsyncListener; + process.addAsyncListener = addAsyncListener; + process.removeAsyncListener = removeAsyncListener; + + // Setup shared objects/callbacks with native layer. + process._setupAsyncListener(asyncFlags, + runAsyncQueue, + loadAsyncQueue, + unloadAsyncQueue, + pushListener, + stripListener); + + function popQueue() { + if (asyncStack.length > 0) + asyncQueue = asyncStack.pop(); + else + asyncQueue = []; + } + + // Run all the async listeners attached when an asynchronous event is + // instantiated. + function runAsyncQueue(context) { + var queue = []; + var queueItem, item, i, value; + + inAsyncTick = true; + for (i = 0; i < asyncQueue.length; i++) { + queueItem = asyncQueue[i]; + // Not passing "this" context because it hasn't actually been + // instantiated yet, so accessing some of the object properties + // can cause a segfault. + // Passing the original value will allow users to manipulate the + // original value object, while also allowing them to return a + // new value for current async call tracking. + value = queueItem.listener(queueItem.value); + if (typeof value !== 'undefined') { + item = { + callbacks: queueItem.callbacks, + value: value, + listener: queueItem.listener, + uid: queueItem.uid + }; + } else { + item = queueItem; + } + queue[i] = item; + } + inAsyncTick = false; + + context._asyncQueue = queue; + } + + // Uses the _asyncQueue object attached by runAsyncQueue. + function loadAsyncQueue(context) { + var queue = context._asyncQueue; + var item, before, i; + + asyncStack.push(asyncQueue); + asyncQueue = queue; + // Since the async listener callback is required, the number of + // objects in the asyncQueue implies the number of async listeners + // there are to be processed. + asyncFlags[kCount] = queue.length; + + // Run "before" callbacks. + inAsyncTick = true; + for (i = 0; i < queue.length; i++) { + item = queue[i]; + if (!item.callbacks) + continue; + before = item.callbacks.before; + if (typeof before === 'function') + before(context, item.value); + } + inAsyncTick = false; + } + + // Unload one level of the async stack. Returns true if there are + // still listeners somewhere in the stack. + function unloadAsyncQueue(context) { + var item, after, i; + + // Run "after" callbacks. + inAsyncTick = true; + for (i = 0; i < asyncQueue.length; i++) { + item = asyncQueue[i]; + if (!item.callbacks) + continue; + after = item.callbacks.after; + if (typeof after === 'function') + after(context, item.value); + } + inAsyncTick = false; + + // Unload the current queue from the stack. + popQueue(); + + asyncFlags[kCount] = asyncQueue.length; + + return asyncQueue.length > 0 || asyncStack.length > 0; + } + + // Create new async listener object. Useful when instantiating a new + // object and want the listener instance, but not add it to the stack. + function createAsyncListener(listener, callbacks, value) { + return { + callbacks: callbacks, + value: value, + listener: listener, + uid: uid++ + }; + } + + // Add a listener to the current queue. + function addAsyncListener(listener, callbacks, value) { + // Accept new listeners or previous created listeners. + if (typeof listener === 'function') + callbacks = createAsyncListener(listener, callbacks, value); + else + callbacks = listener; + + var inQueue = false; + // The asyncQueue will be small. Probably always <= 3 items. + for (var i = 0; i < asyncQueue.length; i++) { + if (callbacks.uid === asyncQueue[i].uid) { + inQueue = true; + break; + } + } + + // Make sure the callback doesn't already exist in the queue. + if (!inQueue) + asyncQueue.push(callbacks); + + asyncFlags[kCount] = asyncQueue.length; + return callbacks; + } + + // Remove listener from the current queue and the entire stack. + function removeAsyncListener(obj) { + var i, j; + + for (i = 0; i < asyncQueue.length; i++) { + if (obj.uid === asyncQueue[i].uid) { + asyncQueue.splice(i, 1); + break; + } + } + + for (i = 0; i < asyncStack.length; i++) { + for (j = 0; j < asyncStack[i].length; j++) { + if (obj.uid === asyncStack[i][j].uid) { + asyncStack[i].splice(j, 1); + break; + } + } + } + + asyncFlags[kCount] = asyncQueue.length; + } + + // Error handler used by _fatalException to run through all error + // callbacks in the current asyncQueue. + function errorHandler(er) { + var handled = false; + var error, item, i; + + if (inErrorTick) + return false; + + inErrorTick = true; + for (i = 0; i < asyncQueue.length; i++) { + item = asyncQueue[i]; + if (!item.callbacks) + continue; + error = item.callbacks.error; + if (typeof error === 'function') { + try { + var threw = true; + handled = error(item.value, er) || handled; + threw = false; + } finally { + // If the error callback throws then we're going to die + // quickly with no chance of recovery. Only thing we're going + // to allow is execution of process exit event callbacks. + if (threw) { + process._exiting = true; + process.emit('exit', 1); + } + } + } + } + inErrorTick = false; + + // Unload the current queue from the stack. + popQueue(); + + return handled && !inAsyncTick; + } + + // Used by AsyncWrap::AddAsyncListener() to add an individual listener + // to the async queue. It will check the uid of the listener and only + // allow it to be added once. + function pushListener(obj) { + if (!this._asyncQueue) + this._asyncQueue = []; + + var queue = this._asyncQueue; + var inQueue = false; + // The asyncQueue will be small. Probably always <= 3 items. + for (var i = 0; i < queue.length; i++) { + if (obj.uid === queue.uid) { + inQueue = true; + break; + } + } + + if (!inQueue) + queue.push(obj); + } + + // Used by AsyncWrap::RemoveAsyncListener() to remove an individual + // listener from the async queue, and return whether there are still + // listeners in the queue. + function stripListener(obj) { + if (!this._asyncQueue || this._asyncQueue.length === 0) + return false; + + // The asyncQueue will be small. Probably always <= 3 items. + for (var i = 0; i < this._asyncQueue.length; i++) { + if (obj.uid === this._asyncQueue[i].uid) { + this._asyncQueue.splice(i, 1); + break; + } + } + + return this._asyncQueue.length > 0; + } + }; + var assert; startup.processAssert = function() { assert = process.assert = function(x, msg) { @@ -272,19 +547,28 @@ startup.processNextTick = function() { var nextTickQueue = []; + var asyncFlags = process._asyncFlags; + var _runAsyncQueue = process._runAsyncQueue; + var _loadAsyncQueue = process._loadAsyncQueue; + var _unloadAsyncQueue = process._unloadAsyncQueue; // This tickInfo thing is used so that the C++ code in src/node.cc // can have easy accesss to our nextTick state, and avoid unnecessary - var tickInfo = process._tickInfo; + var tickInfo = {}; // *Must* match Environment::TickInfo::Fields in src/env.h. var kIndex = 0; var kLength = 1; + // For asyncFlags. + // *Must* match Environment::AsyncListeners::Fields in src/env.h + var kCount = 0; + process.nextTick = nextTick; - // needs to be accessible from cc land + // Needs to be accessible from beyond this scope. process._tickCallback = _tickCallback; - process._tickDomainCallback = _tickDomainCallback; + + process._setupNextTick(tickInfo, _tickCallback); function tickDone() { if (tickInfo[kLength] !== 0) { @@ -299,45 +583,26 @@ tickInfo[kIndex] = 0; } - // run callbacks that have no domain - // using domains will cause this to be overridden + // Run callbacks that have no domain. function _tickCallback() { - var callback, threw; - - while (tickInfo[kIndex] < tickInfo[kLength]) { - callback = nextTickQueue[tickInfo[kIndex]++].callback; - threw = true; - try { - callback(); - threw = false; - } finally { - if (threw) tickDone(); - } - } - - tickDone(); - } - - function _tickDomainCallback() { - var tock, callback, threw, domain; + var callback, hasQueue, threw, tock; while (tickInfo[kIndex] < tickInfo[kLength]) { tock = nextTickQueue[tickInfo[kIndex]++]; callback = tock.callback; - domain = tock.domain; - if (domain) { - if (domain._disposed) continue; - domain.enter(); - } threw = true; + hasQueue = !!tock._asyncQueue; + if (hasQueue) + _loadAsyncQueue(tock); try { callback(); threw = false; } finally { - if (threw) tickDone(); + if (threw) + tickDone(); } - if (domain) - domain.exit(); + if (hasQueue) + _unloadAsyncQueue(tock); } tickDone(); @@ -348,10 +613,15 @@ if (process._exiting) return; - nextTickQueue.push({ + var obj = { callback: callback, - domain: process.domain || null - }); + _asyncQueue: undefined + }; + + if (asyncFlags[kCount] > 0) + _runAsyncQueue(obj); + + nextTickQueue.push(obj); tickInfo[kLength]++; } }; diff --git a/src/node_contextify.cc b/src/node_contextify.cc index ae3e4ace4ac7..d1568f8f4855 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -412,8 +412,9 @@ class ContextifyScript : public WeakObject { return ThrowError("Must call vm.Script as a constructor."); } + Environment* env = Environment::GetCurrent(args.GetIsolate()); ContextifyScript* contextify_script = - new ContextifyScript(args.GetIsolate(), args.This()); + new ContextifyScript(env, args.This()); TryCatch try_catch; Local code = args[0]->ToString(); @@ -605,8 +606,8 @@ class ContextifyScript : public WeakObject { } - ContextifyScript(Isolate* isolate, Local object) - : WeakObject(isolate, object) { + ContextifyScript(Environment* env, Local object) + : WeakObject(env, object) { } diff --git a/src/node_crypto.cc b/src/node_crypto.cc index c885cb8a1675..7a5e3657b626 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -845,7 +845,7 @@ int SSLWrap::NewSessionCallback(SSL* s, SSL_SESSION* sess) { HandleScope scope(node_isolate); Base* w = static_cast(SSL_get_app_data(s)); - Environment* env = w->env(); + Environment* env = w->ssl_env(); if (!w->session_callbacks_) return 0; @@ -866,11 +866,7 @@ int SSLWrap::NewSessionCallback(SSL* s, SSL_SESSION* sess) { reinterpret_cast(sess->session_id), sess->session_id_length); Local argv[] = { session, buff }; - MakeCallback(env, - w->weak_object(node_isolate), - env->onnewsession_string(), - ARRAY_SIZE(argv), - argv); + w->MakeCallback(env->onnewsession_string(), ARRAY_SIZE(argv), argv); return 0; } @@ -882,7 +878,7 @@ void SSLWrap::OnClientHello(void* arg, HandleScope scope(node_isolate); Base* w = static_cast(arg); - Environment* env = w->env(); + Environment* env = w->ssl_env(); Local hello_obj = Object::New(); Local buff = Buffer::New( @@ -901,11 +897,7 @@ void SSLWrap::OnClientHello(void* arg, hello_obj->Set(env->tls_ticket_string(), Boolean::New(hello.has_ticket())); Local argv[] = { hello_obj }; - MakeCallback(env, - w->weak_object(node_isolate), - env->onclienthello_string(), - ARRAY_SIZE(argv), - argv); + w->MakeCallback(env->onclienthello_string(), ARRAY_SIZE(argv), argv); } @@ -916,7 +908,7 @@ void SSLWrap::GetPeerCertificate( HandleScope scope(node_isolate); Base* w = Unwrap(args.This()); - Environment* env = w->env(); + Environment* env = w->ssl_env(); Local info = Object::New(); X509* peer_cert = SSL_get_peer_certificate(w->ssl_); @@ -1109,7 +1101,7 @@ void SSLWrap::LoadSession(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Base* w = Unwrap(args.This()); - Environment* env = w->env(); + Environment* env = w->ssl_env(); if (args.Length() >= 1 && Buffer::HasInstance(args[0])) { ssize_t slen = Buffer::Length(args[0]); @@ -1258,7 +1250,7 @@ void SSLWrap::GetCurrentCipher(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); Base* w = Unwrap(args.This()); - Environment* env = w->env(); + Environment* env = w->ssl_env(); OPENSSL_CONST SSL_CIPHER* c = SSL_get_current_cipher(w->ssl_); if (c == NULL) @@ -1432,8 +1424,7 @@ int Connection::HandleBIOError(BIO *bio, const char* func, int rv) { HandleScope scope(node_isolate); Local exception = Exception::Error(OneByteString(node_isolate, ssl_error_buf)); - weak_object(node_isolate)->Set( - FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); + object()->Set(FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); DEBUG_PRINT("[%p] BIO: %s failed: (%d) %s\n", ssl_, @@ -1476,8 +1467,7 @@ int Connection::HandleSSLError(const char* func, } else if (err == SSL_ERROR_ZERO_RETURN) { Local exception = Exception::Error(FIXED_ONE_BYTE_STRING(node_isolate, "ZERO_RETURN")); - weak_object(node_isolate)->Set( - FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); + object()->Set(FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); return rv; } else if (err == SSL_ERROR_SYSCALL && ss == kIgnoreSyscall) { @@ -1501,8 +1491,7 @@ int Connection::HandleSSLError(const char* func, BIO_get_mem_ptr(bio, &mem); Local exception = Exception::Error(OneByteString(node_isolate, mem->data, mem->length)); - weak_object(node_isolate)->Set( - FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); + object()->Set(FIXED_ONE_BYTE_STRING(node_isolate, "error"), exception); BIO_free_all(bio); } @@ -1519,7 +1508,7 @@ void Connection::ClearError() { // We should clear the error in JS-land Local error_key = FIXED_ONE_BYTE_STRING(node_isolate, "error"); - Local error = weak_object(node_isolate)->Get(error_key); + Local error = object()->Get(error_key); assert(error->BooleanValue() == false); #endif // NDEBUG } @@ -1533,13 +1522,13 @@ void Connection::SetShutdownFlags() { if (flags & SSL_SENT_SHUTDOWN) { Local sent_shutdown_key = FIXED_ONE_BYTE_STRING(node_isolate, "sentShutdown"); - weak_object(node_isolate)->Set(sent_shutdown_key, True(node_isolate)); + object()->Set(sent_shutdown_key, True(node_isolate)); } if (flags & SSL_RECEIVED_SHUTDOWN) { Local received_shutdown_key = FIXED_ONE_BYTE_STRING(node_isolate, "receivedShutdown"); - weak_object(node_isolate)->Set(received_shutdown_key, True(node_isolate)); + object()->Set(received_shutdown_key, True(node_isolate)); } } @@ -1644,10 +1633,8 @@ int Connection::SelectSNIContextCallback_(SSL *s, int *ad, void* arg) { if (!conn->sniObject_.IsEmpty()) { conn->sniContext_.Dispose(); - Local sni_object = - PersistentToLocal(node_isolate, conn->sniObject_); Local arg = PersistentToLocal(node_isolate, conn->servername_); - Local ret = MakeCallback(env, sni_object, "onselect", 1, &arg); + Local ret = conn->MakeCallback(env->onselect_string(), 1, &arg); // If ret is SecureContext Local secure_context_constructor_template = @@ -1766,15 +1753,11 @@ void Connection::SSLInfoCallback(const SSL *ssl_, int where, int ret) { HandleScope handle_scope(env->isolate()); if (where & SSL_CB_HANDSHAKE_START) { - MakeCallback(env, - conn->weak_object(node_isolate), - env->onhandshakestart_string()); + conn->MakeCallback(env->onhandshakestart_string(), 0, NULL); } if (where & SSL_CB_HANDSHAKE_DONE) { - MakeCallback(env, - conn->weak_object(node_isolate), - env->onhandshakedone_string()); + conn->MakeCallback(env->onhandshakedone_string(), 0, NULL); } } @@ -2088,7 +2071,8 @@ void CipherBase::Initialize(Environment* env, Handle target) { void CipherBase::New(const FunctionCallbackInfo& args) { assert(args.IsConstructCall() == true); CipherKind kind = args[0]->IsTrue() ? kCipher : kDecipher; - new CipherBase(args.GetIsolate(), args.This(), kind); + Environment* env = Environment::GetCurrent(args.GetIsolate()); + new CipherBase(env, args.This(), kind); } @@ -2329,7 +2313,8 @@ void Hmac::Initialize(Environment* env, v8::Handle target) { void Hmac::New(const FunctionCallbackInfo& args) { - new Hmac(args.GetIsolate(), args.This()); + Environment* env = Environment::GetCurrent(args.GetIsolate()); + new Hmac(env, args.This()); } @@ -2466,7 +2451,8 @@ void Hash::New(const FunctionCallbackInfo& args) { const String::Utf8Value hash_type(args[0]); - Hash* hash = new Hash(args.GetIsolate(), args.This()); + Environment* env = Environment::GetCurrent(args.GetIsolate()); + Hash* hash = new Hash(env, args.This()); if (!hash->HashInit(*hash_type)) { return ThrowError("Digest method not supported"); } @@ -2565,7 +2551,8 @@ void Sign::Initialize(Environment* env, v8::Handle target) { void Sign::New(const FunctionCallbackInfo& args) { - new Sign(args.GetIsolate(), args.This()); + Environment* env = Environment::GetCurrent(args.GetIsolate()); + new Sign(env, args.This()); } @@ -2746,7 +2733,8 @@ void Verify::Initialize(Environment* env, v8::Handle target) { void Verify::New(const FunctionCallbackInfo& args) { - new Verify(args.GetIsolate(), args.This()); + Environment* env = Environment::GetCurrent(args.GetIsolate()); + new Verify(env, args.This()); } @@ -3006,8 +2994,8 @@ void DiffieHellman::DiffieHellmanGroup( const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); - DiffieHellman* diffieHellman = - new DiffieHellman(args.GetIsolate(), args.This()); + Environment* env = Environment::GetCurrent(args.GetIsolate()); + DiffieHellman* diffieHellman = new DiffieHellman(env, args.This()); if (args.Length() != 1 || !args[0]->IsString()) { return ThrowError("No group name given"); @@ -3034,8 +3022,9 @@ void DiffieHellman::DiffieHellmanGroup( void DiffieHellman::New(const FunctionCallbackInfo& args) { HandleScope scope(node_isolate); + Environment* env = Environment::GetCurrent(args.GetIsolate()); DiffieHellman* diffieHellman = - new DiffieHellman(args.GetIsolate(), args.This()); + new DiffieHellman(env, args.This()); bool initialized = false; if (args.Length() > 0) { @@ -3286,74 +3275,141 @@ bool DiffieHellman::VerifyContext() { } -// TODO(bnoordhuis) Turn into proper RAII class. -struct pbkdf2_req { - uv_work_t work_req; - Environment* env; - int err; - char* pass; - size_t passlen; - char* salt; - size_t saltlen; - size_t iter; - char* key; - size_t keylen; - Persistent obj; +class PBKDF2Request : public AsyncWrap { + public: + PBKDF2Request(Environment* env, + Local object, + ssize_t passlen, + char* pass, + ssize_t saltlen, + char* salt, + ssize_t iter, + ssize_t keylen) + : AsyncWrap(env, object), + error_(0), + passlen_(passlen), + pass_(pass), + saltlen_(saltlen), + salt_(salt), + keylen_(keylen), + key_(static_cast(malloc(keylen))), + iter_(iter) { + if (key() == NULL) + FatalError("node::PBKDF2Request()", "Out of Memory"); + } + + ~PBKDF2Request() { + persistent().Dispose(); + } + + uv_work_t* work_req() { + return &work_req_; + } + + inline ssize_t passlen() const { + return passlen_; + } + + inline char* pass() const { + return pass_; + } + + inline ssize_t saltlen() const { + return saltlen_; + } + + inline char* salt() const { + return salt_; + } + + inline ssize_t keylen() const { + return keylen_; + } + + inline char* key() const { + return key_; + } + + inline ssize_t iter() const { + return iter_; + } + + inline void release() { + free(pass_); + passlen_ = 0; + free(salt_); + saltlen_ = 0; + free(key_); + keylen_ = 0; + } + + inline int error() const { + return error_; + } + + inline void set_error(int err) { + error_ = err; + } + + // TODO(trevnorris): Make private and make work with container_of macro. + uv_work_t work_req_; + + private: + int error_; + ssize_t passlen_; + char* pass_; + ssize_t saltlen_; + char* salt_; + ssize_t keylen_; + char* key_; + ssize_t iter_; }; -void EIO_PBKDF2(pbkdf2_req* req) { - req->err = PKCS5_PBKDF2_HMAC_SHA1( - req->pass, - req->passlen, - (unsigned char*)req->salt, - req->saltlen, - req->iter, - req->keylen, - (unsigned char*)req->key); - memset(req->pass, 0, req->passlen); - memset(req->salt, 0, req->saltlen); +void EIO_PBKDF2(PBKDF2Request* req) { + req->set_error(PKCS5_PBKDF2_HMAC_SHA1( + req->pass(), + req->passlen(), + reinterpret_cast(req->salt()), + req->saltlen(), + req->iter(), + req->keylen(), + reinterpret_cast(req->key()))); + memset(req->pass(), 0, req->passlen()); + memset(req->salt(), 0, req->saltlen()); } void EIO_PBKDF2(uv_work_t* work_req) { - pbkdf2_req* req = container_of(work_req, pbkdf2_req, work_req); + PBKDF2Request* req = container_of(work_req, PBKDF2Request, work_req_); EIO_PBKDF2(req); } -void EIO_PBKDF2After(pbkdf2_req* req, Local argv[2]) { - if (req->err) { +void EIO_PBKDF2After(PBKDF2Request* req, Local argv[2]) { + if (req->error()) { argv[0] = Undefined(node_isolate); - argv[1] = Encode(req->key, req->keylen, BUFFER); - memset(req->key, 0, req->keylen); + argv[1] = Encode(req->key(), req->keylen(), BUFFER); + memset(req->key(), 0, req->keylen()); } else { argv[0] = Exception::Error( FIXED_ONE_BYTE_STRING(node_isolate, "PBKDF2 error")); argv[1] = Undefined(node_isolate); } - - delete[] req->pass; - delete[] req->salt; - delete[] req->key; - delete req; } void EIO_PBKDF2After(uv_work_t* work_req, int status) { assert(status == 0); - pbkdf2_req* req = container_of(work_req, pbkdf2_req, work_req); - Environment* env = req->env; + PBKDF2Request* req = container_of(work_req, PBKDF2Request, work_req_); + Environment* env = req->env(); Context::Scope context_scope(env->context()); HandleScope handle_scope(env->isolate()); - // Create a new Local that's associated with the current HandleScope. - // PersistentToLocal() returns a handle that gets zeroed when we call - // Dispose() so don't use that. - Local obj = Local::New(node_isolate, req->obj); - req->obj.Dispose(); Local argv[2]; EIO_PBKDF2After(req, argv); - MakeCallback(env, obj, "ondone", ARRAY_SIZE(argv), argv); + req->MakeCallback(env->ondone_string(), ARRAY_SIZE(argv), argv); + req->release(); + delete req; } @@ -3370,7 +3426,8 @@ void PBKDF2(const FunctionCallbackInfo& args) { ssize_t pass_written = -1; ssize_t salt_written = -1; ssize_t iter = -1; - pbkdf2_req* req = NULL; + PBKDF2Request* req = NULL; + Local obj; if (args.Length() != 4 && args.Length() != 5) { type_error = "Bad parameter"; @@ -3384,7 +3441,10 @@ void PBKDF2(const FunctionCallbackInfo& args) { goto err; } - pass = new char[passlen]; + pass = static_cast(malloc(passlen)); + if (pass == NULL) { + FatalError("node::PBKDF2()", "Out of Memory"); + } pass_written = DecodeWrite(pass, passlen, args[0], BINARY); assert(pass_written == passlen); @@ -3395,7 +3455,10 @@ void PBKDF2(const FunctionCallbackInfo& args) { goto err; } - salt = new char[saltlen]; + salt = static_cast(malloc(saltlen)); + if (salt == NULL) { + FatalError("node::PBKDF2()", "Out of Memory"); + } salt_written = DecodeWrite(salt, saltlen, args[1], BINARY); assert(salt_written == saltlen); @@ -3421,26 +3484,13 @@ void PBKDF2(const FunctionCallbackInfo& args) { goto err; } - req = new pbkdf2_req; - req->env = env; - req->err = 0; - req->pass = pass; - req->passlen = passlen; - req->salt = salt; - req->saltlen = saltlen; - req->iter = iter; - req->key = new char[keylen]; - req->keylen = keylen; + obj = Object::New(); + req = new PBKDF2Request(env, obj, passlen, pass, saltlen, salt, iter, keylen); if (args[4]->IsFunction()) { - Local obj = Object::New(); - obj->Set(FIXED_ONE_BYTE_STRING(node_isolate, "ondone"), args[4]); - if (env->in_domain()) { - obj->Set(env->domain_string(), env->domain_array()->Get(0)); - } - req->obj.Reset(node_isolate, obj); + obj->Set(env->ondone_string(), args[4]); uv_queue_work(env->event_loop(), - &req->work_req, + req->work_req(), EIO_PBKDF2, EIO_PBKDF2After); } else { @@ -3455,29 +3505,70 @@ void PBKDF2(const FunctionCallbackInfo& args) { return; err: - delete[] salt; - delete[] pass; + free(salt); + free(pass); return ThrowTypeError(type_error); } -// TODO(bnoordhuis) Turn into proper RAII class. -struct RandomBytesRequest { - ~RandomBytesRequest(); - Environment* env_; - Persistent obj_; - unsigned long error_; // openssl error code or zero +// Only instantiate within a valid HandleScope. +class RandomBytesRequest : public AsyncWrap { + public: + RandomBytesRequest(Environment* env, Local object, size_t size) + : AsyncWrap(env, object), + error_(0), + size_(size), + data_(static_cast(malloc(size))) { + if (data() == NULL) + FatalError("node::RandomBytesRequest()", "Out of Memory"); + } + + ~RandomBytesRequest() { + persistent().Dispose(); + } + + uv_work_t* work_req() { + return &work_req_; + } + + inline size_t size() const { + return size_; + } + + inline char* data() const { + return data_; + } + + inline void release() { + free(data_); + size_ = 0; + } + + inline void return_memory(char** d, size_t* len) { + *d = data_; + data_ = NULL; + *len = size_; + size_ = 0; + } + + inline unsigned long error() const { + return error_; + } + + inline void set_error(unsigned long err) { + error_ = err; + } + + // TODO(trevnorris): Make private and make work with container_of macro. uv_work_t work_req_; + + private: + unsigned long error_; size_t size_; char* data_; }; -RandomBytesRequest::~RandomBytesRequest() { - obj_.Dispose(); -} - - template void RandomBytesWork(uv_work_t* work_req) { RandomBytesRequest* req = container_of(work_req, @@ -3486,38 +3577,40 @@ void RandomBytesWork(uv_work_t* work_req) { int r; if (pseudoRandom == true) { - r = RAND_pseudo_bytes(reinterpret_cast(req->data_), - req->size_); + r = RAND_pseudo_bytes(reinterpret_cast(req->data()), + req->size()); } else { - r = RAND_bytes(reinterpret_cast(req->data_), req->size_); + r = RAND_bytes(reinterpret_cast(req->data()), req->size()); } // RAND_bytes() returns 0 on error. RAND_pseudo_bytes() returns 0 when the // result is not cryptographically strong - but that's not an error. if (r == 0 && pseudoRandom == false) { - req->error_ = ERR_get_error(); + req->set_error(ERR_get_error()); } else if (r == -1) { - req->error_ = static_cast(-1); + req->set_error(static_cast(-1)); } } // don't call this function without a valid HandleScope void RandomBytesCheck(RandomBytesRequest* req, Local argv[2]) { - if (req->error_) { + if (req->error()) { char errmsg[256] = "Operation not supported"; - if (req->error_ != static_cast(-1)) - ERR_error_string_n(req->error_, errmsg, sizeof errmsg); + if (req->error() != static_cast(-1)) + ERR_error_string_n(req->error(), errmsg, sizeof errmsg); argv[0] = Exception::Error(OneByteString(node_isolate, errmsg)); argv[1] = Null(node_isolate); + req->release(); } else { + char* data = NULL; + size_t size; + req->return_memory(&data, &size); argv[0] = Null(node_isolate); - argv[1] = Buffer::Use(req->data_, req->size_); - req->data_ = NULL; + argv[1] = Buffer::Use(data, size); } - free(req->data_); } @@ -3526,13 +3619,12 @@ void RandomBytesAfter(uv_work_t* work_req, int status) { RandomBytesRequest* req = container_of(work_req, RandomBytesRequest, work_req_); - Environment* env = req->env_; + Environment* env = req->env(); Context::Scope context_scope(env->context()); HandleScope handle_scope(env->isolate()); Local argv[2]; RandomBytesCheck(req, argv); - Local obj = PersistentToLocal(node_isolate, req->obj_); - MakeCallback(env, obj, "ondone", ARRAY_SIZE(argv), argv); + req->MakeCallback(env->ondone_string(), ARRAY_SIZE(argv), argv); delete req; } @@ -3553,34 +3645,19 @@ void RandomBytes(const FunctionCallbackInfo& args) { return ThrowTypeError("size > Buffer::kMaxLength"); } - RandomBytesRequest* req = new RandomBytesRequest(); - req->env_ = env; - req->error_ = 0; - req->size_ = size; - req->data_ = static_cast(malloc(size)); - - if (req->data_ == NULL) { - delete req; - V8::LowMemoryNotification(); - return ThrowError("Out of memory"); - } + Local obj = Object::New(); + RandomBytesRequest* req = new RandomBytesRequest(env, obj, size); if (args[1]->IsFunction()) { - Local obj = Object::New(); obj->Set(FIXED_ONE_BYTE_STRING(node_isolate, "ondone"), args[1]); - if (env->in_domain()) { - obj->Set(env->domain_string(), env->domain_array()->Get(0)); - } - req->obj_.Reset(node_isolate, obj); - uv_queue_work(env->event_loop(), - &req->work_req_, + req->work_req(), RandomBytesWork, RandomBytesAfter); args.GetReturnValue().Set(obj); } else { Local argv[2]; - RandomBytesWork(&req->work_req_); + RandomBytesWork(req->work_req()); RandomBytesCheck(req, argv); delete req; @@ -3664,7 +3741,8 @@ void Certificate::Initialize(Handle target) { void Certificate::New(const FunctionCallbackInfo& args) { - new Certificate(args.GetIsolate(), args.This()); + Environment* env = Environment::GetCurrent(args.GetIsolate()); + new Certificate(env, args.This()); } diff --git a/src/node_crypto.h b/src/node_crypto.h index 90483b7dbf1c..48c842114738 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -63,10 +63,6 @@ class SecureContext : public WeakObject { public: static void Initialize(Environment* env, v8::Handle target); - inline Environment* env() const { - return env_; - } - X509_STORE* ca_store_; SSL_CTX* ctx_; @@ -93,10 +89,9 @@ class SecureContext : public WeakObject { static void SetTicketKeys(const v8::FunctionCallbackInfo& args); SecureContext(Environment* env, v8::Local wrap) - : WeakObject(env->isolate(), wrap), + : WeakObject(env, wrap), ca_store_(NULL), - ctx_(NULL), - env_(env) { + ctx_(NULL) { } void FreeCTXMem() { @@ -119,9 +114,6 @@ class SecureContext : public WeakObject { ~SecureContext() { FreeCTXMem(); } - - private: - Environment* const env_; }; template @@ -202,7 +194,7 @@ class SSLWrap { void* arg); #endif // OPENSSL_NPN_NEGOTIATED - inline Environment* env() const { + inline Environment* ssl_env() const { return env_; } @@ -279,7 +271,7 @@ class Connection : public SSLWrap, public WeakObject { SecureContext* sc, SSLWrap::Kind kind) : SSLWrap(env, sc, kind), - WeakObject(env->isolate(), wrap), + WeakObject(env, wrap), bio_read_(NULL), bio_write_(NULL), hello_offset_(0) { @@ -337,10 +329,10 @@ class CipherBase : public WeakObject { static void Final(const v8::FunctionCallbackInfo& args); static void SetAutoPadding(const v8::FunctionCallbackInfo& args); - CipherBase(v8::Isolate* isolate, + CipherBase(Environment* env, v8::Local wrap, CipherKind kind) - : WeakObject(isolate, wrap), + : WeakObject(env, wrap), cipher_(NULL), initialised_(false), kind_(kind) { @@ -373,8 +365,8 @@ class Hmac : public WeakObject { static void HmacUpdate(const v8::FunctionCallbackInfo& args); static void HmacDigest(const v8::FunctionCallbackInfo& args); - Hmac(v8::Isolate* isolate, v8::Local wrap) - : WeakObject(isolate, wrap), + Hmac(Environment* env, v8::Local wrap) + : WeakObject(env, wrap), md_(NULL), initialised_(false) { } @@ -403,8 +395,8 @@ class Hash : public WeakObject { static void HashUpdate(const v8::FunctionCallbackInfo& args); static void HashDigest(const v8::FunctionCallbackInfo& args); - Hash(v8::Isolate* isolate, v8::Local wrap) - : WeakObject(isolate, wrap), + Hash(Environment* env, v8::Local wrap) + : WeakObject(env, wrap), md_(NULL), initialised_(false) { } @@ -439,8 +431,8 @@ class Sign : public WeakObject { static void SignUpdate(const v8::FunctionCallbackInfo& args); static void SignFinal(const v8::FunctionCallbackInfo& args); - Sign(v8::Isolate* isolate, v8::Local wrap) - : WeakObject(isolate, wrap), + Sign(Environment* env, v8::Local wrap) + : WeakObject(env, wrap), md_(NULL), initialised_(false) { } @@ -474,8 +466,8 @@ class Verify : public WeakObject { static void VerifyUpdate(const v8::FunctionCallbackInfo& args); static void VerifyFinal(const v8::FunctionCallbackInfo& args); - Verify(v8::Isolate* isolate, v8::Local wrap) - : WeakObject(isolate, wrap), + Verify(Environment* env, v8::Local wrap) + : WeakObject(env, wrap), md_(NULL), initialised_(false) { } @@ -513,8 +505,8 @@ class DiffieHellman : public WeakObject { static void SetPublicKey(const v8::FunctionCallbackInfo& args); static void SetPrivateKey(const v8::FunctionCallbackInfo& args); - DiffieHellman(v8::Isolate* isolate, v8::Local wrap) - : WeakObject(isolate, wrap), + DiffieHellman(Environment* env, v8::Local wrap) + : WeakObject(env, wrap), initialised_(false), dh(NULL) { } @@ -547,8 +539,8 @@ class Certificate : public WeakObject { static void ExportPublicKey(const v8::FunctionCallbackInfo& args); static void ExportChallenge(const v8::FunctionCallbackInfo& args); - Certificate(v8::Isolate* isolate, v8::Local wrap) - : WeakObject(isolate, wrap) { + Certificate(Environment* env, v8::Local wrap) + : WeakObject(env, wrap) { } }; diff --git a/src/node_file.cc b/src/node_file.cc index cb37124c1a06..5650e4cc2d32 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -67,7 +67,7 @@ using v8::Value; class FSReqWrap: public ReqWrap { public: FSReqWrap(Environment* env, const char* syscall, char* data = NULL) - : ReqWrap(env), + : ReqWrap(env, Object::New()), syscall_(syscall), data_(data) { } @@ -214,7 +214,7 @@ static void After(uv_fs_t *req) { } } - MakeCallback(env, req_wrap->object(), env->oncomplete_string(), argc, argv); + req_wrap->MakeCallback(env->oncomplete_string(), argc, argv); uv_fs_req_cleanup(&req_wrap->req_); delete req_wrap; diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index cc04fabe2dc9..10378eef3606 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -166,8 +166,7 @@ struct StringPtr { class Parser : public WeakObject { public: Parser(Environment* env, Local wrap, enum http_parser_type type) - : WeakObject(env->isolate(), wrap), - env_(env), + : WeakObject(env, wrap), current_buffer_len_(0), current_buffer_data_(NULL) { Init(type); @@ -230,7 +229,7 @@ class Parser : public WeakObject { HTTP_CB(on_headers_complete) { - Local obj = weak_object(node_isolate); + Local obj = object(); Local cb = obj->Get(kOnHeadersComplete); if (!cb->IsFunction()) @@ -291,7 +290,7 @@ class Parser : public WeakObject { HTTP_DATA_CB(on_body) { HandleScope scope(node_isolate); - Local obj = weak_object(node_isolate); + Local obj = object(); Local cb = obj->Get(kOnBody); if (!cb->IsFunction()) @@ -320,7 +319,7 @@ class Parser : public WeakObject { if (num_fields_) Flush(); // Flush trailing HTTP headers. - Local obj = weak_object(node_isolate); + Local obj = object(); Local cb = obj->Get(kOnMessageComplete); if (!cb->IsFunction()) @@ -491,7 +490,7 @@ class Parser : public WeakObject { void Flush() { HandleScope scope(node_isolate); - Local obj = weak_object(node_isolate); + Local obj = object(); Local cb = obj->Get(kOnHeaders); if (!cb->IsFunction()) @@ -522,12 +521,6 @@ class Parser : public WeakObject { } - inline Environment* env() const { - return env_; - } - - - Environment* const env_; http_parser parser_; StringPtr fields_[32]; // header fields StringPtr values_[32]; // header values diff --git a/src/node_internals.h b/src/node_internals.h index a4847792432c..84c523b2950a 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -49,7 +49,7 @@ inline v8::Local PersistentToLocal( // Call with valid HandleScope and while inside Context scope. v8::Handle MakeCallback(Environment* env, - const v8::Handle object, + v8::Handle object, const char* method, int argc = 0, v8::Handle* argv = NULL); diff --git a/src/node_stat_watcher.cc b/src/node_stat_watcher.cc index 6d8e61b2ff76..b5a04c1f9474 100644 --- a/src/node_stat_watcher.cc +++ b/src/node_stat_watcher.cc @@ -66,9 +66,8 @@ static void Delete(uv_handle_t* handle) { StatWatcher::StatWatcher(Environment* env, Local wrap) - : WeakObject(env->isolate(), wrap), - watcher_(new uv_fs_poll_t), - env_(env) { + : WeakObject(env, wrap), + watcher_(new uv_fs_poll_t) { uv_fs_poll_init(env->event_loop(), watcher_); watcher_->data = static_cast(this); } @@ -94,11 +93,7 @@ void StatWatcher::Callback(uv_fs_poll_t* handle, BuildStatsObject(env, prev), Integer::New(status, node_isolate) }; - MakeCallback(env, - wrap->weak_object(node_isolate), - env->onchange_string(), - ARRAY_SIZE(argv), - argv); + wrap->MakeCallback(env->onchange_string(), ARRAY_SIZE(argv), argv); } @@ -131,7 +126,7 @@ void StatWatcher::Stop(const FunctionCallbackInfo& args) { Environment* env = wrap->env(); Context::Scope context_scope(env->context()); HandleScope handle_scope(env->isolate()); - MakeCallback(env, wrap->weak_object(node_isolate), env->onstop_string()); + wrap->MakeCallback(env->onstop_string(), 0, NULL); wrap->Stop(); } diff --git a/src/node_stat_watcher.h b/src/node_stat_watcher.h index 233c137baa0a..780b2baf7e1a 100644 --- a/src/node_stat_watcher.h +++ b/src/node_stat_watcher.h @@ -33,7 +33,6 @@ namespace node { class StatWatcher : public WeakObject { public: static void Initialize(v8::Handle target); - inline Environment* env() const { return env_; } protected: StatWatcher(Environment* env, v8::Local wrap); @@ -51,7 +50,6 @@ class StatWatcher : public WeakObject { void Stop(); uv_fs_poll_t* watcher_; - Environment* const env_; }; } // namespace node diff --git a/src/node_zlib.cc b/src/node_zlib.cc index e2b37d86b1c9..aed93fe7c64e 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -73,11 +73,10 @@ class ZCtx : public WeakObject { public: ZCtx(Environment* env, Local wrap, node_zlib_mode mode) - : WeakObject(env->isolate(), wrap), + : WeakObject(env, wrap), chunk_size_(0), dictionary_(NULL), dictionary_len_(0), - env_(env), err_(0), flush_(0), init_done_(false), @@ -95,11 +94,6 @@ class ZCtx : public WeakObject { Close(); } - - inline Environment* env() const { - return env_; - } - void Close() { assert(!write_in_progress_ && "write in progress"); assert(init_done_ && "close before init"); @@ -200,7 +194,7 @@ class ZCtx : public WeakObject { ZCtx::Process, ZCtx::After); - args.GetReturnValue().Set(ctx->weak_object(node_isolate)); + args.GetReturnValue().Set(ctx->object()); } @@ -290,9 +284,8 @@ class ZCtx : public WeakObject { ctx->write_in_progress_ = false; // call the write() cb - Local handle = ctx->weak_object(node_isolate); Local args[2] = { avail_in, avail_out }; - MakeCallback(env, handle, env->callback_string(), ARRAY_SIZE(args), args); + ctx->MakeCallback(env->callback_string(), ARRAY_SIZE(args), args); ctx->Unref(); } @@ -307,13 +300,12 @@ class ZCtx : public WeakObject { message = ctx->strm_.msg; } - Local handle = ctx->weak_object(node_isolate); HandleScope scope(node_isolate); Local args[2] = { OneByteString(node_isolate, message), Number::New(ctx->err_) }; - MakeCallback(env, handle, env->onerror_string(), ARRAY_SIZE(args), args); + ctx->MakeCallback(env->onerror_string(), ARRAY_SIZE(args), args); // no hope of rescue. ctx->write_in_progress_ = false; @@ -540,7 +532,6 @@ class ZCtx : public WeakObject { int chunk_size_; Bytef* dictionary_; size_t dictionary_len_; - Environment* const env_; int err_; int flush_; bool init_done_; diff --git a/src/pipe_wrap.cc b/src/pipe_wrap.cc index 08ab7190bdfe..a5227757435b 100644 --- a/src/pipe_wrap.cc +++ b/src/pipe_wrap.cc @@ -111,6 +111,8 @@ void PipeWrap::Initialize(Handle target, NODE_SET_PROTOTYPE_METHOD(t, "setPendingInstances", SetPendingInstances); #endif + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "Pipe"), t->GetFunction()); env->set_pipe_constructor_template(t); } @@ -192,11 +194,7 @@ void PipeWrap::OnConnection(uv_stream_t* handle, int status) { }; if (status != 0) { - MakeCallback(env, - pipe_wrap->object(), - env->onconnection_string(), - ARRAY_SIZE(argv), - argv); + pipe_wrap->MakeCallback(env->onconnection_string(), ARRAY_SIZE(argv), argv); return; } @@ -212,11 +210,7 @@ void PipeWrap::OnConnection(uv_stream_t* handle, int status) { // Successful accept. Call the onconnection callback in JavaScript land. argv[1] = client_obj; - MakeCallback(env, - pipe_wrap->object(), - env->onconnection_string(), - ARRAY_SIZE(argv), - argv); + pipe_wrap->MakeCallback(env->onconnection_string(), ARRAY_SIZE(argv), argv); } // TODO(bnoordhuis) Maybe share this with TCPWrap? @@ -251,11 +245,7 @@ void PipeWrap::AfterConnect(uv_connect_t* req, int status) { Boolean::New(writable) }; - MakeCallback(env, - req_wrap_obj, - env->oncomplete_string(), - ARRAY_SIZE(argv), - argv); + req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv); delete req_wrap; } diff --git a/src/process_wrap.cc b/src/process_wrap.cc index ba09745eaa31..7b8125cebab5 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -284,11 +284,7 @@ class ProcessWrap : public HandleWrap { OneByteString(node_isolate, signo_string(term_signal)) }; - MakeCallback(env, - wrap->object(), - env->onexit_string(), - ARRAY_SIZE(argv), - argv); + wrap->MakeCallback(env->onexit_string(), ARRAY_SIZE(argv), argv); } uv_process_t process_; diff --git a/src/req_wrap.h b/src/req_wrap.h index 8a701e5158e4..2e08ef738025 100644 --- a/src/req_wrap.h +++ b/src/req_wrap.h @@ -22,6 +22,8 @@ #ifndef SRC_REQ_WRAP_H_ #define SRC_REQ_WRAP_H_ +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "env.h" #include "env-inl.h" #include "queue.h" @@ -33,22 +35,10 @@ namespace node { extern QUEUE req_wrap_queue; template -class ReqWrap { +class ReqWrap : public AsyncWrap { public: - ReqWrap(Environment* env, - v8::Handle object = v8::Handle()) - : env_(env) { - v8::HandleScope handle_scope(env->isolate()); - - if (object.IsEmpty()) { - object = v8::Object::New(); - } - persistent().Reset(env->isolate(), object); - - if (env->in_domain()) { - object->Set(env->domain_string(), env->domain_array()->Get(0)); - } - + ReqWrap(Environment* env, v8::Handle object) + : AsyncWrap(env, object) { QUEUE_INSERT_TAIL(&req_wrap_queue, &req_wrap_queue_); } @@ -66,25 +56,9 @@ class ReqWrap { req_.data = this; } - inline Environment* env() const { - return env_; - } - - inline v8::Local object() { - return PersistentToLocal(env()->isolate(), persistent()); - } - - inline v8::Persistent& persistent() { - return object_; - } - // TODO(bnoordhuis) Make these private. QUEUE req_wrap_queue_; T req_; // *must* be last, GetActiveRequests() in node.cc depends on it - - private: - v8::Persistent object_; - Environment* const env_; }; diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc index 3a1e5bf86370..b0f16196fae1 100644 --- a/src/signal_wrap.cc +++ b/src/signal_wrap.cc @@ -19,6 +19,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "env.h" #include "env-inl.h" #include "handle_wrap.h" @@ -100,8 +102,9 @@ class SignalWrap : public HandleWrap { Environment* env = wrap->env(); Context::Scope context_scope(env->context()); HandleScope handle_scope(env->isolate()); + Local arg = Integer::New(signum, env->isolate()); - MakeCallback(env, wrap->object(), env->onsignal_string(), 1, &arg); + wrap->MakeCallback(env->onsignal_string(), 1, &arg); } uv_signal_t handle_; diff --git a/src/stream_wrap.cc b/src/stream_wrap.cc index f98fcded0e6b..89c1a3ee128b 100644 --- a/src/stream_wrap.cc +++ b/src/stream_wrap.cc @@ -451,11 +451,7 @@ void StreamWrap::AfterWrite(uv_write_t* req, int status) { req_wrap_obj }; - MakeCallback(env, - req_wrap_obj, - env->oncomplete_string(), - ARRAY_SIZE(argv), - argv); + req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv); req_wrap->~WriteWrap(); delete[] reinterpret_cast(req_wrap); @@ -499,11 +495,7 @@ void StreamWrap::AfterShutdown(uv_shutdown_t* req, int status) { req_wrap_obj }; - MakeCallback(env, - req_wrap_obj, - env->oncomplete_string(), - ARRAY_SIZE(argv), - argv); + req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv); delete req_wrap; } @@ -574,7 +566,7 @@ void StreamWrapCallbacks::DoRead(uv_stream_t* handle, if (nread < 0) { if (buf->base != NULL) free(buf->base); - MakeCallback(env, Self(), env->onread_string(), ARRAY_SIZE(argv), argv); + wrap()->MakeCallback(env->onread_string(), ARRAY_SIZE(argv), argv); return; } @@ -603,11 +595,7 @@ void StreamWrapCallbacks::DoRead(uv_stream_t* handle, argv[2] = pending_obj; } - MakeCallback(env, - wrap()->object(), - env->onread_string(), - ARRAY_SIZE(argv), - argv); + wrap()->MakeCallback(env->onread_string(), ARRAY_SIZE(argv), argv); } @@ -615,9 +603,4 @@ int StreamWrapCallbacks::DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb) { return uv_shutdown(&req_wrap->req_, wrap()->stream(), cb); } - -Handle StreamWrapCallbacks::Self() { - return wrap()->object(); -} - } // namespace node diff --git a/src/stream_wrap.h b/src/stream_wrap.h index 374e7c75cea8..ed8c53ebe87e 100644 --- a/src/stream_wrap.h +++ b/src/stream_wrap.h @@ -88,8 +88,6 @@ class StreamWrapCallbacks { uv_handle_type pending); virtual int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb); - v8::Handle Self(); - protected: inline StreamWrap* wrap() const { return wrap_; diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index 840dd9207764..85fff070a66d 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -116,6 +116,8 @@ void TCPWrap::Initialize(Handle target, SetSimultaneousAccepts); #endif + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "TCP"), t->GetFunction()); env->set_tcp_constructor_template(t); } @@ -321,11 +323,7 @@ void TCPWrap::OnConnection(uv_stream_t* handle, int status) { argv[1] = client_obj; } - MakeCallback(env, - tcp_wrap->object(), - env->onconnection_string(), - ARRAY_SIZE(argv), - argv); + tcp_wrap->MakeCallback(env->onconnection_string(), ARRAY_SIZE(argv), argv); } @@ -350,11 +348,8 @@ void TCPWrap::AfterConnect(uv_connect_t* req, int status) { v8::True(node_isolate), v8::True(node_isolate) }; - MakeCallback(env, - req_wrap_obj, - env->oncomplete_string(), - ARRAY_SIZE(argv), - argv); + + req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv); delete req_wrap; } diff --git a/src/timer_wrap.cc b/src/timer_wrap.cc index d6e32bdedd18..393def3d5940 100644 --- a/src/timer_wrap.cc +++ b/src/timer_wrap.cc @@ -19,6 +19,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "env.h" #include "env-inl.h" #include "handle_wrap.h" @@ -138,7 +140,7 @@ class TimerWrap : public HandleWrap { Context::Scope context_scope(env->context()); HandleScope handle_scope(env->isolate()); Local argv[1] = { Integer::New(status, node_isolate) }; - MakeCallback(env, wrap->object(), kOnTimeout, ARRAY_SIZE(argv), argv); + wrap->MakeCallback(kOnTimeout, ARRAY_SIZE(argv), argv); } static void Now(const FunctionCallbackInfo& args) { diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index c95c5007ebe8..85758814821a 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -20,6 +20,8 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "tls_wrap.h" +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "node_buffer.h" // Buffer #include "node_crypto.h" // SecureContext #include "node_crypto_bio.h" // NodeBIO @@ -62,6 +64,7 @@ TLSCallbacks::TLSCallbacks(Environment* env, StreamWrapCallbacks* old) : SSLWrap(env, Unwrap(sc), kind), StreamWrapCallbacks(old), + AsyncWrap(env, env->tls_wrap_constructor_function()->NewInstance()), enc_in_(NULL), enc_out_(NULL), clear_in_(NULL), @@ -75,9 +78,7 @@ TLSCallbacks::TLSCallbacks(Environment* env, sc_ = Unwrap(sc); sc_handle_.Reset(node_isolate, sc); - Local object = env->tls_wrap_constructor_function()->NewInstance(); - persistent().Reset(node_isolate, object); - node::Wrap(object, this); + node::Wrap(object(), this); // Initialize queue for clearIn writes QUEUE_INIT(&write_item_queue_); @@ -237,12 +238,12 @@ void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) { // There should be a Context::Scope a few stack frames down. assert(env->context() == env->isolate()->GetCurrentContext()); HandleScope handle_scope(env->isolate()); - Local object = c->weak_object(env->isolate()); + Local object = c->object(); if (where & SSL_CB_HANDSHAKE_START) { Local callback = object->Get(env->onhandshakestart_string()); if (callback->IsFunction()) { - MakeCallback(env, object, callback.As()); + c->MakeCallback(callback.As(), 0, NULL); } } @@ -250,7 +251,7 @@ void TLSCallbacks::SSLInfoCallback(const SSL* ssl_, int where, int ret) { c->established_ = true; Local callback = object->Get(env->onhandshakedone_string()); if (callback->IsFunction()) { - MakeCallback(env, object, callback.As()); + c->MakeCallback(callback.As(), 0, NULL); } } } @@ -313,11 +314,7 @@ void TLSCallbacks::EncOutCb(uv_write_t* req, int status) { Local arg = String::Concat( FIXED_ONE_BYTE_STRING(node_isolate, "write cb error, status: "), Integer::New(status, node_isolate)->ToString()); - MakeCallback(env, - callbacks->weak_object(node_isolate), - env->onerror_string(), - 1, - &arg); + callbacks->MakeCallback(env->onerror_string(), 1, &arg); callbacks->InvokeQueued(status); return; } @@ -385,24 +382,16 @@ void TLSCallbacks::ClearOut() { Integer::New(read, node_isolate), Buffer::New(env(), out, read) }; - MakeCallback(env(), - Self(), - env()->onread_string(), - ARRAY_SIZE(argv), - argv); + wrap()->MakeCallback(env()->onread_string(), ARRAY_SIZE(argv), argv); } } while (read > 0); if (read == -1) { int err; - Handle argv = GetSSLError(read, &err); - - if (!argv.IsEmpty()) { - MakeCallback(env(), - weak_object(node_isolate), - env()->onerror_string(), - 1, - &argv); + Handle arg = GetSSLError(read, &err); + + if (!arg.IsEmpty()) { + MakeCallback(env()->onerror_string(), 1, &arg); } } } @@ -435,13 +424,9 @@ bool TLSCallbacks::ClearIn() { // Error or partial write int err; - Handle argv = GetSSLError(written, &err); - if (!argv.IsEmpty()) { - MakeCallback(env(), - weak_object(node_isolate), - env()->onerror_string(), - 1, - &argv); + Handle arg = GetSSLError(written, &err); + if (!arg.IsEmpty()) { + MakeCallback(env()->onerror_string(), 1, &arg); } return false; @@ -502,13 +487,9 @@ int TLSCallbacks::DoWrite(WriteWrap* w, int err; Context::Scope context_scope(env()->context()); HandleScope handle_scope(env()->isolate()); - Handle argv = GetSSLError(written, &err); - if (!argv.IsEmpty()) { - MakeCallback(env(), - weak_object(node_isolate), - env()->onerror_string(), - 1, - &argv); + Handle arg = GetSSLError(written, &err); + if (!arg.IsEmpty()) { + MakeCallback(env()->onerror_string(), 1, &arg); return -1; } @@ -547,7 +528,7 @@ void TLSCallbacks::DoRead(uv_stream_t* handle, Context::Scope context_scope(env()->context()); HandleScope handle_scope(env()->isolate()); Local arg = Integer::New(nread, node_isolate); - MakeCallback(env(), Self(), env()->onread_string(), 1, &arg); + wrap()->MakeCallback(env()->onread_string(), 1, &arg); return; } @@ -685,7 +666,7 @@ int TLSCallbacks::SelectSNIContextCallback(SSL* s, int* ad, void* arg) { if (servername != NULL) { // Call the SNI callback and use its return value as context - Local object = p->weak_object(node_isolate); + Local object = p->object(); Local ctx = object->Get(env->sni_context_string()); if (!ctx->IsObject()) diff --git a/src/tls_wrap.h b/src/tls_wrap.h index ec82154230e3..924a7243c4a5 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -25,6 +25,7 @@ #include "node.h" #include "node_crypto.h" // SSLWrap +#include "async-wrap.h" #include "env.h" #include "queue.h" #include "stream_wrap.h" @@ -42,7 +43,8 @@ namespace crypto { } class TLSCallbacks : public crypto::SSLWrap, - public StreamWrapCallbacks { + public StreamWrapCallbacks, + public AsyncWrap { public: static void Initialize(v8::Handle target, v8::Handle unused, @@ -63,11 +65,6 @@ class TLSCallbacks : public crypto::SSLWrap, uv_handle_type pending); int DoShutdown(ShutdownWrap* req_wrap, uv_shutdown_cb cb); - // Just for compliance with Connection - inline v8::Local weak_object(v8::Isolate* isolate) { - return PersistentToLocal(isolate, persistent()); - } - protected: static const int kClearOutChunkSize = 1024; @@ -123,13 +120,8 @@ class TLSCallbacks : public crypto::SSLWrap, static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); #endif // SSL_CTRL_SET_TLSEXT_SERVERNAME_CB - inline v8::Persistent& persistent() { - return object_; - } - crypto::SecureContext* sc_; v8::Persistent sc_handle_; - v8::Persistent object_; BIO* enc_in_; BIO* enc_out_; NodeBIO* clear_in_; diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 3b5044968f11..5a16a55b4ec7 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -119,6 +119,9 @@ void UDPWrap::Initialize(Handle target, NODE_SET_PROTOTYPE_METHOD(t, "ref", HandleWrap::Ref); NODE_SET_PROTOTYPE_METHOD(t, "unref", HandleWrap::Unref); + + AsyncWrap::AddMethods(t); + target->Set(FIXED_ONE_BYTE_STRING(node_isolate, "UDP"), t->GetFunction()); env->set_udp_constructor_function(t->GetFunction()); } @@ -363,9 +366,8 @@ void UDPWrap::OnSend(uv_udp_send_t* req, int status) { Environment* env = req_wrap->env(); Context::Scope context_scope(env->context()); HandleScope handle_scope(env->isolate()); - Local req_wrap_obj = req_wrap->object(); Local arg = Integer::New(status, node_isolate); - MakeCallback(env, req_wrap_obj, env->oncomplete_string(), 1, &arg); + req_wrap->MakeCallback(env->oncomplete_string(), 1, &arg); } delete req_wrap; } @@ -405,25 +407,21 @@ void UDPWrap::OnRecv(uv_udp_t* handle, Local argv[] = { Integer::New(nread, node_isolate), wrap_obj, - Undefined(), - Undefined() + Undefined(env->isolate()), + Undefined(env->isolate()) }; if (nread < 0) { if (buf->base != NULL) free(buf->base); - MakeCallback(env, - wrap_obj, - env->onmessage_string(), - ARRAY_SIZE(argv), - argv); + wrap->MakeCallback(env->onmessage_string(), ARRAY_SIZE(argv), argv); return; } char* base = static_cast(realloc(buf->base, nread)); argv[2] = Buffer::Use(env, base, nread); argv[3] = AddressToJS(env, addr); - MakeCallback(env, wrap_obj, env->onmessage_string(), ARRAY_SIZE(argv), argv); + wrap->MakeCallback(env->onmessage_string(), ARRAY_SIZE(argv), argv); } diff --git a/src/weak-object-inl.h b/src/weak-object-inl.h index 2f55494ab665..c35cb9dbeeb3 100644 --- a/src/weak-object-inl.h +++ b/src/weak-object-inl.h @@ -23,14 +23,16 @@ #define SRC_WEAK_OBJECT_INL_H_ #include "weak-object.h" +#include "async-wrap.h" +#include "async-wrap-inl.h" #include "util.h" #include "util-inl.h" namespace node { -WeakObject::WeakObject(v8::Isolate* isolate, v8::Local object) - : weak_object_(isolate, object) { - weak_object_.MarkIndependent(); +WeakObject::WeakObject(Environment* env, v8::Local object) + : AsyncWrap(env, object) { + persistent().MarkIndependent(); // The pointer is resolved as void*. Wrap(object, this); @@ -40,16 +42,12 @@ WeakObject::WeakObject(v8::Isolate* isolate, v8::Local object) WeakObject::~WeakObject() { } -v8::Local WeakObject::weak_object(v8::Isolate* isolate) const { - return v8::Local::New(isolate, weak_object_); -} - void WeakObject::MakeWeak() { - weak_object_.MakeWeak(this, WeakCallback); + persistent().MakeWeak(this, WeakCallback); } void WeakObject::ClearWeak() { - weak_object_.ClearWeak(); + persistent().ClearWeak(); } void WeakObject::WeakCallback(v8::Isolate* isolate, @@ -57,7 +55,9 @@ void WeakObject::WeakCallback(v8::Isolate* isolate, WeakObject* self) { // Dispose now instead of in the destructor to avoid child classes that call // `delete this` in their destructor from blowing up. - persistent->Dispose(); + // Dispose the class member instead of the argument or else the IsEmpty() + // check in ~AsyncWrap will fail. + self->persistent().Dispose(); delete self; } diff --git a/src/weak-object.h b/src/weak-object.h index 39bf35fa7611..829d4128afaa 100644 --- a/src/weak-object.h +++ b/src/weak-object.h @@ -22,18 +22,17 @@ #ifndef SRC_WEAK_OBJECT_H_ #define SRC_WEAK_OBJECT_H_ +#include "async-wrap.h" +#include "env.h" #include "v8.h" namespace node { -class WeakObject { - public: - // Returns the wrapped object. Illegal to call in your destructor. - inline v8::Local weak_object(v8::Isolate* isolate) const; +class WeakObject : public AsyncWrap { protected: // |object| should be an instance of a v8::ObjectTemplate that has at least // one internal field reserved with v8::ObjectTemplate::SetInternalFieldCount. - inline WeakObject(v8::Isolate* isolate, v8::Local object); + inline WeakObject(Environment* env, v8::Local object); virtual inline ~WeakObject(); inline void MakeWeak(); inline void ClearWeak(); @@ -41,7 +40,6 @@ class WeakObject { inline static void WeakCallback(v8::Isolate* isolate, v8::Persistent* persistent, WeakObject* self); - v8::Persistent weak_object_; }; } // namespace node diff --git a/test/simple/test-asynclistener-error-add-after.js b/test/simple/test-asynclistener-error-add-after.js new file mode 100644 index 000000000000..81eae8ea66cb --- /dev/null +++ b/test/simple/test-asynclistener-error-add-after.js @@ -0,0 +1,165 @@ +// 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 dns = require('dns'); +var fs = require('fs'); +var net = require('net'); + +var errorMsgs = []; +var caught = 0; +var expectCaught = 0; +var exitCbRan = false; + +function asyncL() { } + +var callbacksObj = { + error: function(value, er) { + var idx = errorMsgs.indexOf(er.message); + caught++; + + process._rawDebug('Handling error: ' + er.message); + + if (-1 < idx) + errorMsgs.splice(idx, 1); + else + throw new Error('Message not found: ' + er.message); + + return true; + } +}; + +var listener = process.createAsyncListener(asyncL, callbacksObj); + +process.on('exit', function(code) { + // Just in case. + process.removeAsyncListener(listener); + + if (code > 0) + return; + + assert.ok(!exitCbRan); + exitCbRan = true; + + if (errorMsgs.length > 0) + throw new Error('Errors not fired: ' + errorMsgs); + + assert.equal(caught, expectCaught); + process._rawDebug('ok'); +}); + + +// Simple cases +errorMsgs.push('setTimeout - simple'); +errorMsgs.push('setImmediate - simple'); +errorMsgs.push('setInterval - simple'); +setTimeout(function() { + throw new Error('setTimeout - simple'); +}).addAsyncListener(listener); +expectCaught++; + +setImmediate(function() { + throw new Error('setImmediate - simple'); +}).addAsyncListener(listener); +expectCaught++; + +var b = setInterval(function() { + clearInterval(b); + throw new Error('setInterval - simple'); +}).addAsyncListener(listener); +expectCaught++; + + +// Deeply nested +errorMsgs.push('setInterval - nested'); +errorMsgs.push('setImmediate - nested'); +errorMsgs.push('setTimeout - nested'); +setTimeout(function() { + setImmediate(function() { + var b = setInterval(function() { + clearInterval(b); + throw new Error('setInterval - nested'); + }).addAsyncListener(listener); + expectCaught++; + throw new Error('setImmediate - nested'); + }).addAsyncListener(listener); + expectCaught++; + throw new Error('setTimeout - nested'); +}).addAsyncListener(listener); +expectCaught++; + + +// Net +var iter = 3; +for (var i = 0; i < iter; i++) { + errorMsgs.push('net - error: server connection'); + errorMsgs.push('net - error: client data'); + errorMsgs.push('net - error: server data'); +} +errorMsgs.push('net - error: server closed'); + +var server = net.createServer(function(c) { + c._handle.addAsyncListener(listener); + + assert.equal(server._handle._asyncQueue.length, 1); + + c.on('data', function() { + assert.equal(c._handle._asyncQueue.length, 1); + + if (0 === --iter) { + server.close(function() { + process._rawDebug('net - server closing'); + throw new Error('net - error: server closed'); + }); + expectCaught++; + } + process._rawDebug('net - connection received data'); + throw new Error('net - error: server data'); + }); + expectCaught++; + + c.end('bye'); + process._rawDebug('net - connection received'); + throw new Error('net - error: server connection'); +}); +expectCaught += iter; + +server.listen(common.PORT, function() { + // Test adding the async listener after server creation. Though it + // won't catch errors that originate synchronously from this point. + server._handle.addAsyncListener(listener); + for (var i = 0; i < iter; i++) + clientConnect(); +}); + +function clientConnect() { + var client = net.connect(common.PORT, function() { + client._handle.addAsyncListener(listener); + }); + + client.on('data', function() { + client.end('see ya'); + process._rawDebug('net - client received data'); + throw new Error('net - error: client data'); + }); + expectCaught++; +} diff --git a/test/simple/test-asynclistener-error-multiple-handled.js b/test/simple/test-asynclistener-error-multiple-handled.js new file mode 100644 index 000000000000..5a8dfbdf044a --- /dev/null +++ b/test/simple/test-asynclistener-error-multiple-handled.js @@ -0,0 +1,68 @@ +// 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 active = null; +var cntr = 0; + +function onAsync0() { + return 0; +} + +function onAsync1() { + return 1; +} + +var results = []; +var asyncNoHandleError = { + error: function(stor) { + results.push(stor); + return true; + } +}; + +var listeners = [ + process.addAsyncListener(onAsync0, asyncNoHandleError), + process.addAsyncListener(onAsync1, asyncNoHandleError) +]; + +process.nextTick(function() { + throw new Error(); +}); + +process.removeAsyncListener(listeners[0]); +process.removeAsyncListener(listeners[1]); + +process.on('exit', function(code) { + // If the exit code isn't ok then return early to throw the stack that + // caused the bad return code. + if (code !== 0) + return; + + // Handling of errors should propagate to all listeners. + assert.equal(results[0], 0); + assert.equal(results[1], 1); + assert.equal(results.length, 2); + + console.log('ok'); +}); diff --git a/test/simple/test-asynclistener-error-multiple-mix.js b/test/simple/test-asynclistener-error-multiple-mix.js new file mode 100644 index 000000000000..544900d0ac9b --- /dev/null +++ b/test/simple/test-asynclistener-error-multiple-mix.js @@ -0,0 +1,66 @@ +// 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'); + +function onAsync() {} + +var results = []; +var asyncNoHandleError = { + error: function(stor) { + results.push(1); + } +}; + +var asyncHandleError = { + error: function(stor) { + results.push(0); + return true; + } +}; + +var listeners = [ + process.addAsyncListener(onAsync, asyncHandleError), + process.addAsyncListener(onAsync, asyncNoHandleError) +]; + +// Even if an error handler returns true, both should fire. +process.nextTick(function() { + throw new Error(); +}); + +process.removeAsyncListener(listeners[0]); +process.removeAsyncListener(listeners[1]); + +process.on('exit', function(code) { + // If the exit code isn't ok then return early to throw the stack that + // caused the bad return code. + if (code !== 0) + return; + + // Mixed handling of errors should propagate to all listeners. + assert.equal(results[0], 0); + assert.equal(results[1], 1); + assert.equal(results.length, 2); + + console.log('ok'); +}); diff --git a/test/simple/test-asynclistener-error-multiple-unhandled.js b/test/simple/test-asynclistener-error-multiple-unhandled.js new file mode 100644 index 000000000000..7f3540b0d30b --- /dev/null +++ b/test/simple/test-asynclistener-error-multiple-unhandled.js @@ -0,0 +1,71 @@ +// 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'); + +function onAsync0() { + return 0; +} + +function onAsync1() { + return 1; +} + +var results = []; +var asyncNoHandleError = { + error: function(stor) { + results.push(stor); + } +}; + +var listeners = [ + process.addAsyncListener(onAsync0, asyncNoHandleError), + process.addAsyncListener(onAsync1, asyncNoHandleError) +]; + +var uncaughtFired = false; +process.on('uncaughtException', function() { + uncaughtFired = true; + + // Unhandled errors should propagate to all listeners. + assert.equal(results[0], 0); + assert.equal(results[1], 1); + assert.equal(results.length, 2); +}); + +process.nextTick(function() { + throw new Error(); +}); + +process.on('exit', function(code) { + // If the exit code isn't ok then return early to throw the stack that + // caused the bad return code. + if (code !== 0) + return; + + // Need to remove the async listeners or tests will always pass + for (var i = 0; i < listeners.length; i++) + process.removeAsyncListener(listeners[i]); + + assert.ok(uncaughtFired); + console.log('ok'); +}); diff --git a/test/simple/test-asynclistener-error-net.js b/test/simple/test-asynclistener-error-net.js new file mode 100644 index 000000000000..ed837903fefc --- /dev/null +++ b/test/simple/test-asynclistener-error-net.js @@ -0,0 +1,109 @@ +// 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 dns = require('dns'); +var fs = require('fs'); +var net = require('net'); + +var errorMsgs = []; +var caught = 0; +var expectCaught = 0; + +function asyncL() { } + +var callbacksObj = { + error: function(value, er) { + var idx = errorMsgs.indexOf(er.message); + caught++; + + process._rawDebug('Handling error: ' + er.message); + + if (-1 < idx) + errorMsgs.splice(idx, 1); + else + throw new Error('Message not found: ' + er.message); + + return true; + } +}; + +var listener = process.addAsyncListener(asyncL, callbacksObj); + +process.on('exit', function(code) { + process.removeAsyncListener(listener); + + if (code > 0) + return; + + if (errorMsgs.length > 0) + throw new Error('Errors not fired: ' + errorMsgs); + + assert.equal(caught, expectCaught); + process._rawDebug('ok'); +}); + + +// Net +var iter = 3; +for (var i = 0; i < iter; i++) { + errorMsgs.push('net - error: server connection'); + errorMsgs.push('net - error: client data'); + errorMsgs.push('net - error: server data'); +} +errorMsgs.push('net - error: server closed'); + +var server = net.createServer(function(c) { + c.on('data', function() { + if (0 === --iter) { + server.close(function() { + process._rawDebug('net - server closing'); + throw new Error('net - error: server closed'); + }); + expectCaught++; + } + process._rawDebug('net - connection received data'); + throw new Error('net - error: server data'); + }); + expectCaught++; + + c.end('bye'); + process._rawDebug('net - connection received'); + throw new Error('net - error: server connection'); +}); +expectCaught += iter; + +server.listen(common.PORT, function() { + for (var i = 0; i < iter; i++) + clientConnect(); +}); + +function clientConnect() { + var client = net.connect(common.PORT, function() { }); + + client.on('data', function() { + client.end('see ya'); + process._rawDebug('net - client received data'); + throw new Error('net - error: client data'); + }); + expectCaught++; +} diff --git a/test/simple/test-asynclistener-error-throw-in-after.js b/test/simple/test-asynclistener-error-throw-in-after.js new file mode 100644 index 000000000000..77f89653fe78 --- /dev/null +++ b/test/simple/test-asynclistener-error-throw-in-after.js @@ -0,0 +1,62 @@ +// 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 once = 0; +function onAsync0() { } + +var results = []; +var handlers = { + after: function() { + throw 1; + }, + error: function(stor, err) { + // Error handler must be called exactly *once*. + once++; + assert.equal(err, 1); + return true; + } +} + +var key = process.addAsyncListener(onAsync0, handlers); + +var uncaughtFired = false; +process.on('uncaughtException', function(err) { + uncaughtFired = true; + + assert.equal(once, 1); +}); + +process.nextTick(function() { }); + +process.removeAsyncListener(key); + +process.on('exit', function(code) { + // If the exit code isn't ok then return early to throw the stack that + // caused the bad return code. + if (code !== 0) + return; + + assert.ok(uncaughtFired); + console.log('ok'); +}); diff --git a/test/simple/test-asynclistener-error-throw-in-before-multiple.js b/test/simple/test-asynclistener-error-throw-in-before-multiple.js new file mode 100644 index 000000000000..38da4e3238f8 --- /dev/null +++ b/test/simple/test-asynclistener-error-throw-in-before-multiple.js @@ -0,0 +1,81 @@ +// 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 once = 0; +function onAsync0() { } +function onAsync1() { } + +var results = []; +var handlers = { + before: function() { + throw 1; + }, + error: function(stor, err) { + // Must catch error thrown in before callback. + assert.equal(err, 1); + once++; + return true; + } +} + +var handlers1 = { + before: function() { + throw 2; + }, + error: function(stor, err) { + // Must catch *other* handlers throw by error callback. + assert.equal(err, 1); + once++; + return true; + } +} + +var listeners = [ + process.addAsyncListener(onAsync0, handlers), + process.addAsyncListener(onAsync1, handlers1) +]; + +var uncaughtFired = false; +process.on('uncaughtException', function(err) { + uncaughtFired = true; + + // Both error handlers must fire. + assert.equal(once, 2); +}); + +process.nextTick(function() { }); + +for (var i = 0; i < listeners.length; i++) + process.removeAsyncListener(listeners[i]); + +process.on('exit', function(code) { + // If the exit code isn't ok then return early to throw the stack that + // caused the bad return code. + if (code !== 0) + return; + // Make sure uncaughtException actually fired. + assert.ok(uncaughtFired); + console.log('ok'); +}); + diff --git a/test/simple/test-asynclistener-error-throw-in-before.js b/test/simple/test-asynclistener-error-throw-in-before.js new file mode 100644 index 000000000000..b8d0348196d8 --- /dev/null +++ b/test/simple/test-asynclistener-error-throw-in-before.js @@ -0,0 +1,64 @@ +// 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 once = 0; +function onAsync0() {} + +var results = []; +var handlers = { + before: function() { + throw 1; + }, + error: function(stor, err) { + // Error handler must be called exactly *once*. + once++; + assert.equal(err, 1); + return true; + } +} + +var key = process.addAsyncListener(onAsync0, handlers); + +var uncaughtFired = false; +process.on('uncaughtException', function(err) { + uncaughtFired = true; + + // Process should propagate error regardless of handlers return value. + assert.equal(once, 1); +}); + +process.nextTick(function() { }); + +process.removeAsyncListener(key); + +process.on('exit', function(code) { + // If the exit code isn't ok then return early to throw the stack that + // caused the bad return code. + if (code !== 0) + return; + + // Make sure that the uncaughtException actually fired. + assert.ok(uncaughtFired); + console.log('ok'); +}); diff --git a/test/simple/test-asynclistener-error-throw-in-error.js b/test/simple/test-asynclistener-error-throw-in-error.js new file mode 100644 index 000000000000..271dd03832cd --- /dev/null +++ b/test/simple/test-asynclistener-error-throw-in-error.js @@ -0,0 +1,86 @@ +// 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 checkStr = 'WRITTEN ON EXIT\n'; + +if (process.argv[2] === 'child') + runChild(); +else + runParent(); + + +function runChild() { + var cntr = 0; + + var key = process.addAsyncListener(function() { }, { + error: function onError() { + cntr++; + throw new Error('onError'); + } + }); + + process.on('unhandledException', function() { + // Throwing in 'error' should bypass unhandledException. + process.exit(2); + }); + + process.on('exit', function() { + // Make sure that we can still write out to stderr even when the + // process dies. + process._rawDebug(checkStr); + }); + + process.nextTick(function() { + throw new Error('nextTick'); + }); +} + + +function runParent() { + var childDidExit = false; + var childStr = ''; + var child = spawn(process.execPath, [__filename, 'child']); + child.stderr.on('data', function(chunk) { + process._rawDebug('received data from child'); + childStr += chunk.toString(); + }); + + child.on('exit', function(code) { + process._rawDebug('child process exiting'); + childDidExit = true; + // This is thrown when Node throws from _fatalException. + assert.equal(code, 7); + }); + + process.on('exit', function() { + process._rawDebug('child ondata message:', + childStr.substr(0, childStr.indexOf('\n'))); + + assert.ok(childDidExit); + assert.notEqual(childStr.indexOf(checkStr), -1); + console.log('ok'); + }); +} + diff --git a/test/simple/test-asynclistener-error.js b/test/simple/test-asynclistener-error.js new file mode 100644 index 000000000000..c1525e46bba1 --- /dev/null +++ b/test/simple/test-asynclistener-error.js @@ -0,0 +1,258 @@ +// 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 dns = require('dns'); +var fs = require('fs'); +var net = require('net'); + +var addListener = process.addAsyncListener; +var removeListener = process.removeAsyncListener; +var errorMsgs = []; +var currentMsg = ''; +var caught = 0; +var expectCaught = 0; +var exitCbRan = false; + +function asyncL() { } + +var callbacksObj = { + error: function(value, er) { + var idx = errorMsgs.indexOf(er.message); + + caught++; + + if (-1 < idx) + errorMsgs.splice(idx, 1); + + return currentMsg === er.message; + } +}; + +var listener = process.createAsyncListener(asyncL, callbacksObj); + +process.on('exit', function(code) { + removeListener(listener); + + // Something else went wrong, no need to further check. + if (code > 0) + return; + + // Make sure the exit callback only runs once. + assert.ok(!exitCbRan); + exitCbRan = true; + + // Check if any error messages weren't removed from the msg queue. + if (errorMsgs.length > 0) + throw new Error('Errors not fired: ' + errorMsgs); + + assert.equal(caught, expectCaught, 'caught all expected errors'); + process._rawDebug('ok'); +}); + + +// Catch synchronous throws +errorMsgs.push('sync throw'); +process.nextTick(function() { + addListener(listener); + + expectCaught++; + currentMsg = 'sync throw'; + throw new Error(currentMsg); + + removeListener(listener); +}); + + +// Simple cases +errorMsgs.push('setTimeout - simple'); +errorMsgs.push('setImmediate - simple'); +errorMsgs.push('setInterval - simple'); +errorMsgs.push('process.nextTick - simple'); +process.nextTick(function() { + addListener(listener); + + setTimeout(function() { + currentMsg = 'setTimeout - simple'; + throw new Error(currentMsg); + }); + expectCaught++; + + setImmediate(function() { + currentMsg = 'setImmediate - simple'; + throw new Error(currentMsg); + }); + expectCaught++; + + var b = setInterval(function() { + clearInterval(b); + currentMsg = 'setInterval - simple'; + throw new Error(currentMsg); + }); + expectCaught++; + + process.nextTick(function() { + currentMsg = 'process.nextTick - simple'; + throw new Error(currentMsg); + }); + expectCaught++; + + removeListener(listener); +}); + + +// Deeply nested +errorMsgs.push('setInterval - nested'); +errorMsgs.push('setImmediate - nested'); +errorMsgs.push('process.nextTick - nested'); +errorMsgs.push('setTimeout2 - nested'); +errorMsgs.push('setTimeout - nested'); +process.nextTick(function() { + addListener(listener); + + setTimeout(function() { + process.nextTick(function() { + setImmediate(function() { + var b = setInterval(function() { + clearInterval(b); + currentMsg = 'setInterval - nested'; + throw new Error(currentMsg); + }); + expectCaught++; + currentMsg = 'setImmediate - nested'; + throw new Error(currentMsg); + }); + expectCaught++; + currentMsg = 'process.nextTick - nested'; + throw new Error(currentMsg); + }); + expectCaught++; + setTimeout(function() { + currentMsg = 'setTimeout2 - nested'; + throw new Error(currentMsg); + }); + expectCaught++; + currentMsg = 'setTimeout - nested'; + throw new Error(currentMsg); + }); + expectCaught++; + + removeListener(listener); +}); + + +// FS +errorMsgs.push('fs - file does not exist'); +errorMsgs.push('fs - exists'); +errorMsgs.push('fs - realpath'); +process.nextTick(function() { + addListener(listener); + + fs.stat('does not exist', function(err, stats) { + currentMsg = 'fs - file does not exist'; + throw new Error(currentMsg); + }); + expectCaught++; + + fs.exists('hi all', function(exists) { + currentMsg = 'fs - exists'; + throw new Error(currentMsg); + }); + expectCaught++; + + fs.realpath('/some/path', function(err, resolved) { + currentMsg = 'fs - realpath'; + throw new Error(currentMsg); + }); + expectCaught++; + + removeListener(listener); +}); + + +// Nested FS +errorMsgs.push('fs - nested file does not exist'); +process.nextTick(function() { + addListener(listener); + + setTimeout(function() { + setImmediate(function() { + var b = setInterval(function() { + clearInterval(b); + process.nextTick(function() { + fs.stat('does not exist', function(err, stats) { + currentMsg = 'fs - nested file does not exist'; + throw new Error(currentMsg); + }); + expectCaught++; + }); + }); + }); + }); + + removeListener(listener); +}); + + +// Net +errorMsgs.push('net - connection listener'); +errorMsgs.push('net - client connect'); +errorMsgs.push('net - server listening'); +process.nextTick(function() { + addListener(listener); + + var server = net.createServer(function(c) { + server.close(); + currentMsg = 'net - connection listener'; + throw new Error(currentMsg); + }); + expectCaught++; + + server.listen(common.PORT, function() { + var client = net.connect(common.PORT, function() { + client.end(); + currentMsg = 'net - client connect'; + throw new Error(currentMsg); + }); + expectCaught++; + currentMsg = 'net - server listening'; + throw new Error(currentMsg); + }); + expectCaught++; + + removeListener(listener); +}); + + +// DNS +errorMsgs.push('dns - lookup'); +process.nextTick(function() { + addListener(listener); + + dns.lookup('localhost', function() { + currentMsg = 'dns - lookup'; + throw new Error(currentMsg); + }); + expectCaught++; + + removeListener(listener); +}); diff --git a/test/simple/test-asynclistener-multi-timeout.js b/test/simple/test-asynclistener-multi-timeout.js new file mode 100644 index 000000000000..30ec6dd9ae8d --- /dev/null +++ b/test/simple/test-asynclistener-multi-timeout.js @@ -0,0 +1,71 @@ +// 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 addListener = process.addAsyncListener; +var removeListener = process.removeAsyncListener; +var caught = []; +var expect = []; + +function asyncL(a) {} + +var callbacksObj = { + error: function(value, er) { + process._rawDebug('caught', er.message); + caught.push(er.message); + return (expect.indexOf(er.message) !== -1); + } +}; + +var listener = process.createAsyncListener(asyncL, callbacksObj); + +process.on('exit', function(code) { + removeListener(listener); + + if (code > 0) + return; + + expect = expect.sort(); + caught = caught.sort(); + + process._rawDebug('expect', expect); + process._rawDebug('caught', caught); + assert.deepEqual(caught, expect, 'caught all expected errors'); + process._rawDebug('ok'); +}); + + +expect.push('immediate simple a'); +expect.push('immediate simple b'); +process.nextTick(function() { + addListener(listener); + // Tests for a setImmediate specific bug encountered while implementing + // AsyncListeners. + setImmediate(function() { + throw new Error('immediate simple a'); + }); + setImmediate(function() { + throw new Error('immediate simple b'); + }); + removeListener(listener); +}); diff --git a/test/simple/test-asynclistener-remove-after.js b/test/simple/test-asynclistener-remove-after.js new file mode 100644 index 000000000000..41d7c9697175 --- /dev/null +++ b/test/simple/test-asynclistener-remove-after.js @@ -0,0 +1,66 @@ +// 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 net = require('net'); + + +// TODO(trevnorris): Test has the flaw that it's not checking if the async +// flag has been removed on the class instance. Though currently there's +// no way to do that. +var listener = process.addAsyncListener(function() { }); + + +// Test timers + +setImmediate(function() { + assert.equal(this._asyncQueue.length, 0); +}).removeAsyncListener(listener); + +setTimeout(function() { + assert.equal(this._asyncQueue.length, 0); +}).removeAsyncListener(listener); + +setInterval(function() { + clearInterval(this); + assert.equal(this._asyncQueue.length, 0); +}).removeAsyncListener(listener); + + +// Test net + +var server = net.createServer(function(c) { + c._handle.removeAsyncListener(listener); + assert.equal(c._handle._asyncQueue.length, 0); +}); + +server.listen(common.PORT, function() { + server._handle.removeAsyncListener(listener); + assert.equal(server._handle._asyncQueue.length, 0); + + var client = net.connect(common.PORT, function() { + client._handle.removeAsyncListener(listener); + assert.equal(client._handle._asyncQueue.length, 0); + client.end(); + server.close(); + }); +}); diff --git a/test/simple/test-asynclistener-remove-before.js b/test/simple/test-asynclistener-remove-before.js new file mode 100644 index 000000000000..5154e673d173 --- /dev/null +++ b/test/simple/test-asynclistener-remove-before.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 set = 0; + +function onAsync0() { } + +var asyncNoHandleError = { + before: function() { + set++; + }, + after: function() { + set++; + } +} + +var key = process.addAsyncListener(onAsync0, asyncNoHandleError); + +process.removeAsyncListener(key); + +process.nextTick(function() { }); + +process.on('exit', function(code) { + // If the exit code isn't ok then return early to throw the stack that + // caused the bad return code. + if (code !== 0) + return; + + // The async handler should never be called. + assert.equal(set, 0); + console.log('ok'); +}); + + diff --git a/test/simple/test-asynclistener-remove-inflight-error.js b/test/simple/test-asynclistener-remove-inflight-error.js new file mode 100644 index 000000000000..40135b04e7f3 --- /dev/null +++ b/test/simple/test-asynclistener-remove-inflight-error.js @@ -0,0 +1,59 @@ +// 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'); + +function onAsync0() { } + +var set = 0; +var asyncNoHandleError = { + error: function() { + set++; + } +} + +var key = process.addAsyncListener(onAsync0, asyncNoHandleError); + +process.nextTick(function() { + throw 1; +}); + +process.removeAsyncListener(key); + +var uncaughtFired = false; +process.on('uncaughtException', function() { + uncaughtFired = true; + + // Throwing should call the error handler once, then propagate to + // uncaughtException + assert.equal(set, 1); +}); + +process.on('exit', function(code) { + // If the exit code isn't ok then return early to throw the stack that + // caused the bad return code. + if (code !== 0) + return; + + assert.ok(uncaughtFired); + console.log('ok'); +}); diff --git a/test/simple/test-asynclistener-remove-inflight.js b/test/simple/test-asynclistener-remove-inflight.js new file mode 100644 index 000000000000..1346609a59b9 --- /dev/null +++ b/test/simple/test-asynclistener-remove-inflight.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'); + +function onAsync0() { } + +var set = 0; +var asyncNoHandleError = { + before: function() { + set++; + }, + after: function() { + set++; + } +} + +var key = process.addAsyncListener(onAsync0, asyncNoHandleError); + +process.nextTick(function() { }); + +process.removeAsyncListener(key); + +process.on('exit', function(code) { + // If the exit code isn't ok then return early to throw the stack that + // caused the bad return code. + if (code !== 0) + return; + + // Calling removeAsyncListener *after* a callback is scheduled + // should not affect the handler from responding to the callback. + assert.equal(set, 2); + console.log('ok'); +}); + diff --git a/test/simple/test-asynclistener-throw-before-infinite-recursion.js b/test/simple/test-asynclistener-throw-before-infinite-recursion.js new file mode 100644 index 000000000000..fcf0fd69e887 --- /dev/null +++ b/test/simple/test-asynclistener-throw-before-infinite-recursion.js @@ -0,0 +1,47 @@ +// 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'); + +// If there is an uncaughtException listener then the error thrown from +// "before" will be considered handled, thus calling setImmediate to +// finish execution of the nextTickQueue. This in turn will cause "before" +// to fire again, entering into an infinite loop. +// So the asyncQueue is cleared from the returned setImmediate in +// _fatalException to prevent this from happening. +var cntr = 0; + + +process.addAsyncListener(function() { }, { + before: function() { + if (++cntr > 1) { + // Can't throw since uncaughtException will also catch that. + process._rawDebug('Error: Multiple before callbacks called'); + process.exit(1); + } + throw new Error('before'); + } +}); + +process.on('uncaughtException', function() { }); + +process.nextTick(); diff --git a/test/simple/test-asynclistener.js b/test/simple/test-asynclistener.js new file mode 100644 index 000000000000..c5110de9ee1e --- /dev/null +++ b/test/simple/test-asynclistener.js @@ -0,0 +1,185 @@ +// 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 net = require('net'); +var fs = require('fs'); +var dgram = require('dgram'); + +var addListener = process.addAsyncListener; +var removeListener = process.removeAsyncListener; +var actualAsync = 0; +var expectAsync = 0; + +function onAsync() { + actualAsync++; +} + +var listener = process.createAsyncListener(onAsync); + +process.on('exit', function() { + process._rawDebug('expected', expectAsync); + process._rawDebug('actual ', actualAsync); + // TODO(trevnorris): Not a great test. If one was missed, but others + // overflowed then the test would still pass. + assert.ok(actualAsync >= expectAsync); +}); + + +// Test listeners side-by-side +process.nextTick(function() { + addListener(listener); + + var b = setInterval(function() { + clearInterval(b); + }); + expectAsync++; + + var c = setInterval(function() { + clearInterval(c); + }); + expectAsync++; + + setTimeout(function() { }); + expectAsync++; + + setTimeout(function() { }); + expectAsync++; + + process.nextTick(function() { }); + expectAsync++; + + process.nextTick(function() { }); + expectAsync++; + + setImmediate(function() { }); + expectAsync++; + + setImmediate(function() { }); + expectAsync++; + + setTimeout(function() { }, 10); + expectAsync++; + + setTimeout(function() { }, 10); + expectAsync++; + + removeListener(listener); +}); + + +// Async listeners should propagate with nested callbacks +process.nextTick(function() { + addListener(listener); + var interval = 3; + + process.nextTick(function() { + setTimeout(function() { + setImmediate(function() { + var i = setInterval(function() { + if (--interval <= 0) + clearInterval(i); + }); + expectAsync++; + }); + expectAsync++; + process.nextTick(function() { + setImmediate(function() { + setTimeout(function() { }, 20); + expectAsync++; + }); + expectAsync++; + }); + expectAsync++; + }); + expectAsync++; + }); + expectAsync++; + + removeListener(listener); +}); + + +// Test triggers with two async listeners +process.nextTick(function() { + addListener(listener); + addListener(listener); + + setTimeout(function() { + process.nextTick(function() { }); + expectAsync += 2; + }); + expectAsync += 2; + + removeListener(listener); + removeListener(listener); +}); + + +// Test callbacks from fs I/O +process.nextTick(function() { + addListener(listener); + + fs.stat('something random', function(err, stat) { }); + expectAsync++; + + setImmediate(function() { + fs.stat('random again', function(err, stat) { }); + expectAsync++; + }); + expectAsync++; + + removeListener(listener); +}); + + +// Test net I/O +process.nextTick(function() { + addListener(listener); + + var server = net.createServer(function(c) { }); + expectAsync++; + + server.listen(common.PORT, function() { + server.close(); + expectAsync++; + }); + expectAsync++; + + removeListener(listener); +}); + + +// Test UDP +process.nextTick(function() { + addListener(listener); + + var server = dgram.createSocket('udp4'); + expectAsync++; + + server.bind(common.PORT); + + server.close(); + expectAsync++; + + removeListener(listener); +}); diff --git a/test/simple/test-crypto.js b/test/simple/test-crypto.js index 22ce55f80d58..47a8f73bfe53 100644 --- a/test/simple/test-crypto.js +++ b/test/simple/test-crypto.js @@ -1005,3 +1005,6 @@ assert.throws(function() { assert.throws(function() { crypto.createVerify('RSA-SHA1').update('0', 'hex'); }, /Bad input string/); + +// Make sure memory isn't released before being returned +console.log(crypto.randomBytes(16)); diff --git a/test/simple/test-domain-http-server.js b/test/simple/test-domain-http-server.js index 666f5d190aca..57e8ac4d14a3 100644 --- a/test/simple/test-domain-http-server.js +++ b/test/simple/test-domain-http-server.js @@ -45,11 +45,10 @@ var server = http.createServer(function(req, res) { res.end(er.stack || er.message || 'Unknown error'); }); - var data; dom.run(function() { // Now, an action that has the potential to fail! // if you request 'baz', then it'll throw a JSON circular ref error. - data = JSON.stringify(objects[req.url.replace(/[^a-z]/g, '')]); + var data = JSON.stringify(objects[req.url.replace(/[^a-z]/g, '')]); // this line will throw if you pick an unknown key assert(data !== undefined, 'Data should not be undefined'); @@ -64,8 +63,6 @@ server.listen(common.PORT, next); function next() { console.log('listening on localhost:%d', common.PORT); - // now hit it a few times - var dom = domain.create(); var requests = 0; var responses = 0; diff --git a/test/simple/test-event-emitter-no-error-provided-to-error-event.js b/test/simple/test-event-emitter-no-error-provided-to-error-event.js index 92d9f8ad011f..79f14904e0ab 100644 --- a/test/simple/test-event-emitter-no-error-provided-to-error-event.js +++ b/test/simple/test-event-emitter-no-error-provided-to-error-event.js @@ -31,12 +31,12 @@ var e = new events.EventEmitter(); var d = domain.create(); d.add(e); d.on('error', function (er) { - assert(er instanceof TypeError, 'type error created'); + assert(er instanceof Error, 'error created'); errorCatched = true; }); e.emit('error'); process.on('exit', function () { - assert(errorCatched, 'error got catched'); + assert(errorCatched, 'error got caught'); });