Skip to content

Commit 3141f83

Browse files
authored
Merge pull request #14359 from emberjs/move-is-destroyed-flags
Refactor Meta and object destruction tracking.
2 parents e34ed31 + 5c0e590 commit 3141f83

File tree

8 files changed

+196
-118
lines changed

8 files changed

+196
-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: 99 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,10 @@ let members = {
5558
tag: ownCustomObject
5659
};
5760

61+
const SOURCE_DESTROYING = 1 << 1;
62+
const SOURCE_DESTROYED = 1 << 2;
63+
const META_DESTROYED = 1 << 3;
64+
5865
if (isEnabled('ember-glimmer-detect-backtracking-rerender') ||
5966
isEnabled('ember-glimmer-allow-backtracking-rerender')) {
6067
members.lastRendered = ownMap;
@@ -78,6 +85,10 @@ export function Meta(obj, parentMeta) {
7885
this._chains = undefined;
7986
this._tag = undefined;
8087

88+
// initial value for all flags right now is false
89+
// see FLAGS const for detailed list of flags used
90+
this._flags = 0;
91+
8192
// used only internally
8293
this.source = obj;
8394

@@ -104,11 +115,79 @@ Meta.prototype.isInitialized = function(obj) {
104115
return this.proto !== obj;
105116
};
106117

118+
const NODE_STACK = [];
119+
120+
Meta.prototype.destroy = function() {
121+
if (this.isMetaDestroyed()) { return; }
122+
123+
// remove chainWatchers to remove circular references that would prevent GC
124+
let node, nodes, key, nodeObject;
125+
node = this.readableChains();
126+
if (node) {
127+
NODE_STACK.push(node);
128+
// process tree
129+
while (NODE_STACK.length > 0) {
130+
node = NODE_STACK.pop();
131+
// push children
132+
nodes = node._chains;
133+
if (nodes) {
134+
for (key in nodes) {
135+
if (nodes[key] !== undefined) {
136+
NODE_STACK.push(nodes[key]);
137+
}
138+
}
139+
}
140+
141+
// remove chainWatcher in node object
142+
if (node._watching) {
143+
nodeObject = node._object;
144+
if (nodeObject) {
145+
let foreignMeta = peekMeta(nodeObject);
146+
// avoid cleaning up chain watchers when both current and
147+
// foreign objects are being destroyed
148+
// if both are being destroyed manual cleanup is not needed
149+
// as they will be GC'ed and no non-destroyed references will
150+
// be remaining
151+
if (foreignMeta && !foreignMeta.isSourceDestroying()) {
152+
removeChainWatcher(nodeObject, node._key, node, foreignMeta);
153+
}
154+
}
155+
}
156+
}
157+
}
158+
159+
this.setMetaDestroyed();
160+
};
161+
107162
for (let name in listenerMethods) {
108163
Meta.prototype[name] = listenerMethods[name];
109164
}
110165
memberNames.forEach(name => members[name](name, Meta));
111166

167+
Meta.prototype.isSourceDestroying = function isSourceDestroying() {
168+
return (this._flags & SOURCE_DESTROYING) !== 0;
169+
};
170+
171+
Meta.prototype.setSourceDestroying = function setSourceDestroying() {
172+
this._flags |= SOURCE_DESTROYING;
173+
};
174+
175+
Meta.prototype.isSourceDestroyed = function isSourceDestroyed() {
176+
return (this._flags & SOURCE_DESTROYED) !== 0;
177+
};
178+
179+
Meta.prototype.setSourceDestroyed = function setSourceDestroyed() {
180+
this._flags |= SOURCE_DESTROYED;
181+
};
182+
183+
Meta.prototype.isMetaDestroyed = function isMetaDestroyed() {
184+
return (this._flags & META_DESTROYED) !== 0;
185+
};
186+
187+
Meta.prototype.setMetaDestroyed = function setMetaDestroyed() {
188+
this._flags |= META_DESTROYED;
189+
};
190+
112191
// Implements a member that is a lazily created, non-inheritable
113192
// POJO.
114193
function ownMap(name, Meta) {
@@ -135,6 +214,8 @@ function inheritedMap(name, Meta) {
135214
let capitalized = capitalize(name);
136215

137216
Meta.prototype['write' + capitalized] = function(subkey, value) {
217+
assert(`Cannot call write${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
218+
138219
let map = this._getOrCreateOwnMap(key);
139220
map[subkey] = value;
140221
};
@@ -161,6 +242,8 @@ function inheritedMap(name, Meta) {
161242
};
162243

163244
Meta.prototype['clear' + capitalized] = function() {
245+
assert(`Cannot call clear${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
246+
164247
this[key] = undefined;
165248
};
166249

@@ -206,6 +289,8 @@ function inheritedMapOfMaps(name, Meta) {
206289
let capitalized = capitalize(name);
207290

208291
Meta.prototype['write' + capitalized] = function(subkey, itemkey, value) {
292+
assert(`Cannot call write${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
293+
209294
let outerMap = this._getOrCreateOwnMap(key);
210295
let innerMap = outerMap[subkey];
211296
if (!innerMap) {
@@ -277,6 +362,8 @@ function ownCustomObject(name, Meta) {
277362
let key = memberProperty(name);
278363
let capitalized = capitalize(name);
279364
Meta.prototype['writable' + capitalized] = function(create) {
365+
assert(`Cannot call writable${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
366+
280367
let ret = this[key];
281368
if (!ret) {
282369
ret = this[key] = create(this.source);
@@ -295,6 +382,8 @@ function inheritedCustomObject(name, Meta) {
295382
let key = memberProperty(name);
296383
let capitalized = capitalize(name);
297384
Meta.prototype['writable' + capitalized] = function(create) {
385+
assert(`Cannot call writable${capitalized} after the object is destroyed.`, !this.isMetaDestroyed());
386+
298387
let ret = this[key];
299388
if (!ret) {
300389
if (this.parent) {
@@ -376,7 +465,7 @@ const HAS_NATIVE_WEAKMAP = (function() {
376465
return Object.prototype.toString.call(instance) === '[object WeakMap]';
377466
})();
378467

379-
let setMeta, peekMeta, deleteMeta;
468+
let setMeta, peekMeta;
380469

381470
// choose the one appropriate for given platform
382471
if (HAS_NATIVE_WEAKMAP) {
@@ -412,14 +501,6 @@ if (HAS_NATIVE_WEAKMAP) {
412501
runInDebug(() => counters.peakPrototypeWalks++);
413502
}
414503
};
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-
};
423504
} else {
424505
setMeta = function Fallback_setMeta(obj, meta) {
425506
// if `null` already, just set it to the new value
@@ -438,13 +519,15 @@ if (HAS_NATIVE_WEAKMAP) {
438519
peekMeta = function Fallback_peekMeta(obj) {
439520
return obj[META_FIELD];
440521
};
522+
}
441523

442-
deleteMeta = function Fallback_deleteMeta(obj) {
443-
if (typeof obj[META_FIELD] !== 'object') {
444-
return;
445-
}
446-
obj[META_FIELD] = null;
447-
};
524+
export function deleteMeta(obj) {
525+
runInDebug(() => counters.deleteCalls++);
526+
527+
let meta = peekMeta(obj);
528+
if (meta) {
529+
meta.destroy();
530+
}
448531
}
449532

450533
/**
@@ -487,6 +570,5 @@ export function meta(obj) {
487570
export {
488571
peekMeta,
489572
setMeta,
490-
deleteMeta,
491573
counters
492574
};

0 commit comments

Comments
 (0)