-
-
Notifications
You must be signed in to change notification settings - Fork 408
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
Destroyables #580
Conversation
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.
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.
@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 |
Co-Authored-By: Robert Jackson <me@rwjblue.com>
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.
Thanks for the updates here @pzuraq, looks like great progress!
|
||
- Calling `destroy` on a non-destroyable should throw an error. | ||
1. Mark the destroyable such that `isDestroying(destroyable)` returns `true` |
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.
Does it also mark each of the children?
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 does while calling destroy()
on the children, since it recurses. It does not mark them as destroying before running its own destructors.
Co-Authored-By: Robert Jackson <me@rwjblue.com>
After discussing this in the meeting today, we are very excited to move this RFC into final comment period! |
Given how @pzuraq loves decorators, I’m surprised to see there are no mention of decorators in this RFC 😏 |
Suggestion. Create a codemod to convert willDestroy to using destroyables instead |
- Calling `unregisterDestructor` with a destructor that is not associated with | ||
the object should throw an error. |
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.
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)
.
Calling `destroy` with a destroyable that has no destructors or associated children | ||
will not throw an error, and will do nothing. |
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.
Will isDestroying
/ isDestroyed
still return true
though?
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, it will.
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. |
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 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?
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 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. |
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.
- 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
)?
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. |
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 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.
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.
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?
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.
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.
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'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).
I made a polyfill for this RFC: It's not perfect yet: The test suite could be expanded and the integration with |
Co-Authored-By: Jan Buschtöns <buschtoens@gmail.com>
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.
Thank you for updating the learning content! 👍
Thanks for working through this with us! |
@buschtoens Awesome addon! Just installed it, works great! 💯 |
Advance RFC #580 `"Destroyables"` to Stage Recommended
Rendered