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

Commit 75fbded

Browse files
committed
fix(change-detection): remove memory leak, use iterator
When the change detection finds change it creates a linked list of records which have changed. The linked list is not bound to groups but rather crosses groups, this implies that if we remove a group it is possible that a record in a live group retains a pointer to a record in the now deleted record. While technically not a leak, the memory will not be realized until that record detects another change and gets re-added to the dirty list. If the record never fires than the memory is retained forever.
1 parent 5b1416b commit 75fbded

File tree

6 files changed

+223
-161
lines changed

6 files changed

+223
-161
lines changed

lib/change_detection/ast.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ String _argList(List<AST> items) => items.join(', ');
123123
/**
124124
* The name is a bit oxymoron, but it is essentially the NullObject pattern.
125125
*
126-
* This allows children to set a handler on this ChangeRecord and then let it write the initial
127-
* constant value to the forwarding ChangeRecord.
126+
* This allows children to set a handler on this Record and then let it write the initial
127+
* constant value to the forwarding Record.
128128
*/
129129
class _ConstantWatchRecord extends WatchRecord<_Handler> {
130130
final currentValue;
@@ -134,7 +134,7 @@ class _ConstantWatchRecord extends WatchRecord<_Handler> {
134134
: currentValue = currentValue,
135135
handler = new _ConstantHandler(watchGroup, expression, currentValue);
136136

137-
ChangeRecord<_Handler> check() => null;
137+
bool check() => false;
138138
void remove() => null;
139139

140140
get field => null;

lib/change_detection/change_detection.dart

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ abstract class ChangeDetectorGroup<H> {
2323
* Parameters:
2424
* - [object] to watch.
2525
* - [field] to watch on the [object].
26-
* - [handler] an opaque object passed on to [ChangeRecord].
26+
* - [handler] an opaque object passed on to [Record].
2727
*/
2828
WatchRecord<H> watch(Object object, String field, H handler);
2929

@@ -43,18 +43,18 @@ abstract class ChangeDetectorGroup<H> {
4343
* predictable performance, and the developer can implement `.equals()` on top
4444
* of identity checks.
4545
*
46-
* - [H] A [ChangeRecord] has associated handler object. The handler object is
46+
* - [H] A [Record] has associated handler object. The handler object is
4747
* opaque to the [ChangeDetector] but it is meaningful to the code which
4848
* registered the watcher. It can be a data structure, an object, or a function.
4949
* It is up to the developer to attach meaning to it.
5050
*/
5151
abstract class ChangeDetector<H> extends ChangeDetectorGroup<H> {
5252
/**
5353
* This method does the work of collecting the changes and returns them as a
54-
* linked list of [ChangeRecord]s. The [ChangeRecord]s are returned in the
54+
* linked list of [Record]s. The [Record]s are returned in the
5555
* same order as they were registered.
5656
*/
57-
ChangeRecord<H> collectChanges({ EvalExceptionHandler exceptionHandler,
57+
Iterator<Record<H>> collectChanges({ EvalExceptionHandler exceptionHandler,
5858
AvgStopwatch stopwatch });
5959
}
6060

@@ -93,24 +93,14 @@ abstract class WatchRecord<H> extends Record<H> {
9393
set object(value);
9494

9595
/**
96-
* Check to see if the field on the object has changed. Returns [null] if no
97-
* change, or a [ChangeRecord] if a change has been detected.
96+
* Check to see if the field on the object has changed. Returns [true] if
97+
* change has been detected.
9898
*/
99-
ChangeRecord<H> check();
99+
bool check();
100100

101101
void remove();
102102
}
103103

104-
/**
105-
* Provides information about the changes which were detected in objects.
106-
*
107-
* It exposes a `nextChange` method for traversing all of the changes.
108-
*/
109-
abstract class ChangeRecord<H> extends Record<H> {
110-
/** Next [ChangeRecord] */
111-
ChangeRecord<H> get nextChange;
112-
}
113-
114104
/**
115105
* If the [ChangeDetector] is watching a [Map] then the [currentValue] of
116106
* [Record] will contain an instance of this object. A [MapChangeRecord]

lib/change_detection/dirty_checking_change_detector.dart

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,6 @@ class GetterCache {
3737
* Each property to be watched is recorded as a [DirtyCheckingRecord] and kept
3838
* in a linked list. Linked list are faster than Arrays for iteration. They also
3939
* allow removal of large blocks of watches in an efficient manner.
40-
*
41-
* [ChangeRecord]
42-
*
43-
* When the results are delivered they are a linked list of [ChangeRecord]s. For
44-
* efficiency reasons the [DirtyCheckingRecord] and [ChangeRecord] are two
45-
* different interfaces for the same underlying object this makes reporting
46-
* efficient since no additional memory allocation is performed.
4740
*/
4841
class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
4942
/**
@@ -177,6 +170,10 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
177170
nextGroup._prev = prevGroup;
178171
}
179172
_parent = null;
173+
_prev = _next = null;
174+
_recordHead._prevRecord = null;
175+
_recordTail._nextRecord = null;
176+
_recordHead = _recordTail = null;
180177
assert(root._assertRecordsOk());
181178
}
182179

@@ -281,7 +278,7 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
281278
return true;
282279
}
283280

284-
DirtyCheckingRecord<H> collectChanges({ EvalExceptionHandler exceptionHandler,
281+
Iterator<Record<H>> collectChanges({ EvalExceptionHandler exceptionHandler,
285282
AvgStopwatch stopwatch}) {
286283
if (stopwatch != null) stopwatch.start();
287284
DirtyCheckingRecord changeHead = null;
@@ -291,11 +288,11 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
291288
int count = 0;
292289
while (current != null) {
293290
try {
294-
if (current.check() != null) {
291+
if (current.check()) {
295292
if (changeHead == null) {
296293
changeHead = changeTail = current;
297294
} else {
298-
changeTail = changeTail.nextChange = current;
295+
changeTail = changeTail._nextChange = current;
299296
}
300297
}
301298
if (stopwatch != null) count++;
@@ -308,16 +305,39 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
308305
}
309306
current = current._nextRecord;
310307
}
311-
if (changeTail != null) changeTail.nextChange = null;
308+
if (changeTail != null) changeTail._nextChange = null;
312309
if (stopwatch != null) stopwatch..stop()..increment(count);
313-
return changeHead;
310+
return new _ChangeIterator(changeHead);
314311
}
315312

316313
void remove() {
317314
throw new StateError('Root ChangeDetector can not be removed');
318315
}
319316
}
320317

318+
class _ChangeIterator<H> implements Iterator<Record<H>>{
319+
DirtyCheckingRecord _current;
320+
DirtyCheckingRecord _next;
321+
DirtyCheckingRecord get current => _current;
322+
323+
_ChangeIterator(this._next);
324+
325+
bool moveNext() {
326+
_current = _next;
327+
if (_next != null) {
328+
_next = _current._nextChange;
329+
/*
330+
* This is important to prevent memory leaks. If we don't reset then
331+
* a record maybe pointing to a deleted change detector group and it
332+
* will not release the reference until it fires again. So we have
333+
* to be eager about releasing references.
334+
*/
335+
_current._nextChange = null;
336+
}
337+
return _current != null;
338+
}
339+
}
340+
321341
/**
322342
* [DirtyCheckingRecord] represents as single item to check. The heart of the
323343
* [DirtyCheckingRecord] is a the [check] method which can read the
@@ -327,7 +347,7 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
327347
* removing efficient. [DirtyCheckingRecord] also has a [nextChange] field which
328348
* creates a single linked list of all of the changes for efficient traversal.
329349
*/
330-
class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> {
350+
class DirtyCheckingRecord<H> implements Record<H>, WatchRecord<H> {
331351
static const List<String> _MODE_NAMES =
332352
const ['MARKER', 'IDENT', 'REFLECT', 'GETTER', 'MAP[]', 'ITERABLE', 'MAP'];
333353
static const int _MODE_MARKER_ = 0;
@@ -350,7 +370,7 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> {
350370
var currentValue;
351371
DirtyCheckingRecord<H> _nextRecord;
352372
DirtyCheckingRecord<H> _prevRecord;
353-
ChangeRecord<H> nextChange;
373+
Record<H> _nextChange;
354374
var _object;
355375
InstanceMirror _instanceMirror;
356376

@@ -416,12 +436,12 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> {
416436
}
417437
}
418438

419-
ChangeRecord<H> check() {
439+
bool check() {
420440
assert(_mode != null);
421441
var current;
422442
switch (_mode) {
423443
case _MODE_MARKER_:
424-
return null;
444+
return false;
425445
case _MODE_REFLECT_:
426446
current = _instanceMirror.getField(_symbol).reflectee;
427447
break;
@@ -435,9 +455,9 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> {
435455
current = object;
436456
break;
437457
case _MODE_MAP_:
438-
return (currentValue as _MapChangeRecord)._check(object) ? this : null;
458+
return (currentValue as _MapChangeRecord)._check(object);
439459
case _MODE_ITERABLE_:
440-
return (currentValue as _CollectionChangeRecord)._check(object) ? this : null;
460+
return (currentValue as _CollectionChangeRecord)._check(object);
441461
default:
442462
assert(false);
443463
}
@@ -454,10 +474,10 @@ class DirtyCheckingRecord<H> implements ChangeRecord<H>, WatchRecord<H> {
454474
} else {
455475
previousValue = last;
456476
currentValue = current;
457-
return this;
477+
return true;
458478
}
459479
}
460-
return null;
480+
return false;
461481
}
462482

463483

lib/change_detection/linked_list.dart

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,19 @@ abstract class _EvalWatchList {
9292

9393
static _EvalWatchRecord _add(_EvalWatchList list, _EvalWatchRecord item) {
9494
assert(item._nextEvalWatch == null);
95-
assert(item._previousEvalWatch == null);
95+
assert(item._prevEvalWatch == null);
9696
var prev = list._evalWatchTail;
9797
var next = prev._nextEvalWatch;
9898

9999
if (prev == list._marker) {
100100
list._evalWatchHead = list._evalWatchTail = item;
101-
prev = prev._previousEvalWatch;
101+
prev = prev._prevEvalWatch;
102102
}
103103
item._nextEvalWatch = next;
104-
item._previousEvalWatch = prev;
104+
item._prevEvalWatch = prev;
105105

106106
if (prev != null) prev._nextEvalWatch = item;
107-
if (next != null) next._previousEvalWatch = item;
107+
if (next != null) next._prevEvalWatch = item;
108108

109109
return list._evalWatchTail = item;
110110
}
@@ -113,21 +113,21 @@ abstract class _EvalWatchList {
113113

114114
static void _remove(_EvalWatchList list, _EvalWatchRecord item) {
115115
assert(item.watchGrp == list);
116-
var prev = item._previousEvalWatch;
116+
var prev = item._prevEvalWatch;
117117
var next = item._nextEvalWatch;
118118

119119
if (list._evalWatchHead == list._evalWatchTail) {
120120
list._evalWatchHead = list._evalWatchTail = list._marker;
121121
list._marker
122122
.._nextEvalWatch = next
123-
.._previousEvalWatch = prev;
123+
.._prevEvalWatch = prev;
124124
if (prev != null) prev._nextEvalWatch = list._marker;
125-
if (next != null) next._previousEvalWatch = list._marker;
125+
if (next != null) next._prevEvalWatch = list._marker;
126126
} else {
127127
if (item == list._evalWatchHead) list._evalWatchHead = next;
128128
if (item == list._evalWatchTail) list._evalWatchTail = prev;
129129
if (prev != null) prev._nextEvalWatch = next;
130-
if (next != null) next._previousEvalWatch = prev;
130+
if (next != null) next._prevEvalWatch = prev;
131131
}
132132
}
133133
}
@@ -137,11 +137,11 @@ class _WatchGroupList {
137137

138138
static WatchGroup _add(_WatchGroupList list, WatchGroup item) {
139139
assert(item._nextWatchGroup == null);
140-
assert(item._previousWatchGroup == null);
140+
assert(item._prevWatchGroup == null);
141141
if (list._watchGroupTail == null) {
142142
list._watchGroupHead = list._watchGroupTail = item;
143143
} else {
144-
item._previousWatchGroup = list._watchGroupTail;
144+
item._prevWatchGroup = list._watchGroupTail;
145145
list._watchGroupTail._nextWatchGroup = item;
146146
list._watchGroupTail = item;
147147
}
@@ -151,10 +151,10 @@ class _WatchGroupList {
151151
static bool _isEmpty(_WatchGroupList list) => list._watchGroupHead == null;
152152

153153
static void _remove(_WatchGroupList list, WatchGroup item) {
154-
var previous = item._previousWatchGroup;
154+
var previous = item._prevWatchGroup;
155155
var next = item._nextWatchGroup;
156156

157157
if (previous == null) list._watchGroupHead = next; else previous._nextWatchGroup = next;
158-
if (next == null) list._watchGroupTail = previous; else next._previousWatchGroup = previous;
158+
if (next == null) list._watchGroupTail = previous; else next._prevWatchGroup = previous;
159159
}
160160
}

0 commit comments

Comments
 (0)