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

Add guard & more helpful error massage when calling _setupRelationships when store is destroyed #5282

Conversation

danwenzel
Copy link

@danwenzel danwenzel commented Dec 18, 2017

This PR adds a guard against trying to set up relationships after the store has been destroyed, providing a more helpful error message.

Background:

We ran into a test failure which showed the following error:

TypeError: Cannot read property 'push' of null
    at Class._setupRelationshipsForModel

similar to this issue: miragejs/ember-cli-mirage#1220

After some digging, it turned out that the store had been destroyed too early in the test, and this line was causing the error: https://github.com/emberjs/data/blob/master/addon/-private/system/store.js#L2418

The error happened because the store sets _pushedInternalModels to null when destroyed: https://github.com/emberjs/data/blob/master/addon/-private/system/store.js#L2706, so trying to later call push on it causes an error.

This PR adds an assertion that makes it clearer what's going on, making for faster debugging.

@danwenzel
Copy link
Author

Not sure why the test is failing in Phantom... It's passing just fine for me locally

@@ -2415,6 +2415,11 @@ Store = Service.extend({
return;
}

if (this.isDestroyed) {
assert('Attempting to set up relationships after store has been destroyed');
Copy link
Member

Choose a reason for hiding this comment

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

You can add the isDestroyed check directly in the assert invocation. That will allow all of the code to be stripped in production builds...

I believe it would be something like:

assert('Attempting to set up relationships after store has been destroyed', !this.isDestroying && !this.isDestroyed);

Copy link
Author

Choose a reason for hiding this comment

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

Done

Dan Wenzel added 2 commits December 21, 2017 08:31
In prod, Ember.assert is stripped, so this test would fail
@danwenzel
Copy link
Author

Alright, so I noticed that the failing test was in the production environment, so I added a conditional in the test file. Let me know if there's a better way to do that.

assert.equal(personWillDestroy.called.length, 1, 'expected person to have recieved willDestroy once');
assert.equal(carWillDestroy.called.length, 1, 'expected car to recieve willDestroy once');
assert.equal(carsWillDestroy.called.length, 1, 'expected person.cars to recieve willDestroy once');
assert.equal(adapterPopulatedPeopleWillDestroy.called.length, 1, 'expected adapterPopulatedPeople to recieve willDestroy once');
assert.equal(filterdPeopleWillDestroy.called.length, 1, 'expected filterdPeople.willDestroy to have been called once');
if (config.environment !== 'production') {
assert.throws(() => {
Copy link
Member

Choose a reason for hiding this comment

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

I think we generally use assert.expectAssertion(() => {}, /some matcher/) for this. That custom assertion is provided by https://github.com/workmanw/ember-qunit-assert-helpers/ and is aware of development vs production...

Copy link
Author

Choose a reason for hiding this comment

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

@rwjblue - If I do that, then the production test still fails, because it moves on and hits the original TypeError: Cannot read property 'push' of null.

This is why I had originally built the assertion into a conditional. It would allow an early return - in production, the assertion would be skipped, but you wouldn't hit that error.

Copy link
Author

Choose a reason for hiding this comment

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

Or another idea: Maybe we want to just throw instead of assert? i.e. maybe we want this better error message in production too? This case is currently throwing an error in prod either way.

Copy link
Author

Choose a reason for hiding this comment

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

@rwjblue ⬆️ Any thoughts on this?

Copy link
Author

Choose a reason for hiding this comment

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

Pinging again... @rwjblue ⬆️ Any thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

@danwenzel I don't think we want to throw in prod

Copy link
Contributor

Choose a reason for hiding this comment

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

Also I'm happy to pick this up from where @rwjblue and help you get it over the line. I've been working to fix a lot of these async cleanup issues in Ember-data and Ember so that folks get better info about what went wrong.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks @runspired ! Any thoughts on the other issues above? If we want to leave production alone, we'll have to think of some way to only call that method in the non-prod tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

Thanks, @runspired , that worked perfectly! All passing now

@danwenzel
Copy link
Author

@runspired @rwjblue - Ok to ship this?

assert.throws(() => {
store._setupRelationshipsForModel(null, { relationships: {} });
}, /Attempting to set up relationships after store has 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.

We should design a test scenario similar to a real-world scenario that would have caused this to happen. I'm especially interested in such a test because:

  1. It may be that we need to adjust our waiters [FEAT] Prevent async test leakage (feat. adds test waiters) #5422
  2. _setupRelationshipsForModel should still occur in the same run-loop as whatever scheduled it. This indicates to me that this either might be the wrong location to assert, or that we could improve the message to tell people exactly what they need to do to resolve 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.

Also note, for createRecord causing this, we've improved things: #5415

Copy link
Author

@danwenzel danwenzel Apr 17, 2018

Choose a reason for hiding this comment

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

Hi @runspired. I agree that we should eventually dig more into the root issue, but this PR at least makes the situation 1 step better - Someone encountering this in dev or test will know more about what's going on and fix it faster.

I unfortunately won't have time to dig deeper for a while, so maybe we could ship this as-is?

Copy link
Contributor

Choose a reason for hiding this comment

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

@danwenzel if you can jot down some notes for me about roughly the setup you encountered in the wild, and give me commit access to your fork, I will continue to improve this :)

Copy link
Author

Choose a reason for hiding this comment

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

Hi @runspired , sorry for the delay.

I can't remember the details of my setup, but this gives a pretty good example of the issue: miragejs/ember-cli-mirage#1220.

I've given you access to my fork. Let me know if you need anything else

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@runspired
Copy link
Contributor

Will revisit this soon, I have a pretty good idea of how to design a good test for it, and likely we can simply do the "right" thing and no-op here vs. error.

Basically, the waiters in #5545 and an assert about using store.push when the store is destroyed should be enough, any other async internal timing things should be on ember-data to cleanup.

@runspired
Copy link
Contributor

After revisiting I confirmed that there are only two ways for _setupRelationships to be called: via commit and via push/_push. #5545 has guarded and tested commit, I will follow up with a new PR that guards push/_push.

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.

3 participants