Skip to content
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

Destroyables #580

Merged
merged 12 commits into from
Apr 21, 2020
Merged

Destroyables #580

merged 12 commits into from
Apr 21, 2020

Conversation

pzuraq
Copy link
Contributor

@pzuraq pzuraq commented Jan 11, 2020

Copy link
Member

@rwjblue rwjblue left a comment

Choose a reason for hiding this comment

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

This is really great, thank you for working on it!

I think we may need a couple additional things to allow for introspection:

  • isDestroying(obj)
  • isDestroyed(obj)

IMHO, these are somewhat important so that we can "mark" all of the graph that will be destroyed before calling all of the registered destructors and that the object itself can determine if it is in a destroying state or not before doing any work.

text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
@rwjblue
Copy link
Member

rwjblue commented Jan 13, 2020

@pzuraq - Another feature that ember-lifeline has, that is pretty nice (and we would either have to drop there or not be able to migrate to this builtin API) is that we can assert that all destroyables that were ever registered have been ran. This is used in testing infrastructure (see the documentation here) to ensure that folks don't accidentally forget to properly destroy(obj) something that they should have (the assumption here is that at the end of the test, all destroyables that were created during the test are ran).

Co-Authored-By: Robert Jackson <me@rwjblue.com>
text/0580-destroyables.md Outdated Show resolved Hide resolved
Copy link
Member

@rwjblue rwjblue left a comment

Choose a reason for hiding this comment

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

Thanks for the updates here @pzuraq, looks like great progress!

text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Show resolved Hide resolved

- Calling `destroy` on a non-destroyable should throw an error.
1. Mark the destroyable such that `isDestroying(destroyable)` returns `true`
Copy link
Member

Choose a reason for hiding this comment

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

Does it also mark each of the children?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does while calling destroy() on the children, since it recurses. It does not mark them as destroying before running its own destructors.

text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
text/0580-destroyables.md Outdated Show resolved Hide resolved
@rwjblue rwjblue added the T-framework RFCs that impact the ember.js library label Mar 4, 2020
@rwjblue
Copy link
Member

rwjblue commented Apr 10, 2020

After discussing this in the meeting today, we are very excited to move this RFC into final comment period!

@ming-codes
Copy link

Given how @pzuraq loves decorators, I’m surprised to see there are no mention of decorators in this RFC 😏

@BryanCrotaz
Copy link

Suggestion. Create a codemod to convert willDestroy to using destroyables instead

Comment on lines +245 to +246
- Calling `unregisterDestructor` with a destructor that is not associated with
the object should throw an error.
Copy link
Contributor

@buschtoens buschtoens Apr 12, 2020

Choose a reason for hiding this comment

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

If calling unregisterDestructor with a non-registered destructor throws an error, and there is no function to check whether a destructor is associated with a destroyable, it means that users need to implicitly or explicitly bookkeep what destructors were registered with what destroyables.

I like that it throws an error, but I would recommend to expose another function like isDestructorRegistered(obj, destructor) / hasDestructor(obj, destructor).

Comment on lines +280 to +281
Calling `destroy` with a destroyable that has no destructors or associated children
will not throw an error, and will do nothing.
Copy link
Contributor

Choose a reason for hiding this comment

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

Will isDestroying / isDestroyed still return true though?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it will.

Comment on lines +267 to +275
1. Mark the destroyable such that `isDestroying(destroyable)` returns `true`
2. Schedule calling the destroyable's destructors
3. Call `destroy()` on each of the destroyable's associated children
4. Schedule setting destroyable such that `isDestroyed(destroyable)` returns `true`

This algorithm results in the entire tree of destroyables being first marked as
destroying, then having all of their destructors called, and finally all being
marked as `isDestroyed`. There won't be any in between states where some items
are marked as `isDestroying` while destroying, while others are not.
Copy link
Contributor

@buschtoens buschtoens Apr 12, 2020

Choose a reason for hiding this comment

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

I overlooked the keyword Schedule, meaning that the action happens asynchronously / next tick / etc. and thought that this algorithm actually results in marking the parent, calling the parent destructors already and only then marking the children and calling the children's destructors.

Just adding this here, in case I am not the only one.

Do we wanna make any guarantees with regards to the timing of scheduling? Is it scheduled onto a specific runloop queue (destroy?), next tick, or no guarantees on that at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We do not want to make guarantees about timing, as timing could change per-environment, or could change in the future. The only guarantee is relative timing, as outlined in this RFC.

}
```

- Calling `unregisterDestructor` on a destroyed object should throw an error.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
- Calling `unregisterDestructor` on a destroyed object should throw an error.
- Calling `unregisterDestructor` on a destroyed object or object that is being destroyed should throw an error.

Is it legal to unregister a destructor from an object while it is being destroyed (isDestroying)?

Comment on lines +314 to +318
This function asserts that all objects which have associated destructors or
associated children have been destroyed at the time it is called. It is meant to
be a low level hook that testing frameworks like `ember-qunit` and `ember-mocha`
can use to hook into and validate that all destroyables have in fact been
destroyed.
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be helpful to know which objects did not finish destroying in this case. The objets could be exposed as a property on the error.

Copy link
Contributor

Choose a reason for hiding this comment

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

In my naive polyfill implementation, this function leaks state / failures between test runs, as there is no way to reset the hidden module state: ember-polyfills/ember-destroyable-polyfill#2

test('should fail', function () {
  const obj = {};
  registerDestructor(obj, () => {});
  // run(() => destroy(obj));
  assertDestroyablesDestroyed();
});

test('should pass, but fails', function () {
  const obj = {};
  registerDestructor(obj, () => {});
  run(() => destroy(obj));
  assertDestroyablesDestroyed();
});

How are ember-qunit / ember-mocha supposed to reset the module state in @ember/destroyable after each test run? require.unsee('@ember/destroyable')? Or is this actually a design flaw?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

assertDestroyablesDestroyed could probably do the cleanup during test builds. We would have to track some extra state in DEBUG builds, use a normal Map instead of a WeakMap, but that's something we've done before.

Copy link
Member

Choose a reason for hiding this comment

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

I've commented on the issue linked above (in the polyfill) to explain how this should work. I don't think this is a gap in the public APIs proposed (but the assertion would leverage internal private APIs).

@buschtoens
Copy link
Contributor

I made a polyfill for this RFC: ember-destroyable-polyfill

It's not perfect yet: The test suite could be expanded and the integration with CoreObject / the hooks-based system could be improved, but AFAICT it works.

Co-Authored-By: Jan Buschtöns <buschtoens@gmail.com>
Copy link
Contributor

@MelSumner MelSumner left a comment

Choose a reason for hiding this comment

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

Thank you for updating the learning content! 👍

@rwjblue
Copy link
Member

rwjblue commented Apr 21, 2020

Thanks for working through this with us!

@sandstrom
Copy link
Contributor

@buschtoens Awesome addon! Just installed it, works great! 💯

ef4 added a commit that referenced this pull request Aug 4, 2023
Advance RFC #580 `"Destroyables"` to Stage Recommended
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Final Comment Period T-framework RFCs that impact the ember.js library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants