Skip to content

Commit bcf3acb

Browse files
author
Robert Jackson
committed
Refactor Meta and object destruction tracking.
* Moves the `isDestroying` and `isDestroyed` flags into meta * Prevents recreating meta _while_ destroying the object (was happening during chain node removal) * Allows reading values/caches after object destruction, but triggers an assertion if non-readable meta methods are called after the object is destroyed * Avoids doing extra work when unwatching keys/paths for an object that is also destroyed * Use bitwise flagging instead of many boolean properties for meta, and expose helpful methods to interact with them * Move chains cleanup to `Meta#destroy`.
1 parent 0271597 commit bcf3acb

File tree

8 files changed

+189
-118
lines changed

8 files changed

+189
-118
lines changed

packages/ember-metal/lib/chains.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,23 +116,23 @@ function addChainWatcher(obj, keyName, node) {
116116
watchKey(obj, keyName, m);
117117
}
118118

119-
function removeChainWatcher(obj, keyName, node) {
119+
function removeChainWatcher(obj, keyName, node, _meta) {
120120
if (!isObject(obj)) {
121121
return;
122122
}
123123

124-
let m = peekMeta(obj);
124+
let meta = _meta || peekMeta(obj);
125125

126-
if (!m || !m.readableChainWatchers()) {
126+
if (!meta || !meta.readableChainWatchers()) {
127127
return;
128128
}
129129

130130
// make meta writable
131-
m = metaFor(obj);
131+
meta = metaFor(obj);
132132

133-
m.readableChainWatchers().remove(keyName, node);
133+
meta.readableChainWatchers().remove(keyName, node);
134134

135-
unwatchKey(obj, keyName, m);
135+
unwatchKey(obj, keyName, meta);
136136
}
137137

138138
// A ChainNode watches a single key on an object. If you provide a starting

packages/ember-metal/lib/meta.js

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
import { EmptyObject, lookupDescriptor, symbol } from 'ember-utils';
66
import isEnabled from './features';
77
import { protoMethods as listenerMethods } from './meta_listeners';
8-
import { runInDebug } from './debug';
8+
import { runInDebug, assert } from './debug';
9+
import {
10+
removeChainWatcher
11+
} from './chains';
912

1013
let counters = {
1114
peekCalls: 0,
@@ -55,6 +58,12 @@ let members = {
5558
tag: ownCustomObject
5659
};
5760

61+
const FLAGS = {
62+
sourceDestroying: 1 << 1,
63+
sourceDestroyed: 1 << 2,
64+
metaDestroyed: 1 << 3
65+
};
66+
5867
if (isEnabled('ember-glimmer-detect-backtracking-rerender') ||
5968
isEnabled('ember-glimmer-allow-backtracking-rerender')) {
6069
members.lastRendered = ownMap;
@@ -78,6 +87,10 @@ export function Meta(obj, parentMeta) {
7887
this._chains = undefined;
7988
this._tag = undefined;
8089

90+
// initial value for all flags right now is false
91+
// see FLAGS const for detailed list of flags used
92+
this._flags = 0;
93+
8194
// used only internally
8295
this.source = obj;
8396

@@ -104,11 +117,70 @@ Meta.prototype.isInitialized = function(obj) {
104117
return this.proto !== obj;
105118
};
106119

120+
const NODE_STACK = [];
121+
122+
Meta.prototype.destroy = function() {
123+
// remove chainWatchers to remove circular references that would prevent GC
124+
125+
let node, nodes, key, nodeObject;
126+
node = this.readableChains();
127+
if (node) {
128+
NODE_STACK.push(node);
129+
// process tree
130+
while (NODE_STACK.length > 0) {
131+
node = NODE_STACK.pop();
132+
// push children
133+
nodes = node._chains;
134+
if (nodes) {
135+
for (key in nodes) {
136+
if (nodes[key] !== undefined) {
137+
NODE_STACK.push(nodes[key]);
138+
}
139+
}
140+
}
141+
142+
// remove chainWatcher in node object
143+
if (node._watching) {
144+
nodeObject = node._object;
145+
if (nodeObject) {
146+
let foreignMeta = peekMeta(nodeObject);
147+
// avoid cleaning up chain watchers when both current and
148+
// foreign objects are being destroyed
149+
if (foreignMeta && !foreignMeta.isSourceDestroying()) {
150+
removeChainWatcher(nodeObject, node._key, node, foreignMeta);
151+
}
152+
}
153+
}
154+
}
155+
}
156+
157+
this.setMetaDestroyed();
158+
};
159+
107160
for (let name in listenerMethods) {
108161
Meta.prototype[name] = listenerMethods[name];
109162
}
110163
memberNames.forEach(name => members[name](name, Meta));
111164

165+
for (let name in FLAGS) {
166+
buildFlagAccessors(name, FLAGS[name], Meta);
167+
}
168+
169+
function buildFlagAccessors(name, flag, Meta) {
170+
let capitalized = capitalize(name);
171+
Meta.prototype['is' + capitalized] = function() {
172+
return (this._flags & flag) !== 0;
173+
};
174+
175+
Meta.prototype['set' + capitalized] = function() {
176+
this._flags |= flag;
177+
};
178+
179+
Meta.prototype['unset' + capitalized] = function() {
180+
this._flags &= ~flag;
181+
};
182+
}
183+
112184
// Implements a member that is a lazily created, non-inheritable
113185
// POJO.
114186
function ownMap(name, Meta) {
@@ -135,6 +207,8 @@ function inheritedMap(name, Meta) {
135207
let capitalized = capitalize(name);
136208

137209
Meta.prototype['write' + capitalized] = function(subkey, value) {
210+
assert(`Cannot call write${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
211+
138212
let map = this._getOrCreateOwnMap(key);
139213
map[subkey] = value;
140214
};
@@ -161,6 +235,8 @@ function inheritedMap(name, Meta) {
161235
};
162236

163237
Meta.prototype['clear' + capitalized] = function() {
238+
assert(`Cannot call clear${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
239+
164240
this[key] = undefined;
165241
};
166242

@@ -206,6 +282,8 @@ function inheritedMapOfMaps(name, Meta) {
206282
let capitalized = capitalize(name);
207283

208284
Meta.prototype['write' + capitalized] = function(subkey, itemkey, value) {
285+
assert(`Cannot call write${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
286+
209287
let outerMap = this._getOrCreateOwnMap(key);
210288
let innerMap = outerMap[subkey];
211289
if (!innerMap) {
@@ -277,6 +355,8 @@ function ownCustomObject(name, Meta) {
277355
let key = memberProperty(name);
278356
let capitalized = capitalize(name);
279357
Meta.prototype['writable' + capitalized] = function(create) {
358+
assert(`Cannot call writable${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
359+
280360
let ret = this[key];
281361
if (!ret) {
282362
ret = this[key] = create(this.source);
@@ -295,6 +375,8 @@ function inheritedCustomObject(name, Meta) {
295375
let key = memberProperty(name);
296376
let capitalized = capitalize(name);
297377
Meta.prototype['writable' + capitalized] = function(create) {
378+
assert(`Cannot call writable${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
379+
298380
let ret = this[key];
299381
if (!ret) {
300382
if (this.parent) {
@@ -376,7 +458,7 @@ const HAS_NATIVE_WEAKMAP = (function() {
376458
return Object.prototype.toString.call(instance) === '[object WeakMap]';
377459
})();
378460

379-
let setMeta, peekMeta, deleteMeta;
461+
let setMeta, peekMeta;
380462

381463
// choose the one appropriate for given platform
382464
if (HAS_NATIVE_WEAKMAP) {
@@ -412,14 +494,6 @@ if (HAS_NATIVE_WEAKMAP) {
412494
runInDebug(() => counters.peakPrototypeWalks++);
413495
}
414496
};
415-
416-
deleteMeta = function WeakMap_deleteMeta(obj) {
417-
runInDebug(() => counters.deleteCalls++);
418-
419-
// set value to `null` so that we can detect
420-
// a deleted meta in peekMeta later
421-
metaStore.set(obj, null);
422-
};
423497
} else {
424498
setMeta = function Fallback_setMeta(obj, meta) {
425499
// if `null` already, just set it to the new value
@@ -438,13 +512,15 @@ if (HAS_NATIVE_WEAKMAP) {
438512
peekMeta = function Fallback_peekMeta(obj) {
439513
return obj[META_FIELD];
440514
};
515+
}
441516

442-
deleteMeta = function Fallback_deleteMeta(obj) {
443-
if (typeof obj[META_FIELD] !== 'object') {
444-
return;
445-
}
446-
obj[META_FIELD] = null;
447-
};
517+
export function deleteMeta(obj) {
518+
runInDebug(() => counters.deleteCalls++);
519+
520+
let meta = peekMeta(obj);
521+
if (meta) {
522+
meta.destroy();
523+
}
448524
}
449525

450526
/**
@@ -487,6 +563,5 @@ export function meta(obj) {
487563
export {
488564
peekMeta,
489565
setMeta,
490-
deleteMeta,
491566
counters
492567
};

0 commit comments

Comments
 (0)