-
Notifications
You must be signed in to change notification settings - Fork 1k
fix(labs/virtualizer): guard against layout updates or re-observing when disconnected #4233
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
fix(labs/virtualizer): guard against layout updates or re-observing when disconnected #4233
Conversation
🦋 Changeset detectedLatest commit: abe6b37 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📊 Tachometer Benchmark ResultsSummary⏳ Benchmarks are currently running. Results below are out of date. nop-update
render
update
update-reflect
Results⏳ Benchmarks are currently running. Results below are out of date. this-change
render
update
update-reflect
this-change, tip-of-tree, previous-release
render
update
nop-update
this-change, tip-of-tree, previous-release
render
update
this-change, tip-of-tree, previous-release
render
update
update-reflect
|
I think that was just the end of your commit message |
|
This looks like a great change to me. I'd like to give Gray a chance to review in case there's some subtlety where we'd rather fail on a missing controller here |
|
Oh, please add a changeset by running |
|
These changes may indeed be good, but if possible I'd like to understand better how it is that the virtualizer in HomeAssistant ends up in a state where it has no My guess is that the virtualizer is being disconnected somewhere in the chain of events and then subsequently responds to an async scroll error message (perhaps triggered by a MutationObserver callback) that no longer applies. If this is the case, there may be a spot earlier in the codepath where we can short-circuit, rather than getting all the way into |
|
It's originating from the children resize observer, and it certainly occurs only after a disconnect. It's possible the items get removed before the virtualizer element is fully disconnected (and maybe that observer isn't considering that situation?). I don't quite understand that callback enough at the moment and probably not the best to come up with a minimal test. Here's the longer stack trace: |
|
Given that, I suppose another solution would be to change the order in |
|
Thanks for the additional info, @steverep. I'll play around a bit and see if I can come up with a minimal repro, though I may have to come back to you with followup questions. |
|
@steverep I expect to have time to dig into this later today, but two quick questions:
|
Unfortunately, it is not. Most if not all the virtualizers are in the settings area which isn't in the demo.
Yes, of course. The repro I am using comes from the virtualizer in our Essentially, the repro goes like this:
Let me know if I can test anything for you. |
|
FWIW, I took a closer look at this today. If I'm reading the The deeper problem seems to be that in Note the RO callback is not in the trace this time. It's coming from an earlier call to update the DOM. |
|
@steverep Yes, I believe the problem is that something in application code (probably related to the mechanism for switching between views as the user navigates) is causing the virtualizer to kick off an update cycle after it has been disconnected. In trying to create a minimal repro was, I have tried various ways of toggling between a view with a virtualizer and a view without a virtualizer. However, the only way I've been able to end up in The following Playground example illustrates: https://lit.dev/playground/#gist=e18207161b9d96c5d81dfb7032f474ad If you run it as-is (with the Console open to view log statements) and click the Toggle button, everything is fine. But if you uncomment line 35 and then toggle, you'll see we end up in The good news is that it should probably be fairly straightforward to short-circuit the virtualizer update cycle in the case where it is not currently connected. However, I would still like to see if we can figure out what it is in the Home Assistant code (and in @cintaccs app) that triggers a virtualizer update after disconnection so that we can make some more realistic tests. I do see that there's a |
|
@graynorton based on my testing, I would suggest you set
Yeah, I think that's more or less what is happening.
Nope, the only thing I've narrowed down is that it only seems to error when we have the data filtered, and that means a web worker is involved in returning the reactive property that holds the items. Although I don't immediately see why that would be a problem. I'll amend my last post with the full stack trace. It actually seems to be looping inside lit for a couple update cycles, so maybe the items were actually set long before? 😕 |
I'm looking at how to implement that, but how far upstream which would still be safe is not obvious to me. Certainly stopping at the start of |
The virtualizer is within a child component of the page that is being refreshed. In order to ensure that the content is really refreshed one of the parents are re-created. In some earlier lit versions at least - this was the only way I could figure out to get the page to refresh. Later the virtualizer was introduced in the child list component. (where the list component is a child of the page component...). From my point of view - I wouldn't consider it bad to check for existence before destroying .... I guess destroy could happen for a lot of difference reasons - navigating away etc. |
|
@steverep My apologies—of course! The screenshot was a dumb way to share the diff in the first place. |
|
@graynorton I can test with HA later tonight and let you know. Regarding no other changes, correct me if I'm missing something, but wouldn't the changes in this branch still be necessary as a safeguard? Your change may catch most issues, but it doesn't guarantee the timing, i.e. a disconnect can still happen after that checkpoint. |
|
@steverep I don’t believe so, if everything is working as designed. The fact there is no guard code there is actually intentional, so we don’t inadvertently mask more serious issues, like the unnecessary render that #4182 revealed. We can and should still remove the truly redundant non-null assertions you noticed in other spots, though. |
|
@steverep I’ll add that I’m very much open to being proven wrong, but before we go and guard against it on a blanket basis, I’d really like to have a concrete, understandable repro that shows how a disconnection might occur in the middle of a virtualizer update cycle. I believe the timing of the mutation and resize observers makes this unlikely or impossible, but I’m not 100% sure. |
|
@graynorton I tested HA last night and was unable to reproduce the error with just your change. 👍🏻 I'll admit my gut is still telling me the timing cannot be guaranteed once To that end, I reverted the controller and observer checks here. The only thing I kept (other than the removal of non-null assertions) was nullifying the the observers on disconnect. I would argue that's still a worthwhile change as, to your point, it would cause earlier failures after a disconnect by making sure re-observations cannot occur (as was happening here with Do you want to add your change here or shall I change the commit message to reflect it as is? |
hmm.. I wish it was simple to answer. In general I am using the properties of the lit element class components - normal use I would think - however some of these properties are either ARRAYs or OBJECTS and also might be delayed with a event UP and await down flow... The virtualizer is not subscribing in itself to this - it just knows the items array ... |
|
Thanks, @steverep!
Agreed!
I am flexible on this. I guess it might be somewhat simpler to go ahead and land your improvements first, then have me make a follow-up PR with my change (and likely also some new tests)—only because that way, we don't need to worry about permissions for me to push to your fork, etc. Another possibility would be for you to just go ahead and apply my patch to your fork so that it ends up here; that would be fine with me if it works for you. Just let me know what you prefer! Regardless, I personally won't have much time with my computer for the next few days, as I'm about to head out on a trip. I'm hoping there will be some downtime that I can spend to advance this, but in the worst case it would be next Tuesday or Wednesday before I was able to get back to it. I should be able to keep my eye on the discussion here, though. |
No worries! Thanks for the additional background. I suspect that the changes Steve and I have identified in this PR will fix the issue for you as well. If it would be feasible for you to try applying my patch to your local setup, that would be great. But if not, that's fine—you can wait until the changes land and let us know if you're still seeing the problem then. |
Co-authored-by: graynorton <[email protected]>
|
@graynorton I went ahead and applied your patch with a minor refactoring because your "should" method confusingly didn't return a boolean and it would just force back more non-null assertions. Rather than adjust that I just incorporated the check into If so, I suppose this is ready to go. |
graynorton
left a comment
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.
Looks good! I requested an update to the change description, and I believe this branch also needs to merge the latest changes from main (or be rebased). I approve merging this PR once those changes have been made.
The only other thing I’d like to do is add some tests, but I’m happy to do that in a separate PR.
|
Whenever this is merged and released, it's now going to be difficult to incorporate without first upgrading to lit 3.0. The choice to only have a patch release 2.0.8 after updating the lit dependency doesn't make sense to me. |
Can you explain what you mean here? Making this a minor release wouldn't help, would it? |
No, this PR should definitely only be a patch. My point was that the just released 2.0.8 now has a direct dependency on lit 3.0, with no inclusion of 2.x versions. That makes it a breaking change IMO, and the new virtualizer version should have been 3.0 (unless it still supports lit 2.0 in which case the manifest should be updated). Right now, you cannot update to 2.0.8 without also updating lit to 3.0 unless you bundle 2 different lit versions which I wouldn't dare do (home-assistant/frontend#18206). |
Agreed. I went ahead and merged this PR; will make a separate one to update the version range for the |
| // Only update the layout and trigger a re-render if we have: | ||
| // a) A layout | ||
| // b) A scrollerController, which means we're connected | ||
| // c) An offsetParent, which means we're not hidden |
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 will be null if the hostElement has a parent that is positioned fixed (in chrome, in firefox it returns body). The virtualizer doesn't render inside a mwc-dialog anymore now for example in chrome.
Edited per discussion below and also fixes #3831
Fixes #4182 by adding a check that the controller is not null. At the risk of being slightly out of scope, also:
PS - Looks like there was supposed to be a PR template, but the field was just filled with "...LL error" for me