summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTrevor Norris <trev.norris@gmail.com>2013-02-06 17:26:18 -0800
committerisaacs <i@izs.me>2013-02-15 18:13:01 -0800
commit86c0745a5ebd75a4a5fa69420dfde657a5e717bc (patch)
tree248ce9c77fe55a73991f45b3aa1928f8a06d1c59 /src
parent95ac576bf956055d3d5758cacbd336b53570f89f (diff)
downloadnode-86c0745a5ebd75a4a5fa69420dfde657a5e717bc.tar.gz
process: streamlining tick callback logic
* Callbacks from spinner now calls its own function, separate from the tickCallback logic * MakeCallback will call a domain specific function if a domain is detected * _tickCallback assumes no domains, until nextTick receives a callback with a domain. After that _tickCallback is overridden with the domain specific implementation. * _needTickCallback runs in startup() instead of nextTick (isaacs) * Fix bug in _fatalException where exit would be called twice (isaacs) * Process.domain has a default value of null * Manually track nextTickQueue.length (will be useful later) * Update tests to reflect internal api changes
Diffstat (limited to 'src')
-rw-r--r--src/node.cc158
-rw-r--r--src/node.js165
2 files changed, 216 insertions, 107 deletions
diff --git a/src/node.cc b/src/node.cc
index 890ea6355..c4131069e 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -100,6 +100,8 @@ Persistent<String> process_symbol;
Persistent<String> domain_symbol;
static Persistent<Object> process;
+static Persistent<Function> process_tickWDCallback;
+static Persistent<Function> process_tickFromSpinner;
static Persistent<Function> process_tickCallback;
static Persistent<String> exports_symbol;
@@ -174,20 +176,19 @@ static void Tick(void) {
HandleScope scope;
- if (tick_callback_sym.IsEmpty()) {
- // Lazily set the symbol
- tick_callback_sym = NODE_PSYMBOL("_tickCallback");
+ if (process_tickFromSpinner.IsEmpty()) {
+ Local<Value> cb_v = process->Get(String::New("_tickFromSpinner"));
+ if (!cb_v->IsFunction()) {
+ fprintf(stderr, "process._tickFromSpinner assigned to non-function\n");
+ abort();
+ }
+ Local<Function> cb = cb_v.As<Function>();
+ process_tickFromSpinner = Persistent<Function>::New(cb);
}
- Local<Value> cb_v = process->Get(tick_callback_sym);
- if (!cb_v->IsFunction()) return;
- Local<Function> cb = Local<Function>::Cast(cb_v);
-
TryCatch try_catch;
- // Let the tick callback know that this is coming from the spinner
- Handle<Value> argv[] = { True(node_isolate) };
- cb->Call(process, ARRAY_SIZE(argv), argv);
+ process_tickFromSpinner->Call(process, 0, NULL);
if (try_catch.HasCaught()) {
FatalException(try_catch);
@@ -903,46 +904,23 @@ Handle<Value> FromConstructorTemplate(Persistent<FunctionTemplate> t,
}
-// MakeCallback may only be made directly off the event loop.
-// That is there can be no JavaScript stack frames underneath it.
-// (Is there any way to assert that?)
-//
-// Maybe make this a method of a node::Handle super class
-//
-Handle<Value>
-MakeCallback(const Handle<Object> object,
- const char* method,
- int argc,
- Handle<Value> argv[]) {
- HandleScope scope;
-
- Handle<Value> ret =
- MakeCallback(object, String::NewSymbol(method), argc, argv);
-
- return scope.Close(ret);
-}
-
Handle<Value>
-MakeCallback(const Handle<Object> object,
- const Handle<String> symbol,
+MCWithDomain(const Handle<Object> object,
+ const Handle<Function> callback,
int argc,
Handle<Value> argv[]) {
- HandleScope scope;
-
- Local<Value> callback_v = object->Get(symbol);
- if (!callback_v->IsFunction()) {
- String::Utf8Value method(symbol);
- // XXX: If the object has a domain attached, handle it there?
- // At least, would be good to get *some* sort of indication
- // of how we got here, even if it's not catchable.
- fprintf(stderr, "Non-function in MakeCallback. method = %s\n", *method);
- abort();
+ // lazy load _tickWDCallback
+ if (process_tickWDCallback.IsEmpty()) {
+ Local<Value> cb_v = process->Get(String::New("_tickWDCallback"));
+ if (!cb_v->IsFunction()) {
+ fprintf(stderr, "process._tickWDCallback assigned to non-function\n");
+ abort();
+ }
+ Local<Function> cb = cb_v.As<Function>();
+ process_tickWDCallback = Persistent<Function>::New(cb);
}
- // TODO Hook for long stack traces to be made here.
-
- TryCatch try_catch;
-
+ // lazy load domain specific symbols
if (enter_symbol.IsEmpty()) {
enter_symbol = NODE_PSYMBOL("enter");
exit_symbol = NODE_PSYMBOL("exit");
@@ -953,22 +931,24 @@ MakeCallback(const Handle<Object> object,
Local<Object> domain;
Local<Function> enter;
Local<Function> exit;
- if (!domain_v->IsUndefined()) {
- domain = domain_v->ToObject();
- if (domain->Get(disposed_symbol)->BooleanValue()) {
- // domain has been disposed of.
- return Undefined(node_isolate);
- }
- enter = Local<Function>::Cast(domain->Get(enter_symbol));
- enter->Call(domain, 0, NULL);
+
+ TryCatch try_catch;
+
+ domain = domain_v->ToObject();
+ assert(!domain.IsEmpty());
+ if (domain->Get(disposed_symbol)->IsTrue()) {
+ // domain has been disposed of.
+ return Undefined(node_isolate);
}
+ enter = Local<Function>::Cast(domain->Get(enter_symbol));
+ assert(!enter.IsEmpty());
+ enter->Call(domain, 0, NULL);
if (try_catch.HasCaught()) {
FatalException(try_catch);
return Undefined(node_isolate);
}
- Local<Function> callback = Local<Function>::Cast(callback_v);
Local<Value> ret = callback->Call(object, argc, argv);
if (try_catch.HasCaught()) {
@@ -976,17 +956,46 @@ MakeCallback(const Handle<Object> object,
return Undefined(node_isolate);
}
- if (!domain_v->IsUndefined()) {
- exit = Local<Function>::Cast(domain->Get(exit_symbol));
- exit->Call(domain, 0, NULL);
+ exit = Local<Function>::Cast(domain->Get(exit_symbol));
+ assert(!exit.IsEmpty());
+ exit->Call(domain, 0, NULL);
+
+ if (try_catch.HasCaught()) {
+ FatalException(try_catch);
+ return Undefined(node_isolate);
}
+ // process nextTicks after call
+ process_tickWDCallback->Call(process, 0, NULL);
+
if (try_catch.HasCaught()) {
FatalException(try_catch);
return Undefined(node_isolate);
}
- // process nextTicks after every time we get called.
+ return ret;
+}
+
+
+Handle<Value>
+MakeCallback(const Handle<Object> object,
+ const Handle<String> symbol,
+ int argc,
+ Handle<Value> argv[]) {
+ HandleScope scope;
+
+ Local<Value> callback_v = object->Get(symbol);
+ if (!callback_v->IsFunction()) {
+ String::Utf8Value method(symbol);
+ fprintf(stderr, "Non-function in MakeCallback. method = %s\n", *method);
+ abort();
+ }
+
+ Local<Function> callback = Local<Function>::Cast(callback_v);
+
+ // TODO Hook for long stack traces to be made here.
+
+ // lazy load no domain next tick callbacks
if (process_tickCallback.IsEmpty()) {
Local<Value> cb_v = process->Get(String::New("_tickCallback"));
if (!cb_v->IsFunction()) {
@@ -996,7 +1005,24 @@ MakeCallback(const Handle<Object> object,
Local<Function> cb = cb_v.As<Function>();
process_tickCallback = Persistent<Function>::New(cb);
}
- process_tickCallback->Call(process, NULL, 0);
+
+ // has domain, off with you
+ if (object->Has(domain_symbol) &&
+ !object->Get(domain_symbol)->IsNull() &&
+ !object->Get(domain_symbol)->IsUndefined())
+ return scope.Close(MCWithDomain(object, callback, argc, argv));
+
+ TryCatch try_catch;
+
+ Local<Value> ret = callback->Call(object, argc, argv);
+
+ if (try_catch.HasCaught()) {
+ FatalException(try_catch);
+ return Undefined(node_isolate);
+ }
+
+ // process nextTicks after call
+ process_tickCallback->Call(process, 0, NULL);
if (try_catch.HasCaught()) {
FatalException(try_catch);
@@ -1007,6 +1033,20 @@ MakeCallback(const Handle<Object> object,
}
+Handle<Value>
+MakeCallback(const Handle<Object> object,
+ const char* method,
+ int argc,
+ Handle<Value> argv[]) {
+ HandleScope scope;
+
+ Handle<Value> ret =
+ MakeCallback(object, String::NewSymbol(method), argc, argv);
+
+ return scope.Close(ret);
+}
+
+
void SetErrno(uv_err_t err) {
HandleScope scope;
diff --git a/src/node.js b/src/node.js
index a9e795acd..bc2ff0a29 100644
--- a/src/node.js
+++ b/src/node.js
@@ -156,6 +156,8 @@
});
}
}
+
+ process._needTickCallback();
}
startup.globalVariables = function() {
@@ -165,6 +167,8 @@
global.root = global;
global.Buffer = NativeModule.require('buffer').Buffer;
process.binding('buffer').setFastBufferConstructor(global.Buffer);
+ process.domain = null;
+ process._exiting = false;
};
startup.globalTimeouts = function() {
@@ -262,11 +266,17 @@
// since that means that we'll exit the process, emit the 'exit' event
if (!caught) {
try {
- process.emit('exit', 1);
+ if (!process._exiting) {
+ process._exiting = true;
+ process.emit('exit', 1);
+ }
} catch (er) {
// nothing to be done about it at this point.
}
}
+ // if we handled an error, then make sure any ticks get processed
+ if (caught)
+ process._needTickCallback();
return caught;
};
};
@@ -297,10 +307,20 @@
};
startup.processNextTick = function() {
+ var _needTickCallback = process._needTickCallback;
var nextTickQueue = [];
+ var nextTickQueueLength = 0;
var nextTickIndex = 0;
- var inTick = false;
var tickDepth = 0;
+ var usingDomains = false;
+ var needSpinner = true;
+ var inTick = false;
+
+ process._tickCallback = _tickCallback;
+ process._tickFromSpinner = _tickFromSpinner;
+ // needs to be accessible from cc land
+ process._tickWDCallback = _tickWDCallback;
+ process.nextTick = nextTick;
// the maximum number of times it'll process something like
// nextTick(function f(){nextTick(f)})
@@ -312,13 +332,22 @@
process.maxTickDepth = 1000;
function tickDone(tickDepth_) {
- tickDepth = tickDepth_ || 0;
- nextTickQueue.splice(0, nextTickIndex);
- nextTickIndex = 0;
- inTick = false;
- if (nextTickQueue.length) {
- process._needTickCallback();
+ if (nextTickQueueLength !== 0) {
+ if (nextTickQueueLength <= nextTickIndex) {
+ nextTickQueue = [];
+ nextTickQueueLength = 0;
+ } else {
+ nextTickQueue.splice(0, nextTickIndex);
+ nextTickQueueLength = nextTickQueue.length;
+ if (needSpinner) {
+ _needTickCallback();
+ needSpinner = false;
+ }
+ }
}
+ inTick = false;
+ nextTickIndex = 0;
+ tickDepth = tickDepth_;
}
function maxTickWarn() {
@@ -332,26 +361,65 @@
console.error(msg);
}
- process._tickCallback = function(fromSpinner) {
+ function _tickFromSpinner() {
+ needSpinner = true;
+ // coming from spinner, reset!
+ if (tickDepth !== 0)
+ tickDepth = 0;
+ // no callbacks to run
+ if (nextTickQueueLength === 0)
+ return nextTickIndex = tickDepth = 0;
+ if (nextTickQueue[nextTickQueueLength - 1].domain)
+ _tickWDCallback();
+ else
+ _tickCallback();
+ }
+
+ // run callback that have no domain
+ // this is very hot, so has been super optimized
+ // this will be overridden if user uses domains
+ function _tickCallback() {
+ var callback, nextTickLength, threw;
+
+ if (inTick) return;
+ if (nextTickQueueLength === 0) {
+ nextTickIndex = 0;
+ tickDepth = 0;
+ return;
+ }
+ inTick = true;
+
+ while (tickDepth++ < process.maxTickDepth) {
+ nextTickLength = nextTickQueueLength;
+ if (nextTickIndex === nextTickLength)
+ return tickDone(0);
+
+ while (nextTickIndex < nextTickLength) {
+ callback = nextTickQueue[nextTickIndex++].callback;
+ threw = true;
+ try {
+ callback();
+ threw = false;
+ } finally {
+ if (threw) tickDone(tickDepth);
+ }
+ }
+ }
+
+ tickDone(0);
+ }
+
+ function _tickWDCallback() {
+ var nextTickLength, tock, callback, threw;
+
// if you add a nextTick in a domain's error handler, then
// it's possible to cycle indefinitely. Normally, the tickDone
// in the finally{} block below will prevent this, however if
// that error handler ALSO triggers multiple MakeCallbacks, then
// it'll try to keep clearing the queue, since the finally block
// fires *before* the error hits the top level and is handled.
- if (tickDepth >= process.maxTickDepth) {
- if (fromSpinner) {
- // coming in from the event queue. reset.
- tickDepth = 0;
- } else {
- if (nextTickQueue.length) {
- process._needTickCallback();
- }
- return;
- }
- }
-
- if (!nextTickQueue.length) return tickDone();
+ if (tickDepth >= process.maxTickDepth)
+ return _needTickCallback();
if (inTick) return;
inTick = true;
@@ -360,18 +428,19 @@
// is set to some negative value, or if there were repeated errors
// preventing tickDepth from being cleared, we'd never process any
// of them.
- do {
- tickDepth++;
- var nextTickLength = nextTickQueue.length;
- if (nextTickLength === 0) return tickDone();
+ while (tickDepth++ < process.maxTickDepth) {
+ nextTickLength = nextTickQueueLength;
+ if (nextTickIndex === nextTickLength)
+ return tickDone(0);
+
while (nextTickIndex < nextTickLength) {
- var tock = nextTickQueue[nextTickIndex++];
- var callback = tock.callback;
+ tock = nextTickQueue[nextTickIndex++];
+ callback = tock.callback;
if (tock.domain) {
if (tock.domain._disposed) continue;
tock.domain.enter();
}
- var threw = true;
+ threw = true;
try {
callback();
threw = false;
@@ -384,31 +453,31 @@
tock.domain.exit();
}
}
- nextTickQueue.splice(0, nextTickIndex);
- nextTickIndex = 0;
-
- // continue until the max depth or we run out of tocks.
- } while (tickDepth < process.maxTickDepth &&
- nextTickQueue.length > 0);
-
- tickDone();
- };
+ }
- process.nextTick = function(callback) {
- // on the way out, don't bother.
- // it won't get fired anyway.
- if (process._exiting) return;
+ tickDone(0);
+ }
+ function nextTick(callback) {
+ // on the way out, don't bother. it won't get fired anyway.
+ if (process._exiting)
+ return;
if (tickDepth >= process.maxTickDepth)
maxTickWarn();
- var tock = { callback: callback };
- if (process.domain) tock.domain = process.domain;
- nextTickQueue.push(tock);
- if (nextTickQueue.length) {
- process._needTickCallback();
+ var obj = { callback: callback };
+ if (process.domain !== null) {
+ obj.domain = process.domain;
+ // user has opt'd to use domains, so override default functionality
+ if (!usingDomains) {
+ process._tickCallback = _tickWDCallback;
+ usingDomains = true;
+ }
}
- };
+
+ nextTickQueue.push(obj);
+ nextTickQueueLength++;
+ }
};
function evalScript(name) {