diff options
author | Trevor Norris <trev.norris@gmail.com> | 2013-02-06 17:26:18 -0800 |
---|---|---|
committer | isaacs <i@izs.me> | 2013-02-15 18:13:01 -0800 |
commit | 86c0745a5ebd75a4a5fa69420dfde657a5e717bc (patch) | |
tree | 248ce9c77fe55a73991f45b3aa1928f8a06d1c59 /src | |
parent | 95ac576bf956055d3d5758cacbd336b53570f89f (diff) | |
download | node-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.cc | 158 | ||||
-rw-r--r-- | src/node.js | 165 |
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) { |