|
|
@@ -0,0 +1,396 @@ |
|
|
// 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 EventEmitter = require('events'); |
|
|
var v8binding, process; |
|
|
|
|
|
// This needs to be loaded early, and before the "process" object is made |
|
|
// global. So allow src/node.js to pass the process object in during |
|
|
// initialization. |
|
|
exports._nodeInitialization = function nodeInitialization(pobj) { |
|
|
process = pobj; |
|
|
v8binding = process.binding('v8'); |
|
|
|
|
|
// Finish setting up the v8 Object. |
|
|
v8.getHeapStatistics = v8binding.getHeapStatistics; |
|
|
|
|
|
// Part of the AsyncListener setup to share objects/callbacks with the |
|
|
// native layer. |
|
|
process._setupAsyncListener(asyncFlags, |
|
|
runAsyncQueue, |
|
|
loadAsyncQueue, |
|
|
unloadAsyncQueue); |
|
|
|
|
|
// Do a little housekeeping. |
|
|
delete exports._nodeInitialization; |
|
|
}; |
|
|
|
|
|
|
|
|
// v8 |
|
|
|
|
|
var v8 = exports.v8 = new EventEmitter(); |
|
|
|
|
|
|
|
|
function emitGC(before, after) { |
|
|
v8.emit('gc', before, after); |
|
|
} |
|
|
|
|
|
|
|
|
v8.on('newListener', function(name) { |
|
|
if (name === 'gc' && EventEmitter.listenerCount(this, name) === 0) { |
|
|
v8binding.startGarbageCollectionTracking(emitGC); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
v8.on('removeListener', function(name) { |
|
|
if (name === 'gc' && EventEmitter.listenerCount(this, name) === 0) { |
|
|
v8binding.stopGarbageCollectionTracking(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
// AsyncListener |
|
|
|
|
|
// new Array() is used here because it is more efficient for sparse |
|
|
// arrays. Please *do not* change these to simple bracket notation. |
|
|
|
|
|
// Track the active queue of AsyncListeners that have been added. |
|
|
var asyncQueue = new Array(); |
|
|
|
|
|
// Keep the stack of all contexts that have been loaded in the |
|
|
// execution chain of asynchronous events. |
|
|
var contextStack = new Array(); |
|
|
var currentContext = undefined; |
|
|
|
|
|
// Incremental uid for new AsyncListener instances. |
|
|
var alUid = 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 kHasListener = 0; |
|
|
var kWatchedProviders = 1; |
|
|
|
|
|
// Flags to determine what async listeners are available. |
|
|
var HAS_CREATE_AL = 1 << 0; |
|
|
var HAS_BEFORE_AL = 1 << 1; |
|
|
var HAS_AFTER_AL = 1 << 2; |
|
|
var HAS_ERROR_AL = 1 << 3; |
|
|
|
|
|
// _errorHandler is scoped so it's also accessible by _fatalException. |
|
|
exports._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. |
|
|
exports._asyncFlags = asyncFlags; |
|
|
exports._runAsyncQueue = runAsyncQueue; |
|
|
exports._loadAsyncQueue = loadAsyncQueue; |
|
|
exports._unloadAsyncQueue = unloadAsyncQueue; |
|
|
|
|
|
// Public API. |
|
|
exports.createAsyncListener = createAsyncListener; |
|
|
exports.addAsyncListener = addAsyncListener; |
|
|
exports.removeAsyncListener = removeAsyncListener; |
|
|
|
|
|
// Load the currently executing context as the current context, and |
|
|
// create a new asyncQueue that can receive any added queue items |
|
|
// during the executing of the callback. |
|
|
function loadContext(ctx) { |
|
|
contextStack.push(currentContext); |
|
|
currentContext = ctx; |
|
|
|
|
|
asyncFlags[kHasListener] = 1; |
|
|
} |
|
|
|
|
|
function unloadContext() { |
|
|
currentContext = contextStack.pop(); |
|
|
|
|
|
if (currentContext === undefined && asyncQueue.length === 0) |
|
|
asyncFlags[kHasListener] = 0; |
|
|
} |
|
|
|
|
|
// Run all the async listeners attached when an asynchronous event is |
|
|
// instantiated. |
|
|
function runAsyncQueue(context) { |
|
|
var queue = new Array(); |
|
|
var data = new Array(); |
|
|
var ccQueue, i, item, queueItem, value; |
|
|
|
|
|
context._asyncQueue = queue; |
|
|
context._asyncData = data; |
|
|
context._asyncFlags = 0; |
|
|
|
|
|
inAsyncTick = true; |
|
|
|
|
|
// First run through all callbacks in the currentContext. These may |
|
|
// add new AsyncListeners to the asyncQueue during execution. Hence |
|
|
// why they need to be evaluated first. |
|
|
if (currentContext) { |
|
|
ccQueue = currentContext._asyncQueue; |
|
|
context._asyncFlags |= currentContext._asyncFlags; |
|
|
for (i = 0; i < ccQueue.length; i++) { |
|
|
queueItem = ccQueue[i]; |
|
|
queue[queue.length] = queueItem; |
|
|
if ((queueItem.callback_flags & HAS_CREATE_AL) === 0) { |
|
|
data[queueItem.uid] = queueItem.data; |
|
|
continue; |
|
|
} |
|
|
value = queueItem.create(queueItem.data); |
|
|
data[queueItem.uid] = (value === undefined) ? queueItem.data : value; |
|
|
} |
|
|
} |
|
|
|
|
|
// Then run through all items in the asyncQueue |
|
|
if (asyncQueue) { |
|
|
for (i = 0; i < asyncQueue.length; i++) { |
|
|
queueItem = asyncQueue[i]; |
|
|
// Quick way to check if an AL instance with the same uid was |
|
|
// already run from currentContext. |
|
|
if (data[queueItem.uid] !== undefined) |
|
|
continue; |
|
|
queue[queue.length] = queueItem; |
|
|
context._asyncFlags |= queueItem.callback_flags; |
|
|
if ((queueItem.callback_flags & HAS_CREATE_AL) === 0) { |
|
|
data[queueItem.uid] = queueItem.data; |
|
|
continue; |
|
|
} |
|
|
value = queueItem.create(queueItem.data); |
|
|
data[queueItem.uid] = (value === undefined) ? queueItem.data : value; |
|
|
} |
|
|
} |
|
|
|
|
|
inAsyncTick = false; |
|
|
} |
|
|
|
|
|
// Load the AsyncListener queue attached to context and run all |
|
|
// "before" callbacks, if they exist. |
|
|
function loadAsyncQueue(context) { |
|
|
loadContext(context); |
|
|
|
|
|
if ((context._asyncFlags & HAS_BEFORE_AL) === 0) |
|
|
return; |
|
|
|
|
|
var queue = context._asyncQueue; |
|
|
var data = context._asyncData; |
|
|
var i, queueItem; |
|
|
|
|
|
inAsyncTick = true; |
|
|
for (i = 0; i < queue.length; i++) { |
|
|
queueItem = queue[i]; |
|
|
if ((queueItem.callback_flags & HAS_BEFORE_AL) > 0) |
|
|
queueItem.before(context, data[queueItem.uid]); |
|
|
} |
|
|
inAsyncTick = false; |
|
|
} |
|
|
|
|
|
// Unload the AsyncListener queue attached to context and run all |
|
|
// "after" callbacks, if they exist. |
|
|
function unloadAsyncQueue(context) { |
|
|
if ((context._asyncFlags & HAS_AFTER_AL) === 0) { |
|
|
unloadContext(); |
|
|
return; |
|
|
} |
|
|
|
|
|
var queue = context._asyncQueue; |
|
|
var data = context._asyncData; |
|
|
var i, queueItem; |
|
|
|
|
|
inAsyncTick = true; |
|
|
for (i = 0; i < queue.length; i++) { |
|
|
queueItem = queue[i]; |
|
|
if ((queueItem.callback_flags & HAS_AFTER_AL) > 0) |
|
|
queueItem.after(context, data[queueItem.uid]); |
|
|
} |
|
|
inAsyncTick = false; |
|
|
|
|
|
unloadContext(); |
|
|
} |
|
|
|
|
|
// Handle errors that are thrown while in the context of an |
|
|
// AsyncListener. If an error is thrown from an AsyncListener |
|
|
// callback error handlers will be called once more to report |
|
|
// the error, then the application will die forcefully. |
|
|
function errorHandler(er) { |
|
|
if (inErrorTick) |
|
|
return false; |
|
|
|
|
|
var handled = false; |
|
|
var i, queueItem, threw; |
|
|
|
|
|
inErrorTick = true; |
|
|
|
|
|
// First process error callbacks from the current context. |
|
|
if (currentContext && (currentContext._asyncFlags & HAS_ERROR_AL) > 0) { |
|
|
var queue = currentContext._asyncQueue; |
|
|
var data = currentContext._asyncData; |
|
|
for (i = 0; i < queue.length; i++) { |
|
|
queueItem = queue[i]; |
|
|
if ((queueItem.callback_flags & HAS_ERROR_AL) === 0) |
|
|
continue; |
|
|
try { |
|
|
threw = true; |
|
|
// While it would be possible to pass in currentContext, if |
|
|
// the error is thrown from the "create" callback then there's |
|
|
// a chance the object hasn't been fully constructed. |
|
|
handled = queueItem.error(data[queueItem.uid], er) || handled; |
|
|
threw = false; |
|
|
} finally { |
|
|
// If the error callback thew then die quickly. Only allow the |
|
|
// exit events to be processed. |
|
|
if (threw) { |
|
|
process._exiting = true; |
|
|
process.emit('exit', 1); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// Now process callbacks from any existing queue. |
|
|
if (asyncQueue) { |
|
|
for (i = 0; i < asyncQueue.length; i++) { |
|
|
queueItem = asyncQueue[i]; |
|
|
if ((queueItem.callback_flags & HAS_ERROR_AL) === 0 || |
|
|
(data && data[queueItem.uid] !== undefined)) |
|
|
continue; |
|
|
try { |
|
|
threw = true; |
|
|
handled = queueItem.error(queueItem.data, er) || handled; |
|
|
threw = false; |
|
|
} finally { |
|
|
// If the error callback thew then die quickly. Only allow the |
|
|
// exit events to be processed. |
|
|
if (threw) { |
|
|
process._exiting = true; |
|
|
process.emit('exit', 1); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
inErrorTick = false; |
|
|
|
|
|
unloadContext(); |
|
|
|
|
|
// TODO(trevnorris): If the error was handled, should the after callbacks |
|
|
// be fired anyways? |
|
|
|
|
|
return handled && !inAsyncTick; |
|
|
} |
|
|
|
|
|
// Instance function of an AsyncListener object. |
|
|
function AsyncListenerInst(callbacks, data) { |
|
|
if (typeof callbacks.create === 'function') { |
|
|
this.create = callbacks.create; |
|
|
this.callback_flags |= HAS_CREATE_AL; |
|
|
} |
|
|
if (typeof callbacks.before === 'function') { |
|
|
this.before = callbacks.before; |
|
|
this.callback_flags |= HAS_BEFORE_AL; |
|
|
} |
|
|
if (typeof callbacks.after === 'function') { |
|
|
this.after = callbacks.after; |
|
|
this.callback_flags |= HAS_AFTER_AL; |
|
|
} |
|
|
if (typeof callbacks.error === 'function') { |
|
|
this.error = callbacks.error; |
|
|
this.callback_flags |= HAS_ERROR_AL; |
|
|
} |
|
|
|
|
|
this.uid = ++alUid; |
|
|
this.data = data === undefined ? null : data; |
|
|
} |
|
|
AsyncListenerInst.prototype.create = undefined; |
|
|
AsyncListenerInst.prototype.before = undefined; |
|
|
AsyncListenerInst.prototype.after = undefined; |
|
|
AsyncListenerInst.prototype.error = undefined; |
|
|
AsyncListenerInst.prototype.data = undefined; |
|
|
AsyncListenerInst.prototype.uid = 0; |
|
|
AsyncListenerInst.prototype.callback_flags = 0; |
|
|
|
|
|
// Create new async listener object. Useful when instantiating a new |
|
|
// object and want the listener instance, but not add it to the stack. |
|
|
// If an existing AsyncListenerInst is passed then any new "data" is |
|
|
// ignored. |
|
|
function createAsyncListener(callbacks, data) { |
|
|
if (typeof callbacks !== 'object' || callbacks == null) |
|
|
throw new TypeError('callbacks argument must be an object'); |
|
|
|
|
|
if (callbacks instanceof AsyncListenerInst) |
|
|
return callbacks; |
|
|
else |
|
|
return new AsyncListenerInst(callbacks, data); |
|
|
} |
|
|
|
|
|
// Add a listener to the current queue. |
|
|
function addAsyncListener(callbacks, data) { |
|
|
// Fast track if a new AsyncListenerInst has to be created. |
|
|
if (!(callbacks instanceof AsyncListenerInst)) { |
|
|
callbacks = createAsyncListener(callbacks, data); |
|
|
asyncQueue.push(callbacks); |
|
|
asyncFlags[kHasListener] = 1; |
|
|
return callbacks; |
|
|
} |
|
|
|
|
|
var inQueue = false; |
|
|
// The asyncQueue will be small. Probably always <= 3 items. |
|
|
for (var i = 0; i < asyncQueue.length; i++) { |
|
|
if (callbacks === asyncQueue[i]) { |
|
|
inQueue = true; |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
// Make sure the callback doesn't already exist in the queue. |
|
|
if (!inQueue) { |
|
|
asyncQueue.push(callbacks); |
|
|
asyncFlags[kHasListener] = 1; |
|
|
} |
|
|
|
|
|
return callbacks; |
|
|
} |
|
|
|
|
|
// Remove listener from the current queue. Though this will not remove |
|
|
// the listener from the current context. So callback propagation will |
|
|
// continue. |
|
|
function removeAsyncListener(obj) { |
|
|
for (var i = 0; i < asyncQueue.length; i++) { |
|
|
if (obj === asyncQueue[i]) { |
|
|
asyncQueue.splice(i, 1); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
if (asyncQueue.length > 0 || currentContext !== undefined) |
|
|
asyncFlags[kHasListener] = 1; |
|
|
else |
|
|
asyncFlags[kHasListener] = 0; |
|
|
} |