refactor(VmTurnZone): outer zone = root zone

This commit is contained in:
Victor Berchet 2015-05-07 09:24:59 +02:00
parent fd1d60f03b
commit c75e216871
5 changed files with 108 additions and 201 deletions

View File

@ -29,12 +29,10 @@ class VmTurnZone {
// onTurnDone hook at the end of the current VM turn. // onTurnDone hook at the end of the current VM turn.
Zone _innerZone; Zone _innerZone;
// Number of microtasks pending from _outerZone (& descendants) // Number of microtasks pending from _innerZone (& descendants)
int _pendingMicrotasks = 0; int _pendingMicrotasks = 0;
// Whether some code has been executed in the _innerZone (& descendants) in the current turn // Whether some code has been executed in the _innerZone (& descendants) in the current turn
bool _hasExecutedCodeInInnerZone = false; bool _hasExecutedCodeInInnerZone = false;
// Whether the onTurnStart hook is executing
bool _inTurnStart = false;
// _outerRun() call depth. 0 at the end of a macrotask // _outerRun() call depth. 0 at the end of a macrotask
// zone.run(() => { // top-level call // zone.run(() => { // top-level call
// zone.run(() => {}); // nested call -> in-turn // zone.run(() => {}); // nested call -> in-turn
@ -44,36 +42,26 @@ class VmTurnZone {
/** /**
* Associates with this * Associates with this
* *
* - an "outer" [Zone], which is a child of the one that created this. * - an "outer" [Zone], which is a the one that created this.
* - an "inner" [Zone], which is a child of the outer [Zone]. * - an "inner" [Zone], which is a child of the outer [Zone].
* *
* @param {bool} enableLongStackTrace whether to enable long stack trace. They should only be * @param {bool} enableLongStackTrace whether to enable long stack trace. They should only be
* enabled in development mode as they significantly impact perf. * enabled in development mode as they significantly impact perf.
*/ */
VmTurnZone({bool enableLongStackTrace}) { VmTurnZone({bool enableLongStackTrace}) {
// The _outerZone captures microtask scheduling so that we can run onTurnDone when the queue _outerZone = Zone.current;
// is exhausted and code has been executed in the _innerZone.
if (enableLongStackTrace) { if (enableLongStackTrace) {
_outerZone = Chain.capture( _innerZone = Chain.capture(
() => _createOuterZone(Zone.current), () => _createInnerZone(Zone.current),
onError: _onErrorWithLongStackTrace); onError: _onErrorWithLongStackTrace);
} else { } else {
_outerZone = _createOuterZone( _innerZone = _createInnerZone(
Zone.current, Zone.current,
handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, error, StackTrace trace) => handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, error, StackTrace trace) =>
_onErrorWithoutLongStackTrace(error, trace) _onErrorWithoutLongStackTrace(error, trace)
); );
} }
// Instruments the inner [Zone] to detect when code is executed in this (or a descendant) zone.
// Also runs the onTurnStart hook the first time this zone executes some code in each turn.
_innerZone = _outerZone.fork(
specification: new ZoneSpecification(
run: _innerRun,
runUnary: _innerRunUnary,
runBinary: _innerRunBinary
),
zoneValues: {'_name': 'inner'});
} }
/** /**
@ -136,47 +124,28 @@ class VmTurnZone {
* ``` * ```
*/ */
dynamic runOutsideAngular(fn()) { dynamic runOutsideAngular(fn()) {
return _outerZone.runGuarded(fn); return _outerZone.run(fn);
} }
// Executes code in the [_innerZone] & trigger the onTurnStart hook when code is executed for the void _maybeStartVmTurn(ZoneDelegate parent) {
// first time in a turn.
dynamic _innerRun(Zone self, ZoneDelegate parent, Zone zone, fn()) {
_maybeStartVmTurn(parent, zone);
return parent.run(zone, fn);
}
dynamic _innerRunUnary(Zone self, ZoneDelegate parent, Zone zone, fn(arg), arg) {
_maybeStartVmTurn(parent, zone);
return parent.runUnary(zone, fn, arg);
}
dynamic _innerRunBinary(Zone self, ZoneDelegate parent, Zone zone, fn(arg1, arg2), arg1, arg2) {
_maybeStartVmTurn(parent, zone);
return parent.runBinary(zone, fn, arg1, arg2);
}
void _maybeStartVmTurn(ZoneDelegate parent, Zone zone) {
if (!_hasExecutedCodeInInnerZone) { if (!_hasExecutedCodeInInnerZone) {
_hasExecutedCodeInInnerZone = true; _hasExecutedCodeInInnerZone = true;
if (_onTurnStart != null) { if (_onTurnStart != null) {
_inTurnStart = true; parent.run(_innerZone, _onTurnStart);
parent.run(zone, _onTurnStart);
} }
} }
} }
dynamic _outerRun(Zone self, ZoneDelegate parent, Zone zone, fn()) { dynamic _run(Zone self, ZoneDelegate parent, Zone zone, fn()) {
try { try {
_nestedRun++; _nestedRun++;
_maybeStartVmTurn(parent);
return parent.run(zone, fn); return parent.run(zone, fn);
} finally { } finally {
_nestedRun--; _nestedRun--;
// If there are no more pending microtasks, we are at the end of a VM turn (or in onTurnStart) // If there are no more pending microtasks and we are not in a recursive call, this is the end of a turn
// _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are nested calls
// to _outerRun()).
if (_pendingMicrotasks == 0 && _nestedRun == 0) { if (_pendingMicrotasks == 0 && _nestedRun == 0) {
if (_onTurnDone != null && !_inTurnStart && _hasExecutedCodeInInnerZone) { if (_onTurnDone != null && _hasExecutedCodeInInnerZone) {
// Trigger onTurnDone at the end of a turn if _innerZone has executed some code // Trigger onTurnDone at the end of a turn if _innerZone has executed some code
try { try {
parent.run(_innerZone, _onTurnDone); parent.run(_innerZone, _onTurnDone);
@ -185,15 +154,14 @@ class VmTurnZone {
} }
} }
} }
_inTurnStart = false;
} }
} }
dynamic _outerRunUnary(Zone self, ZoneDelegate parent, Zone zone, fn(arg), arg) => dynamic _runUnary(Zone self, ZoneDelegate parent, Zone zone, fn(arg), arg) =>
_outerRun(self, parent, zone, () => fn(arg)); _run(self, parent, zone, () => fn(arg));
dynamic _outerRunBinary(Zone self, ZoneDelegate parent, Zone zone, fn(arg1, arg2), arg1, arg2) => dynamic _runBinary(Zone self, ZoneDelegate parent, Zone zone, fn(arg1, arg2), arg1, arg2) =>
_outerRun(self, parent, zone, () => fn(arg1, arg2)); _run(self, parent, zone, () => fn(arg1, arg2));
void _scheduleMicrotask(Zone self, ZoneDelegate parent, Zone zone, fn) { void _scheduleMicrotask(Zone self, ZoneDelegate parent, Zone zone, fn) {
_pendingMicrotasks++; _pendingMicrotasks++;
@ -226,16 +194,16 @@ class VmTurnZone {
} }
} }
Zone _createOuterZone(Zone zone, {handleUncaughtError}) { Zone _createInnerZone(Zone zone, {handleUncaughtError}) {
return zone.fork( return zone.fork(
specification: new ZoneSpecification( specification: new ZoneSpecification(
scheduleMicrotask: _scheduleMicrotask, scheduleMicrotask: _scheduleMicrotask,
run: _outerRun, run: _run,
runUnary: _outerRunUnary, runUnary: _runUnary,
runBinary: _outerRunBinary, runBinary: _runBinary,
handleUncaughtError: handleUncaughtError handleUncaughtError: handleUncaughtError
), ),
zoneValues: {'_name': 'outer'} zoneValues: {'_innerZone': true}
); );
} }
} }

