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

When removing a custom component with @Tag, a DetachedHTMLElement occurs #14422

Closed
RhonanMS opened this issue Aug 22, 2022 · 17 comments · Fixed by #14729
Closed

When removing a custom component with @Tag, a DetachedHTMLElement occurs #14422

RhonanMS opened this issue Aug 22, 2022 · 17 comments · Fixed by #14729

Comments

@RhonanMS
Copy link

Description

We have found that the RAM usage within the browser increases with each interaction in our applications.
We could trace this back to an "embed" mechanism in our application, where we dynamically add and remove Components (e.g. HorizontalLayout and VerticalLayout) with a number of child components.
During profiling of the code in the browser with several "embed" actions, we found that a lot of DetachedHTMLElements - each corresponding to one of the components removed from the DOM.
We tried to reproduce this in a minimal example (which is attached below), and found that the DetachedHTMLElements seem to occur when we use custom components with an "@tag" annotation. This is even the case, when the custom component is inherited from the Vaadin Div component - as soon as @tag("my-div") was added, the DetachedHTMLElements occur.
Our first thought was that this might have something to do with reusing a detached HTML component, but when the same component is added and removed twice, two instances of the DetachedHTMLElement can be found.

The issue did not seem to be browser related and occured in our standard browser mix: Chrome, Firefox and Edge. Safari was not tested.

Expected outcome

There should not be a DetachedHTMLElement when a custom Component using @tag is used.

Minimal reproducible example

The
attached project contains an example.
It runs on localhost:8181

Steps to reproduce

Note: we use the Edge browser to reproduce the example because of it "Detached Elements" development tool, but the increased memory usage can also be seen in other browsers.

To reproduce

  1. set "boolean useDivs" to "true" in EmbedSampleView.
  2. run app
  3. open Edge Browser in private mode
  4. open "Dev Tools"
  5. open "Detached Elements" tool
  6. reload page if buttons in "Detached Elements" tool are inactive (seems to be the default)
    detached-elements-buttons
  7. open http://localhost:8181/
  8. click 40 times on "Embed next layout" button
  9. open "Detached Elements" tool
  10. click "collect garbage" button in "Detached Elements" tool (the trash can icon)
  11. click "get detached elements" button in "Detached Elements" tool (the reload icon)
  12. there should be around 10 elements (mostly style and one div class="v-reconnect-dialog" element)
    plain-divs
  13. close tab
  14. kill app
  15. set "boolean useDivs" to "false" in EmbedSampleView.
  16. repeat steps 2 to 11
  17. there should now be around 40 "vaadin-vertical-layout" elements additionally to the elements described in step 12.
    custom-divs

Environment

Vaadin version(s): 14.8.13
OS: Windows 10 Enterprise (Build 20H2, OS build 19042.1889)

Browsers

Chrome, Firefox, Edge

@knoobie
Copy link
Contributor

knoobie commented Aug 22, 2022

@mshabarov this sounds like something to be taken care in flow

@mshabarov
Copy link
Contributor

Thanks @knoobie ! That might be an issue in core.

@mshabarov mshabarov transferred this issue from vaadin/flow-components Aug 24, 2022
@mshabarov mshabarov added this to Needs triage in OLD Vaadin Flow bugs & maintenance (Vaadin 10+) via automation Aug 24, 2022
@johannest
Copy link
Contributor

Seem to reproduce for me with Edge. In Chrome I used Memory tool and similarly did garbage collect and took a memory snapshot after each 20 click: there the memory consumption increased ~10MB on every iteration, and detached element counts doubled. If using default Divs, Chrome's memory consumption does not increase. So the issue clearly reproduces with Chrome as well.

@johannest
Copy link
Contributor

image

@mlindfors
Copy link

With Firefox, with just Divs, the memory consumption seems to grow to a degree, and then drop down eventually. With my-divs the memory consumption just keeps going up. So I suppose I can confirm that this happens on Firefox as well.

@mshabarov mshabarov added the BFP Bugfix priority, also known as Warranty label Aug 25, 2022
@mshabarov mshabarov moved this from Needs triage to P1 - High priority in OLD Vaadin Flow bugs & maintenance (Vaadin 10+) Aug 25, 2022
@mshabarov mshabarov added the bug label Aug 25, 2022
@RhonanMS
Copy link
Author

Hello, I just found the issue #13851. Could it be that this bug is a duplicate of the 13851?

@mshabarov
Copy link
Contributor

@RhonanMS we were talking about that on the planning session today, and indeed these two issues might have the same root cause. Note that we have a patch for #13851 and it's under review. Once we get it merged, we'll retest your case.

@RhonanMS
Copy link
Author

Hi @mshabarov, that is great news!!! I'm looking forward to the results of the re-test!

@mshabarov
Copy link
Contributor

Vaadin 14.8.18 now released and it contains the patch for this 18c4058 .
To be re-tested with V14.8.18.

@RhonanMS
Copy link
Author

Hi @mshabarov,

I re-tested with V14.8.18 (using a clean workspace and emptying the local Maven repository) and unfortunately the problem still seems to exist - both in the example attached in the issue (see also the new screenshot) as well as in our application, the memory consumption increases when custom components are added to the DOM and does not increase when they are removed afterwards

