Skip to content

Conversation

@rwjblue
Copy link
Member

@rwjblue rwjblue commented Sep 26, 2016

  • 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

}

if (obj.isDestroying) { return; }
if (m && m.sourceDestroying) { return; }
Copy link
Member

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?

Copy link
Member Author

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).

this._isDestroyed = false;

// keep the objects destruction state
this.sourceDestroying = false;
Copy link
Contributor

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?

Copy link
Member Author

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
Copy link
Contributor

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?

Copy link
Member Author

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.

};

Meta.prototype.destroy = function() {
// clean up chain watchers
Copy link
Contributor

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

Copy link
Member Author

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.

@stefanpenner
Copy link
Member

@krisselden thoughts on us getting clever \w a bitmask for these flags?

@rwjblue
Copy link
Member Author

rwjblue commented Sep 26, 2016

@stefanpenner - Ya, that is what he was meaning in #14359 (comment) (and he DM'ed to explain).

@stefanpenner
Copy link
Member

@stefanpenner - Ya, that is what he was meaning in #14359 (comment) (and he DM'ed to explain).

Ah, yup that makes sense.

@krisselden
Copy link
Contributor

@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.

@stefanpenner
Copy link
Member

stefanpenner commented Sep 27, 2016

@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.

@rwjblue rwjblue force-pushed the move-is-destroyed-flags branch from 4feb560 to 3624127 Compare September 27, 2016 02:00
@rwjblue
Copy link
Member Author

rwjblue commented Sep 27, 2016

Updated:

  • Uses bitwise logic to track multiple flags in a single field
  • Moves chain node watcher cleanup into Meta#destroy
  • Throw an error when someone manually sets isDestroyed / isDestroying

Ready for another round of reviews...

isDestroying: descriptor({
get() {
let m = meta(this);
return (m._flags & SOURCE_DESTROYING) !== 0;
Copy link
Member

@stefanpenner stefanpenner Sep 27, 2016

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/

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member

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.

@rwjblue rwjblue force-pushed the move-is-destroyed-flags branch from 3624127 to 2dc9334 Compare September 27, 2016 10:26
@rwjblue
Copy link
Member Author

rwjblue commented Sep 27, 2016

@stefanpenner / @krisselden - Updated again. Exposes helper methods for the flags, and uses them throughout the source. Ready for another review...

if (node._watching) {
nodeObject = node._object;
if (nodeObject) {
removeChainWatcher(nodeObject, node._key, node);
Copy link
Contributor

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?

Copy link
Contributor

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

Copy link
Contributor

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.

@krisselden
Copy link
Contributor

@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
Copy link
Member

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) {
Copy link
Member

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again m -> meta

@rwjblue rwjblue force-pushed the move-is-destroyed-flags branch from 2dc9334 to bcf3acb Compare September 27, 2016 17:36
@rwjblue
Copy link
Member Author

rwjblue commented Sep 27, 2016

OK, updated again:

  • Rename single char variables to better names
  • Prevent cleaning up chain nodes when both current object and foreign object are being destroyed
  • Add comment describing flags

const NODE_STACK = [];

Meta.prototype.destroy = function() {
// remove chainWatchers to remove circular references that would prevent GC
Copy link
Contributor

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?

Copy link
Member Author

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
Copy link
Contributor

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point! Updated...


function buildFlagAccessors(name, flag, Meta) {
let capitalized = capitalize(name);
Meta.prototype['is' + capitalized] = function() {
Copy link
Contributor

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

this._flags |= flag;
};

Meta.prototype['unset' + capitalized] = function() {
Copy link
Contributor

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?

Copy link
Member Author

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.

@rwjblue rwjblue force-pushed the move-is-destroyed-flags branch from bcf3acb to 5c0e590 Compare September 27, 2016 21:56
* 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`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants