-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Refactor Meta and object destruction tracking. #14359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| } | ||
|
|
||
| if (obj.isDestroying) { return; } | ||
| if (m && m.sourceDestroying) { return; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i believe we may also bail of m is explicity null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya we can, but we don't ever set it to null manually anymore (used to be nulled out in deleteMeta).
packages/ember-metal/lib/meta.js
Outdated
| this._isDestroyed = false; | ||
|
|
||
| // keep the objects destruction state | ||
| this.sourceDestroying = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we just have a flags field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do that.
| }, | ||
|
|
||
| set(value) { | ||
| // prevent setting while applying mixins |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we allow this to be set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The guard here is to prevent an issue in our mixin apply code (we set the value before calling the descriptor#setup so the value being guarded against is this descriptor itself).
In general, I don't care about allowing this to be set outside of destroy and I can change this to throw if not being set by the quirk/bug in defineProperty mentioned above.
packages/ember-metal/lib/meta.js
Outdated
| }; | ||
|
|
||
| Meta.prototype.destroy = function() { | ||
| // clean up chain watchers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should do chain watchers cleanup here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is being done when this is called in ember-metal/watching's destroy function (which is the only place this is being called). I can move it here though if you'd prefer.
|
@krisselden thoughts on us getting clever \w a bitmask for these flags? |
|
@stefanpenner - Ya, that is what he was meaning in #14359 (comment) (and he DM'ed to explain). |
Ah, yup that makes sense. |
|
@stefanpenner it isn't just getting clever, it will be only one IC stub per read/write and places that check both isDestroying and isDestroyed can do it with one test. |
I wasn't implying "clever" negatively, I think in this situation it makes sense but clever it is. |
4feb560 to
3624127
Compare
|
Updated:
Ready for another round of reviews... |
| isDestroying: descriptor({ | ||
| get() { | ||
| let m = meta(this); | ||
| return (m._flags & SOURCE_DESTROYING) !== 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we hide the bitmasks behind functions?m.isSourceDestroying(); I believe it should come at little or no overhead, but be a-bit more maintainable.
Petka of bluebird fame took this approach, because those methods tend to inline quite nicely. Nice related blog post -> https://reaktor.com/blog/javascript-performance-fundamentals-make-bluebird-fast/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stefanpenner - Yeah, I started with that but then switched to using the flags directly in the source after discussing with @krisselden. I have swapped back to using methods for now (it is much easier to grok for those of us that don't understand the bitwise operations at a glance), but we need to confirm with @krisselden that this is not going to negatively impact the IC hit/miss stuff he has been investigating lately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm just old so it looks familiar but I don't find if (m._flags & SOURCE_DESTROYING) { unreadable or confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, same ... not sure it's worth it to take a hard line against bitmasks that aren't even publicly exposed.
3624127 to
2dc9334
Compare
|
@stefanpenner / @krisselden - Updated again. Exposes helper methods for the flags, and uses them throughout the source. Ready for another review... |
packages/ember-metal/lib/meta.js
Outdated
| if (node._watching) { | ||
| nodeObject = node._object; | ||
| if (nodeObject) { | ||
| removeChainWatcher(nodeObject, node._key, node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we pass in meta here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nevermind I realize this is the foreign object pointing at us
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, didn't we talk about not doing this if the object point at us was destroying or destroyed? the cycle doesn't matter the other object isn't going to be long lived.
|
@rwjblue / @stefanpenner should we black hole sets after destroy? It can hide bugs and still fail anything but it also causes a lot of guards. |
| this._chains = undefined; | ||
| this._tag = undefined; | ||
|
|
||
| // initial value for all flags right now is false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it help future travelers could we also make a comment here describing the layout of this bitfield?
|
|
||
| function notifyBeforeObservers(obj, keyName) { | ||
| if (obj.isDestroying) { return; } | ||
| function notifyBeforeObservers(obj, keyName, m) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about meta (instead of m) for consistency with dependentKeysDidChange and dependentKeysWillChange?
|
|
||
| function notifyObservers(obj, keyName) { | ||
| if (obj.isDestroying) { return; } | ||
| function notifyObservers(obj, keyName, m) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again m -> meta
2dc9334 to
bcf3acb
Compare
|
OK, updated again:
|
packages/ember-metal/lib/meta.js
Outdated
| const NODE_STACK = []; | ||
|
|
||
| Meta.prototype.destroy = function() { | ||
| // remove chainWatchers to remove circular references that would prevent GC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
destroy should be idempotent, should we not check the meta destroyed flag here and set it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point, updated to noop if isMetaDestroying is already set.
| if (nodeObject) { | ||
| let foreignMeta = peekMeta(nodeObject); | ||
| // avoid cleaning up chain watchers when both current and | ||
| // foreign objects are being destroyed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just comment here that marking a source as destroying means you are expected to not be holding onto a reference to the source from anything reachable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great point! Updated...
packages/ember-metal/lib/meta.js
Outdated
|
|
||
| function buildFlagAccessors(name, flag, Meta) { | ||
| let capitalized = capitalize(name); | ||
| Meta.prototype['is' + capitalized] = function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets just do normal methods here please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated!
packages/ember-metal/lib/meta.js
Outdated
| this._flags |= flag; | ||
| }; | ||
|
|
||
| Meta.prototype['unset' + capitalized] = function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this just for completeness? do we currently unset any flags?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I just did it for completeness. Will remove.
bcf3acb to
5c0e590
Compare
* 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`.
isDestroyingandisDestroyedflags into metaduring chain node removal)
assertion if non-readable meta methods are called after the object is
destroyed
is also destroyed