mirror of
https://github.com/nodejs/node.git
synced 2024-11-22 07:37:56 +01:00
03e9f84933
Signed-off-by: Fedor Indutny <fedor@indutny.com>
396 lines
12 KiB
JavaScript
396 lines
12 KiB
JavaScript
// 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;
|
|
|
|
// 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, 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;
|
|
}
|