Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 60ecc72

Browse files
mvuksanojbdeboer
authored andcommitted
feat(scope): Use VmTurnZone.onScheduleMicrotask in Scope
and fix(scope): Use runAsync for microtasks in digest. BREAKING CHANGE: Microtasks scheduled in flush will process in current cycle, but they are not allowed to do model changes. Microtasks scheduled in digest will be executed in digest, counting towards the ScopeDigestTTL.
1 parent dd5f3ff commit 60ecc72

File tree

3 files changed

+140
-13
lines changed

3 files changed

+140
-13
lines changed

lib/core/scope.dart

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class ScopeEvent {
6565
@Injectable()
6666
class ScopeDigestTTL {
6767
final int ttl;
68-
ScopeDigestTTL(): ttl = 5;
68+
ScopeDigestTTL(): ttl = 10;
6969
ScopeDigestTTL.value(this.ttl);
7070
}
7171

@@ -657,6 +657,7 @@ class RootScope extends Scope {
657657
{
658658
_zone.onTurnDone = apply;
659659
_zone.onError = (e, s, ls) => _exceptionHandler(e, s);
660+
_zone.onScheduleMicrotask = runAsync;
660661
cacheRegister.registerCache("ScopeWatchASTs", astCache);
661662
}
662663

@@ -693,15 +694,8 @@ class RootScope extends Scope {
693694
ChangeLog changeLog;
694695
_scopeStats.digestStart();
695696
do {
696-
while (_runAsyncHead != null) {
697-
try {
698-
_runAsyncHead.fn();
699-
} catch (e, s) {
700-
_exceptionHandler(e, s);
701-
}
702-
_runAsyncHead = _runAsyncHead._next;
703-
}
704-
_runAsyncTail = null;
697+
698+
int asyncCount = _runAsyncFns();
705699

706700
digestTTL--;
707701
count = rootWatchGroup.detectChanges(
@@ -717,7 +711,7 @@ class RootScope extends Scope {
717711
digestLog = [];
718712
changeLog = (e, c, p) => digestLog.add('$e: $c <= $p');
719713
} else {
720-
log.add(digestLog.join(', '));
714+
log.add("${asyncCount > 0 ? 'async:$asyncCount' : ''}${digestLog.join(', ')}");
721715
digestLog.clear();
722716
}
723717
}
@@ -726,7 +720,7 @@ class RootScope extends Scope {
726720
'Last $LOG_COUNT iterations:\n${log.join('\n')}';
727721
}
728722
_scopeStats.digestLoop(count);
729-
} while (count > 0);
723+
} while (count > 0 || _runAsyncHead != null);
730724
} finally {
731725
_scopeStats.digestEnd();
732726
_transitionState(STATE_DIGEST, null);
@@ -769,7 +763,8 @@ class RootScope extends Scope {
769763
if (_domReadHead == null) _stats.domReadEnd();
770764
}
771765
_domReadTail = null;
772-
} while (_domWriteHead != null || _domReadHead != null);
766+
_runAsyncFns();
767+
} while (_domWriteHead != null || _domReadHead != null || _runAsyncHead != null);
773768
_stats.flushEnd();
774769
assert((() {
775770
_stats.flushAssertStart();
@@ -801,6 +796,9 @@ class RootScope extends Scope {
801796

802797
// QUEUES
803798
void runAsync(fn()) {
799+
if (_state == STATE_FLUSH_ASSERT) {
800+
throw "Scheduling microtasks not allowed in $state state.";
801+
}
804802
var chain = new _FunctionChain(fn);
805803
if (_runAsyncHead == null) {
806804
_runAsyncHead = _runAsyncTail = chain;
@@ -809,6 +807,21 @@ class RootScope extends Scope {
809807
}
810808
}
811809

810+
_runAsyncFns() {
811+
var count = 0;
812+
while (_runAsyncHead != null) {
813+
try {
814+
count++;
815+
_runAsyncHead.fn();
816+
} catch (e, s) {
817+
_exceptionHandler(e, s);
818+
}
819+
_runAsyncHead = _runAsyncHead._next;
820+
}
821+
_runAsyncTail = null;
822+
return count;
823+
}
824+
812825
void domWrite(fn()) {
813826
var chain = new _FunctionChain(fn);
814827
if (_domWriteHead == null) {

lib/core/zone.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class VmTurnZone {
6060
/// an "inner" [Zone], which is a child of the outer [Zone].
6161
async.Zone _innerZone;
6262

63+
6364
/**
6465
* Associates with this
6566
*

test/core/scope_spec.dart

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,20 @@ void main() {
12261226
});
12271227

12281228

1229+
it(r'should detect infinite digest through runAsync', (RootScope rootScope) {
1230+
rootScope.context['value'] = () { rootScope.runAsync(() {}); return 'a'; };
1231+
rootScope.watch('value()', (_, __) {});
1232+
1233+
expect(() {
1234+
rootScope.digest();
1235+
}).toThrow('Model did not stabilize in 5 digests. '
1236+
'Last 3 iterations:\n'
1237+
'async:1\n'
1238+
'async:1\n'
1239+
'async:1');
1240+
});
1241+
1242+
12291243
it(r'should always call the watchr with newVal and oldVal equal on the first run',
12301244
(RootScope rootScope) {
12311245
var log = [];
@@ -1439,6 +1453,105 @@ void main() {
14391453
});
14401454
});
14411455

1456+
describe('microtask processing', () {
1457+
beforeEach((VmTurnZone zone, RootScope scope, Logger log) {
1458+
var onTurnDone = zone.onTurnDone;
1459+
zone.onTurnDone = () {
1460+
log('[');
1461+
onTurnDone();
1462+
log(']');
1463+
};
1464+
var onScheduleMicrotask = zone.onScheduleMicrotask;
1465+
zone.onScheduleMicrotask = (fn) {
1466+
log('(');
1467+
try {
1468+
onScheduleMicrotask(fn);
1469+
} catch (e) {
1470+
log('CATCH: $e');
1471+
}
1472+
log(')');
1473+
};
1474+
});
1475+
1476+
it('should schedule apply after future resolution',
1477+
async((Logger log, VmTurnZone zone, RootScope scope) {
1478+
Completer completer;
1479+
zone.run(() {
1480+
completer = new Completer();
1481+
completer.future.then((value) {
1482+
log('then($value)');
1483+
});
1484+
});
1485+
1486+
scope.runAsync(() => log('before'));
1487+
log.clear();
1488+
completer.complete('OK'); // this one causes APPLY which processe 'before'
1489+
// This one schedules work but apply already run so it does not execute.
1490+
scope.runAsync(() => log('NOT_EXECUTED'));
1491+
1492+
expect(log).toEqual(['(', ')', '[', 'before', 'then(OK)', ']']);
1493+
})
1494+
);
1495+
1496+
it('should schedule microtask to runAsync queue during digest',
1497+
async((Logger log, VmTurnZone zone, RootScope scope) {
1498+
Completer completer;
1499+
zone.run(() {
1500+
completer = new Completer();
1501+
completer.future.
1502+
then((value) {
1503+
scope.runAsync(() => log('in(${scope.state})'));
1504+
return new Future.value(value);
1505+
}).
1506+
then((value) {
1507+
log('then($value)');
1508+
});
1509+
});
1510+
log.clear();
1511+
completer.complete('OK');
1512+
expect(log).toEqual(['(', ')', '[', '(', ')', 'in(digest)', 'then(OK)', ']']);
1513+
})
1514+
);
1515+
1516+
it('should allow microtasks in flush phase and process them immediatly',
1517+
async((Logger log, VmTurnZone zone, RootScope scope) {
1518+
scope.watch('g()', (_, __) {});
1519+
scope.context['g'] = () {
1520+
log('!');
1521+
return 0;
1522+
};
1523+
1524+
zone.run(() {
1525+
scope.domWrite(() {
1526+
log('domWriteA');
1527+
return new Future.value(null).then((_) => scope.domWrite(() => log('domWriteB')));
1528+
});
1529+
});
1530+
expect(log).toEqual(
1531+
['[', '!', '!', 'domWriteA', '(', ')', 'domWriteB', /* assert */'!', ']']);
1532+
})
1533+
);
1534+
1535+
it('should allow creation of Completers in flush phase',
1536+
async((Logger log, VmTurnZone zone, RootScope scope) {
1537+
Completer completer;
1538+
zone.run(() {
1539+
scope.domWrite(() {
1540+
log('new Completer');
1541+
completer = new Completer();
1542+
completer.future.then((value) {
1543+
log('then($value)');
1544+
});
1545+
});
1546+
});
1547+
log('=');
1548+
completer.complete('OK');
1549+
log(';');
1550+
expect(log).toEqual(
1551+
['[', 'new Completer', ']', '=', '(', ')', '[', 'then(OK)', ']', ';']);
1552+
})
1553+
);
1554+
});
14421555

14431556
describe('domRead/domWrite', () {
14441557
beforeEachModule((Module module) {

0 commit comments

Comments
 (0)