Skip to content
This repository was archived by the owner on Apr 15, 2025. It is now read-only.

Commit d22899a

Browse files
committed
fix(dirty-checking): fix removal of watch record on disconnected group.
1 parent a0d820c commit d22899a

File tree

2 files changed

+123
-16
lines changed

2 files changed

+123
-16
lines changed

lib/change_detection/dirty_checking_change_detector.dart

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,23 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
6868
return tail._recordTail;
6969
}
7070

71+
bool get isAttached {
72+
DirtyCheckingChangeDetectorGroup current = this;
73+
DirtyCheckingChangeDetectorGroup parent;
74+
while ((parent = current._parent) != null) {
75+
current = parent;
76+
}
77+
return current is DirtyCheckingChangeDetector
78+
? true
79+
: current._prev != null && current._next != null;
80+
}
81+
7182

7283
DirtyCheckingChangeDetector get _root {
7384
var root = this;
74-
var next;
75-
while ((next = root._parent) != null) {
76-
root = next;
85+
var parent;
86+
while ((parent = root._parent) != null) {
87+
root = parent;
7788
}
7889
return root is DirtyCheckingChangeDetector ? root : null;
7990
}
@@ -146,16 +157,12 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
146157
assert((root = _root) != null);
147158
assert(root._assertRecordsOk());
148159
DirtyCheckingRecord prevRecord = _recordHead._prevRecord;
149-
DirtyCheckingRecord nextRecord = _childInclRecordTail._nextRecord;
160+
var childInclRecordTail = _childInclRecordTail;
161+
DirtyCheckingRecord nextRecord = childInclRecordTail._nextRecord;
150162

151163
if (prevRecord != null) prevRecord._nextRecord = nextRecord;
152164
if (nextRecord != null) nextRecord._prevRecord = prevRecord;
153165

154-
var cursor = _recordHead;
155-
while (cursor != nextRecord) {
156-
cursor = cursor._nextRecord;
157-
}
158-
159166
var prevGroup = _prev;
160167
var nextGroup = _next;
161168

@@ -172,8 +179,7 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
172179
_parent = null;
173180
_prev = _next = null;
174181
_recordHead._prevRecord = null;
175-
_recordTail._nextRecord = null;
176-
_recordHead = _recordTail = null;
182+
childInclRecordTail._nextRecord = null;
177183
assert(root._assertRecordsOk());
178184
}
179185

@@ -222,8 +228,8 @@ class DirtyCheckingChangeDetectorGroup<H> implements ChangeDetectorGroup<H> {
222228
do {
223229
allRecords.add(record.toString());
224230
record = record._nextRecord;
225-
}
226-
while (record != includeChildrenTail);
231+
} while (record != includeChildrenTail);
232+
allRecords.add(includeChildrenTail);
227233
lines.add('FIELDS: ${allRecords.join(', ')}');
228234
}
229235

@@ -267,17 +273,42 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
267273
}
268274
var groupRecord = group._recordHead;
269275
var groupLast = group._recordTail;
270-
while (true) {
276+
if (record != groupRecord) {
277+
throw "Next record is $record expecting $groupRecord";
278+
}
279+
var done = false;
280+
while (!done && groupRecord != null) {
271281
if (groupRecord == record) {
282+
if (record._group != null && record._group != group) {
283+
throw "Wrong group: $record "
284+
"Got ${record._group} Expecting: $group";
285+
}
272286
record = record._nextRecord;
273287
} else {
274288
throw 'lost: $record found $groupRecord\n$this';
275289
}
276290

277-
if (groupRecord == groupLast) break;
291+
if (groupRecord._nextRecord != null &&
292+
groupRecord._nextRecord._prevRecord != groupRecord) {
293+
throw "prev/next pointer missmatch on "
294+
"$groupRecord -> ${groupRecord._nextRecord} "
295+
"<= ${groupRecord._nextRecord._prevRecord} in $this";
296+
}
297+
if (groupRecord._prevRecord != null &&
298+
groupRecord._prevRecord._nextRecord != groupRecord) {
299+
throw "prev/next pointer missmatch on "
300+
"$groupRecord -> ${groupRecord._prevRecord} "
301+
"<= ${groupRecord._prevRecord._nextChange} in $this";
302+
}
303+
if (groupRecord == groupLast) {
304+
done = true;
305+
}
278306
groupRecord = groupRecord._nextRecord;
279307
}
280308
}
309+
if(record != null) {
310+
throw "Extra records at tail: $record on $this";
311+
}
281312
return true;
282313
}
283314