View File

@ -27,8 +27,6 @@ export class VmTurnZone {
_pendingMicrotask: number; _pendingMicrotask: number;
// Whether some code has been executed in the _innerZone (& descendants) in the current turn // Whether some code has been executed in the _innerZone (& descendants) in the current turn
_hasExecutedCodeInInnerZone: boolean; _hasExecutedCodeInInnerZone: boolean;
// Whether the onTurnStart hook is executing
_inTurnStart: boolean;
// run() call depth in _outerZone. 0 at the end of a macrotask // run() call depth in _outerZone. 0 at the end of a macrotask
// zone.run(() => { // top-level call // zone.run(() => { // top-level call
// zone.run(() => {}); // nested call -> in-turn // zone.run(() => {}); // nested call -> in-turn
@ -51,11 +49,10 @@ export class VmTurnZone {
this._pendingMicrotasks = 0; this._pendingMicrotasks = 0;
this._hasExecutedCodeInInnerZone = false; this._hasExecutedCodeInInnerZone = false;
this._inTurnStart = false;
this._nestedRun = 0; this._nestedRun = 0;
this._outerZone = this._createOuterZone(global.zone); this._outerZone = global.zone;
this._innerZone = this._createInnerZone(this._outerZone, enableLongStackTrace); this._innerZone = this._createInnerZone(this._outerZone, enableLongStackTrace)
} }
/** /**
@ -109,50 +106,6 @@ export class VmTurnZone {
return this._outerZone.run(fn); return this._outerZone.run(fn);
} }
_createOuterZone(zone) {
var vmTurnZone = this;
return zone.fork({
_name: 'outer',
'$run': function(parentRun) {
return function() {
try {
vmTurnZone._nestedRun++;
return parentRun.apply(this, arguments);
} finally {
vmTurnZone._nestedRun--;
// If there are no more pending microtasks, we are at the end of a VM turn (or in onTurnStart)
// _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are nested calls
// to run()).
if (vmTurnZone._pendingMicrotasks == 0 && vmTurnZone._nestedRun == 0) {
if (vmTurnZone._onTurnDone && !vmTurnZone._inTurnStart && vmTurnZone._hasExecutedCodeInInnerZone) {
try {
parentRun.call(vmTurnZone._innerZone, vmTurnZone._onTurnDone);
} finally {
vmTurnZone._hasExecutedCodeInInnerZone = false;
}
}
}
vmTurnZone._inTurnStart = false;
}
}
},
'$scheduleMicrotask': function(parentScheduleMicrotask) {
return function(fn) {
vmTurnZone._pendingMicrotasks++;
var microtask = function() {
try {
fn();
} finally {
vmTurnZone._pendingMicrotasks--;
}
};
parentScheduleMicrotask.call(this, microtask);
}
}
});
}
_createInnerZone(zone, enableLongStackTrace) { _createInnerZone(zone, enableLongStackTrace) {
var vmTurnZone = this; var vmTurnZone = this;
var errorHandling; var errorHandling;
@ -174,28 +127,51 @@ export class VmTurnZone {
return zone return zone
.fork(errorHandling) .fork(errorHandling)
.fork({ .fork({
// Executes code in the _innerZone & trigger the onTurnStart hook when code is executed for the '$run': function(parentRun) {
// first time in a turn. return function() {
'$run': function (parentRun) { try {
return function () { vmTurnZone._nestedRun++;
vmTurnZone._maybeStartVmTurn() if (!vmTurnZone._hasExecutedCodeInInnerZone) {
return parentRun.apply(this, arguments) vmTurnZone._hasExecutedCodeInInnerZone = true;
if (vmTurnZone._onTurnStart) {
parentRun.call(vmTurnZone._innerZone, vmTurnZone._onTurnStart);
}
}
return parentRun.apply(this, arguments);
} finally {
vmTurnZone._nestedRun--;
// If there are no more pending microtasks, we are at the end of a VM turn (or in onTurnStart)
// _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are nested calls
// to run()).
if (vmTurnZone._pendingMicrotasks == 0 && vmTurnZone._nestedRun == 0) {
if (vmTurnZone._onTurnDone && vmTurnZone._hasExecutedCodeInInnerZone) {
try {
parentRun.call(vmTurnZone._innerZone, vmTurnZone._onTurnDone);
} finally {
vmTurnZone._hasExecutedCodeInInnerZone = false;
}
}
}
}
} }
}, },
_name: 'inner' '$scheduleMicrotask': function(parentScheduleMicrotask) {
return function(fn) {
vmTurnZone._pendingMicrotasks++;
var microtask = function() {
try {
fn();
} finally {
vmTurnZone._pendingMicrotasks--;
}
};
parentScheduleMicrotask.call(this, microtask);
}
},
_innerZone: true
}); });
} }
_maybeStartVmTurn(): void {
if (!this._hasExecutedCodeInInnerZone) {
this._hasExecutedCodeInInnerZone = true;
if (this._onTurnStart) {
this._inTurnStart = true;
this._outerZone.run.call(this._innerZone, this._onTurnStart);
}
}
}
_onError(zone, e): void { _onError(zone, e): void {
if (isPresent(this._onErrorHandler)) { if (isPresent(this._onErrorHandler)) {
var trace = [normalizeBlank(e.stack)]; var trace = [normalizeBlank(e.stack)];

View File

@ -226,4 +226,4 @@ String elementText(n) {
return DOM.getText(n); return DOM.getText(n);
} }
String getCurrentZoneName() => Zone.current['_name']; bool isInInnerZone() => Zone.current['_innerZone'] == true;

View File

@ -354,6 +354,6 @@ function elementText(n) {
return DOM.getText(n); return DOM.getText(n);
} }
function getCurrentZoneName(): string { export function isInInnerZone(): boolean {
return global.zone._name; return global.zone._innerZone === true;
} }

View File

@ -9,7 +9,8 @@ import {
it, it,
xdescribe, xdescribe,
xit, xit,
Log Log,
isInInnerZone
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {PromiseWrapper} from 'angular2/src/facade/async'; import {PromiseWrapper} from 'angular2/src/facade/async';
@ -19,10 +20,8 @@ import {BaseException} from 'angular2/src/facade/lang';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone'; import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
// Schedules a macrotask (using a timer) // Schedules a macrotask (using a timer)
// The code is executed in the outer zone to properly detect VM turns - in Dart VM turns could not be properly detected
// in the root zone because scheduleMicrotask() is not overriden.
function macroTask(fn: Function): void { function macroTask(fn: Function): void {
_zone.runOutsideAngular(() => PromiseWrapper.setTimeout(fn, 0)); _zone.runOutsideAngular(() => PromiseWrapper.setTimeout(fn, 1));
} }
// Schedules a microtasks (using a resolved promise .then()) // Schedules a microtasks (using a resolved promise .then())
@ -145,7 +144,16 @@ export function main() {
} }
function commonTests() { function commonTests() {
describe("run", () => { describe('isInInnerZone', () => {
it('should return whether the code executes in the inner zone', () => {
expect(isInInnerZone()).toEqual(false);
_zone.run(() => {
expect(isInInnerZone()).toEqual(true);
});
})
});
describe('run', () => {
it('should return the body return value from run', inject([AsyncTestCompleter], (async) => { it('should return the body return value from run', inject([AsyncTestCompleter], (async) => {
macroTask(() => { macroTask(() => {
expect(_zone.run(() => { expect(_zone.run(() => {
@ -175,7 +183,7 @@ function commonTests() {
macroTask(() => { macroTask(() => {
_zone.run(() => { _zone.run(() => {
_log.add('run start'); _log.add('run start');
microTask(() => { _log.add('async'); }); microTask(_log.fn('async'));
_log.add('run end'); _log.add('run end');
}); });
}); });
@ -194,7 +202,7 @@ function commonTests() {
_log.add('start run'); _log.add('start run');
_zone.run(() => { _zone.run(() => {
_log.add('nested run'); _log.add('nested run');
microTask(() => _log.add('nested run microtask')); microTask(_log.fn('nested run microtask'));
}); });
_log.add('end run'); _log.add('end run');
}); });
@ -210,15 +218,14 @@ function commonTests() {
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
macroTask(() => { macroTask(() => {
_zone.run(_log.fn('run1')); _zone.run(_log.fn('run1'));
});
macroTask(() => {
_zone.run(_log.fn('run2')); _zone.run(_log.fn('run2'));
}); });
macroTask(() => { macroTask(() => {
_zone.run(_log.fn('run3')); expect(_log.result()).toEqual('onTurnStart; run1; onTurnDone; onTurnStart; run2; onTurnDone');
});
macroTask(() => {
expect(_log.result()).toEqual('onTurnStart; run1; run2; onTurnDone; onTurnStart; run3; onTurnDone');
async.done(); async.done();
}); });
})); }));
@ -229,22 +236,21 @@ function commonTests() {
var b; var b;
macroTask(() => { macroTask(() => {
a = PromiseWrapper.completer();
b = PromiseWrapper.completer();
_zone.run(() => { _zone.run(() => {
a = PromiseWrapper.completer();
b = PromiseWrapper.completer();
_log.add('run start'); _log.add('run start');
a.promise.then((_) => { a.promise.then(_log.fn('a then'));
return _log.add('a then'); b.promise.then(_log.fn('b then'));
});
b.promise.then((_) => {
return _log.add('b then');
});
}); });
}); });
macroTask(() => { macroTask(() => {
a.resolve('a'); _zone.run(() => {
b.resolve('b'); a.resolve('a');
b.resolve('b');
});
}); });
macroTask(() => { macroTask(() => {
@ -276,9 +282,7 @@ function commonTests() {
macroTask(() => { macroTask(() => {
_zone.run(() => { _zone.run(() => {
completer.promise.then((_) => { completer.promise.then(_log.fn('executedMicrotask'));
_log.add('executedMicrotask');
});
}); });
}); });
@ -302,34 +306,6 @@ function commonTests() {
}); });
})); }));
it('should not call onTurnStart and onTurnDone when an outer microtask is scheduled from inside angular',
inject([AsyncTestCompleter], (async) => {
var completer;
macroTask(() => {
_zone.runOutsideAngular(() => {
completer = PromiseWrapper.completer();
completer.promise.then((_) => {
_log.add('executedMicrotask');
});
});
});
macroTask(() => {
_zone.run(() => {
_log.add('scheduling a microtask');
completer.resolve(null);
});
});
macroTask(() => {
expect(_log.result()).toEqual(
'onTurnStart; scheduling a microtask; executedMicrotask; onTurnDone'
);
async.done();
});
}));
it('should call onTurnStart before executing a microtask scheduled in onTurnDone as well as ' + it('should call onTurnStart before executing a microtask scheduled in onTurnDone as well as ' +
'onTurnDone after executing the task', inject([AsyncTestCompleter], (async) => { 'onTurnDone after executing the task', inject([AsyncTestCompleter], (async) => {
var ran = false; var ran = false;
@ -347,9 +323,7 @@ function commonTests() {
}}); }});
macroTask(() => { macroTask(() => {
_zone.run(() => { _zone.run(_log.fn('run'));
_log.add('run');
});
}); });
macroTask(() => { macroTask(() => {
@ -383,7 +357,7 @@ function commonTests() {
macroTask(() => { macroTask(() => {
_zone.run(() => { _zone.run(() => {
_log.add('scheduleMicrotask'); _log.add('scheduleMicrotask');
microTask(() => { _log.add('run(executeMicrotask)'); }); microTask(_log.fn('run(executeMicrotask)'));
}); });
}); });
@ -409,7 +383,7 @@ function commonTests() {
_log.add('onTurnStart(begin)'); _log.add('onTurnStart(begin)');
if (!startPromiseRan) { if (!startPromiseRan) {
_log.add('onTurnStart(schedulePromise)'); _log.add('onTurnStart(schedulePromise)');
microTask(() => { _log.add('onTurnStart(executePromise)'); }); microTask(_log.fn('onTurnStart(executePromise)'));
startPromiseRan = true; startPromiseRan = true;
} }
_log.add('onTurnStart(end)'); _log.add('onTurnStart(end)');
@ -418,7 +392,7 @@ function commonTests() {
_log.add('onTurnDone(begin)'); _log.add('onTurnDone(begin)');
if (!donePromiseRan) { if (!donePromiseRan) {
_log.add('onTurnDone(schedulePromise)'); _log.add('onTurnDone(schedulePromise)');
microTask(() => { _log.add('onTurnDone(executePromise)'); }); microTask(_log.fn('onTurnDone(executePromise)'));
donePromiseRan = true; donePromiseRan = true;
} }
_log.add('onTurnDone(end)'); _log.add('onTurnDone(end)');
@ -430,12 +404,10 @@ function commonTests() {
PromiseWrapper.resolve(null) PromiseWrapper.resolve(null)
.then((_) => { .then((_) => {
_log.add('promise then'); _log.add('promise then');
PromiseWrapper.resolve(null).then((_) => { _log.add('promise foo'); }); PromiseWrapper.resolve(null).then(_log.fn('promise foo'));
return PromiseWrapper.resolve(null); return PromiseWrapper.resolve(null);
}) })
.then((_) => { .then(_log.fn('promise bar'));
_log.add('promise bar');
});
_log.add('run end'); _log.add('run end');
}); });
}); });
@ -465,8 +437,8 @@ function commonTests() {
_zone.run(() => { _zone.run(() => {
completerA = PromiseWrapper.completer(); completerA = PromiseWrapper.completer();
completerB = PromiseWrapper.completer(); completerB = PromiseWrapper.completer();
completerA.promise.then((_) => _log.add('a then')); completerA.promise.then(_log.fn('a then'));
completerB.promise.then((_) => _log.add('b then')); completerB.promise.then(_log.fn('b then'));
_log.add('run start'); _log.add('run start');
}); });
}); });
@ -503,9 +475,7 @@ function commonTests() {
_log.add('run start'); _log.add('run start');
microTask(() => { microTask(() => {
_log.add('async1'); _log.add('async1');
microTask(() => { microTask(_log.fn('async2'));
_log.add('async2');
});
}); });
_log.add('run end'); _log.add('run end');
}); });
@ -521,26 +491,19 @@ function commonTests() {
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
var promise; var promise;
_zone.initCallbacks({
onTurnStart: _log.fn('onTurnStart'),
onTurnDone: _log.fn('onTurnDone')
});
macroTask(() => { macroTask(() => {
_zone.runOutsideAngular(() => { _zone.runOutsideAngular(() => {
promise = PromiseWrapper.resolve(4).then((x) => PromiseWrapper.resolve(x)); promise = PromiseWrapper.resolve(4).then((x) => PromiseWrapper.resolve(x));
}); });
_zone.run(() => { _zone.run(() => {
promise.then((_) => { promise.then(_log.fn('promise then'));
_log.add('promise then');
});
_log.add('zone run'); _log.add('zone run');
}); });
}); });
macroTask(() => { macroTask(() => {
expect(_log.result()).toEqual('onTurnStart; zone run; promise then; onTurnDone'); expect(_log.result()).toEqual('onTurnStart; zone run; onTurnDone; onTurnStart; promise then; onTurnDone');
async.done(); async.done();
}); });
})); }));