Open ww2406 opened 2 months ago
Thanks for reporting this, @ww2406.
This isn't something we've had reported before so I'm not initially certain whether it's likely to be a framework issue or application code issue. We're tracking it on the backlog for now. We process work in priority order so it might be some time before we get to this one - if you are keen feel free to dig into the investigation, because if you can point us to a specific bit of framework code that is wrong, we're more likely to move forwards sooner. If not no worries, it can go into the queue.
Thanks @SteveSandersonMS. After a lot of digging, I think I've found the root cause, essentially an inadvertent memory leak for this corner case due to unreleased event listeners.
It looks like the EventInfoStore adds a global listener to the document element:
It seems that disposing of the root component doesn't remove this global listener and the associated event dispatch code. When Blazor loads the component again, it adds the global listener a second time to document
, and now when you interact with the Blazor application, both event stores are still in memory and fire events. As you repeat this, the number of attached event listeners multiplies.
I hacked around this for my immediate needs by adding yet another view ( 🙃 ) with the actual blazor loading which gets rendered in an iframe in the original view. This lets all the event handlers be attached to the iframe's document, so when the iframe is destroyed on navigation, the event listeners actually get destroyed, too.
Is there a great way with the current public interface to effect the removal of the global listener manually? From my dive into everything, it doesn't appear so, but I might be missing something.
If there isn't an existing way, I think long term it would be nice to have this cleanup incorporated as part of the DynamicRootComponent.dispose()
method -- for me, at least, my intuition from the documentation is that dispose
will release all resources. I'm not sure if this would have other breaking effects for other use cases.
The intention is that no matter how many root components you add and dispose, there's still just one continuous instance of the Blazor
JS object that tracks information like global event listeners. As such it shouldn't be necessary for the framework to unregister the global listener. When new root components are registered later, they must be registered against the same Blazor
JS instance.
I had a quick look at your repro code but it was pretty hard to follow because it's so nonstandard. Is it possible you're doing something unsupported around trying to load the Blazor JS scripts multiple times?
I had a quick look at your repro code but it was pretty hard to follow because it's so nonstandard.
Yeahhhhhh, I spend approximately a quarter of my time yelling "whyyyy" to the ether as of late 😢
Is it possible you're doing something unsupported around trying to load the Blazor JS scripts multiple times?
Apparently yes...I swear I've tried this multiple times and had the same issue, but just tried it again and everything works. I must have finally made the magic tweak.
Thanks for the help, I guess I can get up in a few hours and fix it in the actual application now.
Is there an existing issue for this?
Describe the bug
I'll preface this with I'm not sure if this is an actual bug or a function of the interesting code I inherited (or both).
After calling
savedRootComponent.dispose()
, reloading the component, and performing a JSInterop call from Blazor, the client application tries to perform multiple calls based on the number of times the Blazor containing page has been loaded, but the first n-1 calls fail with aSystem.ArgumentException: There is no event handler associated with this event
error.Please see Anything Else for complicating factors...
Expected Behavior
I expected that
savedRootComponent.dispose()
would release all local resourcesSteps To Reproduce
Repo: https://github.com/ww2406/BlazorEventHandlerRepro
To reproduce:
System.ArgumentException...no event handler...
error.Exceptions (if any)
System.ArgumentException: There is no event handler associated with this event
.NET Version
8.0.401
Anything else?
Complicating whether this is actually what's happening or not.....
Basically, someone created a jQuery-based SPA that dynamically loads the pages returned from MVC controllers. I'm trying to slowly start to replace different components with Blazor, but I still need to keep the non-migrated components functional and as part of a cohesive application.
The application uses
jQuery.load()
to load the rendered content retrieved from the MVC controller into a div. Without disposing the root component, if you navigate away from the Blazor containing page to another page in the application and come back, it's as if there are two instances of the Blazor component running and every action is repeated twice (this same thing happens if rendering statically with<component.../>
). If I calldispose
first at page load if I already have a root component saved globally, whenever you perform a JS interop action in the Blazor app, it repeats n-fold (based on the number of navigations), but the first n-1 iterations throw theno event handler
error.