test/change_detection/dirty_checking_change_detector_spec.dart

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import '../_specs.dart';
44
import 'package:angular/change_detection/change_detection.dart';
55
import 'package:angular/change_detection/dirty_checking_change_detector.dart';
66
import 'dart:collection';
7+
import 'dart:math';
78

89
void main() {
910
describe('DirtyCheckingChangeDetector', () {
1011
DirtyCheckingChangeDetector<String> detector;
12+
GetterCache getterCache;
1113

1214
beforeEach(() {
13-
GetterCache getterCache = new GetterCache({
15+
getterCache = new GetterCache({
1416
"first": (o) => o.first,
1517
"age": (o) => o.age
1618
});
@@ -170,6 +172,80 @@ void main() {
170172
var b = detector.newGroup();
171173
expect(detector.collectChanges).not.toThrow();
172174
});
175+
176+
it('should properly disconnect group in case watch is removed in disconected group', () {
177+
var map = {};
178+
var detector0 = new DirtyCheckingChangeDetector<String>(getterCache);
179+
var detector1 = detector0.newGroup();
180+
var detector2 = detector1.newGroup();
181+
var watch2 = detector2.watch(map, 'f1', null);
182+
var detector3 = detector0.newGroup();
183+
detector1.remove();
184+
watch2.remove(); // removing a dead record
185+
detector3.watch(map, 'f2', null);
186+
});
187+
188+
it('should find random bugs', () {
189+
List detectors;
190+
List records;
191+
List steps;
192+
var field = 'someField';
193+
step(text) {
194+
//print(text);
195+
steps.add(text);
196+
}
197+
Map map = {};
198+
var random = new Random();
199+
try {
200+
for (var i = 0; i < 100000; i++) {
201+
if (i % 50 == 0) {
202+
//print(steps);
203+
//print('===================================');
204+
records = [];
205+
steps = [];
206+
detectors = [new DirtyCheckingChangeDetector<String>(getterCache)];
207+
}
208+
switch (random.nextInt(4)) {
209+
case 0: // new child detector
210+
if (detectors.length > 10) break;
211+
var index = random.nextInt(detectors.length);
212+
ChangeDetectorGroup detector = detectors[index];
213+
step('detectors[$index].newGroup()');
214+
var child = detector.newGroup();
215+
detectors.add(child);
216+
break;
217+
case 1: // add watch
218+
var index = random.nextInt(detectors.length);
219+
ChangeDetectorGroup detector = detectors[index];
220+
step('detectors[$index].watch(map, field, null)');
221+
WatchRecord record = detector.watch(map, field, null);
222+
records.add(record);
223+
break;
224+
case 2: // destroy watch group
225+
if (detectors.length == 1) break;
226+
var index = random.nextInt(detectors.length - 1) + 1;
227+
ChangeDetectorGroup detector = detectors[index];
228+
step('detectors[$index].remove()');
229+
detector.remove();
230+
detectors = detectors
231+
.where((s) => s.isAttached)
232+
.toList();
233+
break;
234+
case 3: // remove watch on watch group
235+
if (records.length == 0) break;
236+
var index = random.nextInt(records.length);
237+
WatchRecord record = records.removeAt(index);
238+
step('records.removeAt($index).remove()');
239+
record.remove();
240+
break;
241+
}
242+
}
243+
} catch(e) {
244+
print(steps);
245+
rethrow;
246+
}
247+
});
248+
173249
});
174250

175251
describe('list watching', () {

0 commit comments

Comments
 (0)