issue-screenshot-22-09-26

@mcollovati
Copy link
Collaborator

Taking a look at a heap dump in Chrome, my guess is that the memory leak may be in Flow client SimpleElementBindingStrategy

    private native void bindPolymerModelProperties(StateNode node,
            Element element)
    /*-{
      if ( @com.vaadin.client.PolymerUtils::isPolymerElement(*)(element) ) {
          this.@SimpleElementBindingStrategy::hookUpPolymerElement(*)(node, element);
      } else if ( @com.vaadin.client.PolymerUtils::mayBePolymerElement(*)(element) ) {
          var self = this;
          try {
              $wnd.customElements.whenDefined(element.localName).then( function () {
                  if ( @com.vaadin.client.PolymerUtils::isPolymerElement(*)(element) ) {
                      self.@SimpleElementBindingStrategy::hookUpPolymerElement(*)(node, element);
                  }
              });
          }
          catch (e) {
              // ignore the exception: the element cannot be a custom element
          }
      }
    }-*/;

Flow registers a callback that captures node and element for the Promise returned by customElements.whenDefined(...), but the promise is never fulfilled.

image

Looking at the retainers tree, the code relative to the client-xxxxx.cache.js corresponds to the calls to $wnd.customElements.whenDefined(c.localName)

@mcollovati
Copy link
Collaborator

Did a quick test, changing the code to attach the callback to a promise that gets fulfilled after a second if the custom element is not registered in the meanwhile, and it seems that with this workaround there are leaks

              var promiseTimeout = new Promise(function(r) { setTimeout(r, 1000) });
              var whenDefinedPromise = $wnd.customElements.whenDefined(element.localName);
              Promise.race([whenDefinedPromise, promiseTimeout])
                .then( function () {
                  if ( @com.vaadin.client.PolymerUtils::isPolymerElement(*)(element) ) {
                      self.@SimpleElementBindingStrategy::hookUpPolymerElement(*)(node, element);
                  }
              });

Before garbage collection

image

After garbage collection

image

@mcollovati mcollovati moved this from Ready To Go to In progress in OLD Vaadin Flow ongoing work (Vaadin 10+) Oct 3, 2022
mcollovati added a commit that referenced this issue Oct 4, 2022
Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422
@mcollovati mcollovati self-assigned this Oct 4, 2022
@mshabarov mshabarov moved this from P1 - High priority to WIP in OLD Vaadin Flow bugs & maintenance (Vaadin 10+) Oct 5, 2022
OLD Vaadin Flow ongoing work (Vaadin 10+) automation moved this from In progress to Done - pending release Oct 5, 2022
mshabarov pushed a commit that referenced this issue Oct 5, 2022
#14729)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422
OLD Vaadin Flow bugs & maintenance (Vaadin 10+) automation moved this from WIP to Closed Oct 5, 2022
ZheSun88 pushed a commit that referenced this issue Oct 7, 2022
#14729)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422
ZheSun88 pushed a commit that referenced this issue Oct 7, 2022
#14729)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422
ZheSun88 pushed a commit that referenced this issue Oct 7, 2022
#14729)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422
ZheSun88 pushed a commit that referenced this issue Oct 7, 2022
#14729)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422
ZheSun88 pushed a commit that referenced this issue Oct 7, 2022
#14729)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422
ZheSun88 pushed a commit that referenced this issue Oct 7, 2022
#14729) (#14755)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422

Co-authored-by: Marco Collovati <marco@vaadin.com>
ZheSun88 pushed a commit that referenced this issue Oct 7, 2022
#14729) (#14754)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422

Co-authored-by: Marco Collovati <marco@vaadin.com>
mcollovati added a commit that referenced this issue Oct 7, 2022
#14729) (#14757)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422

Co-authored-by: Marco Collovati <marco@vaadin.com>
mcollovati pushed a commit that referenced this issue Oct 7, 2022
#14729) (CP: 2.7) (#14758)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422
mcollovati pushed a commit that referenced this issue Oct 7, 2022
#14729) (CP: 2.8) (#14756)

Polymer binding callback was attached to the promise returned by
customElements.whenDefined, but this promise may never complete
if the input element is not a custom element, causing memory leaks
on browser because of element capture.
This change introduces a new promise that completes either when
whenDefined is fulfilled or after a fixed timeout, allowing
the garbage collector to clean resources.

Fixes #14422
@vaadin-bot
Copy link
Collaborator

This ticket/PR has been released with Vaadin 14.8.19.

@vaadin-bot
Copy link
Collaborator

This ticket/PR has been released with Vaadin 23.2.4.

@RhonanMS
Copy link
Author

Sorry for not getting back earlier to you, as I was out of order thanks to COVID-19. We retested the issue with both the sample program and our main application and the issue is gone - memory usage in the browser is much better now!
Thanks to everyone involved for your help and work!

@vaadin-bot
Copy link
Collaborator

This ticket/PR has been released with Vaadin 23.1.13.

@vaadin-bot
Copy link
Collaborator

This ticket/PR has been released with Vaadin 14.9.0.beta1 and is also targeting the upcoming stable 14.9.0 version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment