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

bugfix(setup): Disable interaction before start #147

Closed
wants to merge 1 commit into from

Conversation

pzuraq
Copy link
Contributor

@pzuraq pzuraq commented Oct 18, 2017

Some users are reporting the collection is able to get into a bad state where prepends/appends happen before setup has fully been completed. This could if the array is immediately replaced with another array before setup has finished.

This PR disables all interactions with the collection before it has been initialized. Modifying state before first render doesn't particularly make sense anyways, so this is just guarding against a case that shouldn't be happening.

Alternatives

We could warn or error if this occurs, in most cases this could probably be fixed by changes to user code. I do think there may be legitimate use cases, but since the failure reported could not be reproduced (mxz phoned it in) I couldn't say in that case.

In particular this could be problematic in the corner case where initialRenderCount is used, since it won't pick up any changes to the items in between when vertical-collection is initialized and first render. This could potentially confuse users who would expect the initial render to reflect the updated array.

Some users are reporting the collection is able to get into a bad state
where prepends/appends happen before setup has fully been completed.
This could if the array is immediately replaced with another array
before setup has finished.

This PR disables all interactions with the collection before it has been
initialized. Modifying state before first render doesn't particularly
make sense anyways, so this is just guarding against a case that
shouldn't be happening.
@pzuraq pzuraq requested a review from runspired October 18, 2017 20:25
@@ -110,9 +110,10 @@ export default class Radar {
start() {
let { startingIndex } = this;

if (startingIndex !== 0) {
this._updateConstants();
// Run initial measurements and setup initial state, e.g. skipList in dynamic radar
Copy link
Contributor

Choose a reason for hiding this comment

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

why this order change?

Copy link
Contributor

@runspired runspired left a comment

Choose a reason for hiding this comment

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

I think this is the wrong approach.

Modifying state before first render doesn't particularly make sense anyways, so this is just guarding against a case that shouldn't be happening.

The right approach is hinted at in this explanation. If folks are swapping out the array multiple times before first render, they have a data flow problem they need to fix. That's not something that is particularly sustainable for us to guard against, as is evidenced by the sheer quantity of checks that were needed in this PR.

We should find a good place to detect the data change and just error there. This is similar to the did modify twice error that glimmer throws.

@pzuraq
Copy link
Contributor Author

pzuraq commented Oct 18, 2017

So the issue I'm coming up against there is there doesn't really appear to be a single good place to run this check. You'll notice this change in DynamicRadar:

 -    if (this.skipList !== null) {
 -      this.skipList.reset(this.totalItems);		
 -    }

We were already null checking the skip list in this case because A. the computed will run at least once and B. the Radar will not yet have been "started". So we can't assert in the computed, nor can we assert in the prepend/append/reset functions.

This method at least normalizes what we expect from the radar, that interacting before initial render is a no-op. Currently, if you reset it is a no-op, but if you prepend/append it will error.

Ideas on how we could actually assert this is invalid? I suppose we could assert that the computed will run exactly one time between the collection being instantiated and the Radar being started.

@runspired
Copy link
Contributor

I am unsure, I will need to think on it.

@pzuraq
Copy link
Contributor Author

pzuraq commented Oct 19, 2017

So I started trying to do the computed strategy, it turns out the computed will trigger multiple times before the radar can start in certain edge cases, for instance if a promise resolves in a PromiseArray in between when the component has rendered and the RAF that starts the Radar runs.

I do think we can narrow down where we interact with the Radar to the computed, but based on this I think we need to no-op instead of asserting.

@pzuraq
Copy link
Contributor Author

pzuraq commented Oct 19, 2017

The more I think about this, the more I realize the root cause is because we don't have any strong timing guarantees between when a render occurs in Ember and a RAF occurs in our scheduler. In theory, one render should mean one RAF, but it appears that this is not always the case.

@pzuraq
Copy link
Contributor Author

pzuraq commented Oct 22, 2017

Update on this, we've decided that we don't want to normalize this behavior, so we won't be merging this PR. There is definitely still an issue however, and it stems from data flows that cause us to attempt to render twice in the initial render. This is likely bad behavior in a user's code, but we don't have a way to assert against it so we'll need to spend more time on it.

@erichaus
Copy link

@pzuraq could this be related to the issue I've reported previously and you've helped on where going sibling -> sibling route (model changes) causes my vertical-collection to break?

@pzuraq
Copy link
Contributor Author

pzuraq commented Nov 21, 2017

This is probably related to that issue, as you can see from the discussion we haven't figured out a good way to handle these cases. I'd like to say we could definitely close over every edge case and just never have this happen, but I do think there may be legitimate cases where we can have the computed modified twice before the collection has had time to be rendered and do its setup.

@erichaus
Copy link

erichaus commented Nov 21, 2017

ok but the strange thing is the issue resolved when I was on ember-data 2.17.X, since I had to revert back to 2.12.X due to a separate issue, the aforementioned VC issue has returned :(

I can go over it again with you at some point (holler)

@pzuraq
Copy link
Contributor Author

pzuraq commented Nov 21, 2017

Yeah, because this is related to scheduling in general it’s definitely possible that internal changes in other parts of Ember could cause it. There’s no silver bullet here, I’m not sure we’ll have abetter solution for this until the runloop has better integration with requestAnimationFrame.

In the meantime, I’ll see if we can get a fix merged for this.

@erichaus
Copy link

ok great thanks a bunch man

@makepanic
Copy link

We also stumbled over this issue.
The setup is a route that loads a list and creates a sync task after that.

It looks like if i load the tab in background, the sync will fire before vertical-collection initializes (maybe because of throttled background tab?). This can cause the list to change and vertical-collection throwing #154.
It's reproducable in both Firefox and Chrome.

@pzuraq
Copy link
Contributor Author

pzuraq commented Nov 27, 2017

Based on @makepanic's description I think we may need to go with this type of fix after all. We'll need to test, but I know for a fact that RAF's will defer if the tab is not focussed (which makes sense).

If they timeout or cancel themselves we need to figure out a way to reschedule them, but I think what's more likely is they will defer until the tab receives focus again. In the meantime, unfocussed tabs will continue to run other async tasks such as promises, etc. If this is true, it means we will never be able to get a strong enough timing guarantee with the runloop to say that 1-runloop === 1-raf, and we need account for that.

I think we can clean this PR up much more by making started a public API and disabling interaction in the computed that calls these functions in the first place. Much less code blocking interaction that way, so it'll be cleaner overall.

@makepanic
Copy link

Is it possible to advance this PR? I've tried it locally and the issue reported in #147 (comment) isn't appearing anymore.

If not, is there anything once could do to help?

@erichaus
Copy link

If folks are swapping out the array multiple times before first render, they have a data flow problem they need to fix.

I updated the route that was causing trouble with this to use ember-concurrency and improved the control-flow and it seems to have resolved... but I also moved to the latest ember which may have had an impact.

@pzuraq
Copy link
Contributor Author

pzuraq commented Jan 17, 2018

I talked offline about this PR with @runspired, will be rewriting it slightly and getting it merged tonight

@pzuraq
Copy link
Contributor Author

pzuraq commented Jan 24, 2018

superceded by #168

@pzuraq pzuraq closed this Jan 24, 2018
@runspired runspired deleted the bugfix/disable-radar-interaction-before-setup branch July 27, 2023 07:53
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.

4 participants