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

Service's willDestroy hook is not triggered if it was initialised in another service's willDestroy #18323

Closed
andreyfel opened this issue Aug 28, 2019 · 4 comments

Comments

@andreyfel
Copy link

The issue is mostly related to application tests because services are usually not destroyed while the app is running. However, it can be a reason of a significant memory leak in tests.

Reproduction repo:
https://github.com/andreyfel/ember-services-teardown-bug

In ServiceA's willDestroy hook we call hi() method of the ServiceB. Until that moment ServiceB hasn't been invoked in any way, so, that call triggered init() of the ServiceB. ServiceB's willDestroy hook is not triggered. In order to see the issue run tests and look at the console.

Expected output:

ServiceA init
Hi from ServiceA
ServiceA willDestroy
ServiceB init
Hi from ServiceB
ServiceB willDestroy <---- It never shows up in the console.
Copy link
Member

rwjblue commented Aug 29, 2019

The basic cleanup flow is:

  • loop through all the singleton instances that have been created, and run.schedule('destroy', instance, 'willDestroy')
  • later, while the destroy queue is flushing each of the previously scheduled functions are invoked
  • ServiceA is instantiating ServiceB (by looking it up and calling a method)

Since ServiceA is instantiating ServiceB during teardown, and we never attempt to loop through instances a second time, we never realize that ServiceB has never been destroyed.

In general this shouldn't be a major issue (though I agree its pretty bizarre and we should try if we can to fix it), because ServiceB should eventually be garbage collected anyways (though this may of course be thwarted by something ServiceB is actually doing).

AFAICT, the only thing we can actually do to help here would be in the test helpers themselves. Essentially, after all queues have flushed we can confirm that all singletons have been destroyed (if not, loop through them again and destroy them). This could result in some sort of recursive loop, but I think that seems unlikely.

@andreyfel
Copy link
Author

It is even fine if we just fail the test if a singleton hasn't been destroyed after all queues have flushed because this is kind of invalid behavior and probably there is a bug in the code. So, no need for the second attempt.

Another option is to throw an exception if some code tries to initialize a new singleton during the destroy queue flushing. It will expose the bug even earlier.

@rwjblue
Copy link
Member

rwjblue commented Aug 30, 2019

Another option is to throw an exception if some code tries to initialize a new singleton during the destroy queue flushing.

Agreed. This seems like the best option, essentially throw on any new instantiation done from the owner/container IFF the owner/container are in the isDestroying state.

Possible fix is to change this assertion to check this.isDestroying instead of this.isDestroyed:

assert('expected container not to be destroyed', !this.isDestroyed);

@rwjblue
Copy link
Member

rwjblue commented Feb 20, 2020

This was fixed in #18717 and released in 3.16.2.

@pzuraq pzuraq closed this as completed Feb 20, 2020
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

No branches or pull requests

3 participants