polymer: dom-if memory leak

Description

dom-if with restamp causes memory to be leaked until the window is refreshed. This does not work well with SPAs.

Expected outcome

Memory to be cleaned up when the garbage collector runs after a page has been left.

Actual outcome

Memory is leaked until the page is refreshed

Steps to reproduce

Use the polymer-2 starter kit. Modify my-app.html by replacing the normal view code in iron-pages with

<template is="dom-if" if="[[isView1]]" restamp>
    <my-view1 name="view1"></my-view1>
</template>
<template is="dom-if" if="[[isView2]]" restamp>
    <my-view2 name="view2"></my-view2>
</template>
<template is="dom-if" if="[[isView3]]" restamp>
    <my-view3 name="view3"></my-view3>
</template>

Inside of _routePageChanged(page): add this code after this.page = page || 'view';

if (page === 'view1') {
    this.isView1 = true;
    this.isView2 = false;
    this.isView3 = false;
} else if (page === 'view2') {
    this.isView1 = false;
    this.isView2 = true;
    this.isView3 = false;
} else if (page === 'view3') {
    this.isView1 = false;
    this.isView2 = false;
    this.isView3 = true;
}

Adding this code to my-view2.html makes it easy to see the memory leak.

connectedCallback() {
    super.connectedCallback();
    this.hugeArray = [];
    for (var i = 0; i < 1000 * 100; i++) {
        this.hugeArray.push(Math.ceil(Math.random() * 100))
    }
}

This was taken after switching between the views and forcing a garbage collect after landing back on view1. image

Browsers Affected

  • Chrome
  • Edge
  • Firefox
  • IE 11
  • Safari 8
  • Safari 9

Versions

  • Polymer: v2.6, v3.0.2
  • webcomponents: v1.2

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 19 (4 by maintainers)

Commits related to this issue

Most upvoted comments

I found this issue on Polymer 2 and it still exists on Polymer 3. Both dom-if and dom-repeat are registered their function to global debouncer.

__debounceRender(fn, delay = 0) {
    this.__renderDebouncer = Debouncer.debounce(
          this.__renderDebouncer
        , delay > 0 ? timeOut.after(delay) : microTask
        , fn.bind(this));
    enqueueDebouncer(this.__renderDebouncer);
  }

Items in this global debouncer list cannot be removed. It causes memory leak for any elements that use this elements in their template. We need to manually execute the following statement to prevent this memory leak.

Polymer.flush();

I added Polymer.flush() to _routePageChanged(page) in my-app.html in the polymer 2 starter kit. This is my result from the same tests as before.

image

While it seems like with this change, it is no longer leaking memory, it is still leaking nodes.

image

@nazarsa For Polymer 2, the fix for this is in 2.7.1.

I’ve found out:

in flush.html enqueueDebouncer is called from dom-if, but flush or flushDebouncers is never called.

see: bildschirmfoto 2018-07-27 um 21 34 44