-
Notifications
You must be signed in to change notification settings - Fork 396
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
Memory leak when using components #2899
Comments
I think I encountered something similar last night while doing benchmarks but wasn't sure if it was Ractive or the benchmarks. Phantom kept eating up memory until it crashed. 🤔 |
@giovannipiller this is an issue with implicit mappings not being torn down fully. I think I've got it sorted now, but if you get a few minutes to try out v0.8-dev to verify that it's fixed, I'll publish 0.8.12 ASAP. Thanks for the detailed report and enormous effort to track down the leak! |
It looks like this is covered with v0.8-dev, but I'd still like to know that it covers more complex cases too: http://jsfiddle.net/trc4m077/5/ |
I'll test it tomorrow (in ~10h) on my code and report back. Thanks for the super quick turnaround 😊 |
Benchmarks I'm working on doesn't cause Phantom.js to explode anymore. Must be a good sign. |
Things definitely improved 🎉 Isolated another memory leak, very similar, but with computed properties accessing data from parent components. http://jsfiddle.net/trc4m077/6/ Code for future referenceconst ApplicationHeader = Ractive.extend({
template: `
<nav class="header">
<section>
My name is {{altName}}
</section>
</nav>
`,
computed: {
altName() {
// no leak without this.parent
return this.parent.get('name');
},
},
});
const Application = Ractive.extend({
template: `
<button on-click="toggle('hideComponent')">TOGGLE ME</button>
{{#if !hideComponent}}
<ApplicationHeader/>
{{/if}}
`,
components: {
ApplicationHeader,
},
data: () => ({
name: 'Laser',
}),
});
window.r = new Application({ el: '#application' }); |
@giovannipiller well the good news is that that one was already fixed on edge. It also made me think a bit about how template expressions and reference expression should be working. I found a few minor edge-case leaks there too, along with a few small bugs involving aliases and expression lifetime. All of that should be fixed on edge and v0.8-dev as soon as travis finishes building. |
Thanks Chris! I'll report back this Monday morning (I'm from 🇮🇹, UTC+1), so I can test it in the most complex scenario at my disposal. |
(offtopic) @giovannipiller I'm going to Italy in April 14 and I'll stay at least 90 days to obtain my citizenship. Maybe we can exchange some ideas there in Italy. My skype username for future messages: "coghi.com.br" |
Leaks are getting harder to pin down.. which I guess it's a good sign 😃 Isolated another one here: Code for future referenceconst SubComponent = Ractive.extend({
template: `<footer>{{thisDoesntEvenExist}}</footer>`,
});
const ApplicationHeader = Ractive.extend({
template: `
<article class="header">
Something here<br>
{{#if tab}}
<SubComponent/>
{{/if}}
</article>
`,
components: {
SubComponent,
},
data: () => ({
tab: true,
anythingToObserve: 0,
}),
oninit() {
/**
* Observe will set a value different from the initial in data.
* Note: { init: false } would "fix" this leak, but still..
*/
this.observe('anythingToObserve', (newTab, oldTab) => {
this.set('tab', null).then(() => {
this.set('tab', true);
});
});
},
});
const Application = Ractive.extend({
template: `
<button on-click="toggle('hideComponent')">TOGGLE ME</button>
{{#if !hideComponent}}
<ApplicationHeader/>
{{/if}}
`,
components: {
ApplicationHeader,
},
});
window.r = new Application({ el: '#application' }); |
Leak with transitions (0.8.12-build-4): const Application = window.r = new Ractive({
el: '#application',
template: `
{{#if !loaded}}
<section class="if" fade-in-out>
Wait for next transition
</section>
{{else}}
<article class="else" fade-in-out>
Transition completed. Watch out for a memory leak.
</article>
{{/if}}
`,
transitions: {
fade(t, p) {
// Any transition really, so keeping it short.
// Tested with Fade and many other.
t.complete()
},
},
data: { loaded: false },
onrender() {
// example: this could by done after an AJAX
this.set({ loaded: true });
}
}); @evs-chris I'm keeping every new found leak under this issue, but I have no problem in creating multiple issues. Let me know if you have any preference, or need more data. @paulocoghi drop me a line via email (see my profile) |
Might be worth noting that this leak doesn't occur on Edge: http://jsfiddle.net/trc4m077/12/ The latest one does though: http://jsfiddle.net/wfdxgj55/6/ Unfortunately I can't yet test my whole app on Edge, as it will take some time to cover all the deprecations. Last update for today: I noticed at least a couple more leaks occurring. Here are a few screenshots from my Heap Snapshots. Will try to isolate them tomorrow. |
I found part of the unresolved leak in 0.8, but part of it has eluded me so far. Again, the good news, as you've discovered, since 0.9 dropped unresolved support, all of the resolver related leaks don't affect it. |
I think I've got the second part of that unresolved leak covered now. The transition leak at least wouldn't compound, but it should also be handled now too. v0.8-dev should be 0.8.12-build-6 and edge should be 0.9.0-build-110. Based on those reference traces, the last unresolved fixes may cover those as well. I'm good with running out the rest of the leaks in this issue. Thanks for isolating them! |
The great news is that the number of detached trees in my app went down dramatically 😃 I'll still investigate the very few other leaks I'm experiencing, as they might be a big deal for other people. Things are beginning to work quite nicely 😎 🎉 Isolated the EventDirective leak (biggest offender now): const Application = Ractive.extend({
template: `
{{#each stuff}}
<a href="#anything" on-click="@this.fire('myEvent')">Link</a>
{{/each}}
`,
data: {
stuff: [1,2,3],
},
onrender() {
// could be setting the array to [] rather than [1,2,3,4], the result is the same
this.set('stuff', [1,2,3,4]).then(() => {
this.set('stuff', [1,2,3]);
});
}
});
window.r = new Application({ el: '#application' }); Notably, no leak when using the deprecated bindings: <a href="#anything" on-click="myEvent">Link</a> |
Found another one, though it might be related to my previous example: const ApplicationHeader = Ractive.extend({
template: `<article>Hi, I'm a component</article>`
});
const Application = window.r = new Ractive({
el: '#application',
template: `
{{#if stuff}}
<ApplicationHeader something="{{thatDoesntExist()}}"/>
{{/if}}
`,
components: {
ApplicationHeader,
},
data: {
stuff: true,
},
onrender() {
this.set('stuff', false).then(() => {
this.set('stuff', true);
});
}
});
So, to sum up: I still need to isolate at least But still, today is a good day :) |
The event directive leak wasn't an issue for edge, but I've got it patched up in v0.8-dev now. The mapping leak is also patched for both edge and v0.8-dev. Thanks! |
Leak occurring with {{#each}} and data changing quickly: const Application = window.r = new Ractive({
el: '#application',
template: `
<section>
{{#each stuff}}
Something to display.<br>
{{else}}
<div>
{{#if doesntEvenExist}}
AAAAA
{{else}}
BBBBB
{{/if}}
</div>
{{/each}}
</section>
`,
data: {
stuff: null,
},
onrender() {
this.set('stuff', []).then(() => {
this.set('stuff', [1, 2, 3]);
});
}
}); Still at least another one to isolate. |
False alarm. These were all issues on my end. Nothing else to report for now .... 😎 🎉 |
I think I got that last one sorted out on v0.8-dev (0.8.12-build-8). It's resolver-related, so it didn't affect edge 🎉 |
Hate to say it, but I've found a couple more :( Not sure if I'll be able to isolate them by today, so in the meantime I'm attaching the reference traces to give you an idea. This won't be super easy to isolate, as it's in the middle of a component with decorators performing dark magic (aka generating charts). Don't be too afraid by the number of trees/entries. It's an SVG. This one is seems much easier to pin down. I'll see what I can do today! |
Offtopic: could you explain shortly how to read such snapshots to find memory leaks? |
@mefjuu a great post on the matter is: https://developers.google.com/web/tools/chrome-devtools/memory-problems/ Specifically the section: Discover detached DOM tree memory leaks with Heap Snapshots |
Unable to reproduce the 2nd issue with plain Ractive. Must be an small, crazy issue, with my bundler/transpiler/whoknows 😭. Pasting here the fiddle for (my) future reference. This exact code results in leaks on my end, basically nothing else except Webpack and transpiler are involved.
Yeah, right |
Based on those heap dumps, I took a guess at what might be causing both of those last leaks. The 0.8 version should be build-9. We're having a little trouble with travis publishing edge, but when that gets sorted, the same change should be available on edge. |
I found one more potentially leaky place. Does there happen to be a series of splice operations involved with that first leak (push, pop, shift, unshift, splice, sort)? |
Oh yes! |
I took a swing at a potential issue involving multiple rebinds (splices) in one turn of the runloop that may resolve that first issue. Since it's an issue with unresolveds, it shouldn't affect edge. The 0.8 build with the change is 0.8.12-build-10. |
Identified the first issue. It was quite simple actually: const ApplicationHeader = Ractive.extend({
template: `
<article>
test test
</article>
`,
onrender() {
// this will leak memory
this.observe('unexistentStuff', () => {})
}
});
const app = new Ractive({
el: '#application',
components: {
ApplicationHeader,
},
data: {
show: true,
},
template: `
<main>
{{#if show}}
<ApplicationHeader/>
{{/if}}
</main>
`,
onrender() {
this.set('show', false);
},
}); |
This one is slightly trickier. What's happening is the I see a few ways to handle this:
I think option 1 is probably the way to go because it is the least likely to introduce breakage and confusion. There may be a few other methods that need to check for torndown too, like |
I went ahead with option 1, which is currently being built by travis to be published as build-11. I didn't add the same check to edge, since there are no unresolveds to cross component boundaries and hold onto torn down instances. |
Agreed, option 1 seems the best. Will test again tomorrow and report back. Thanks! |
Looks like build-11 failed to be published https://travis-ci.org/ractivejs/ractive/jobs/211115330 |
Ah! There was an old deploy script on the 0.8 branch. I've updated it, and build-11 is now published. |
Confirmed as fixed. Tested build-11 it quite a bit today, and found nothing new. As far as I can tell, I think we're done here 🎉 🎉 🎉 😄 Again, thank you so much @evs-chris for the massive help! |
Thanks for doing the tracking! I've pushed out 0.8.12 🎉 |
Awesome work folks. We @mixmaxhq used to use Meteor i.e. Blaze for our frontend, then switched to Backbone due to performance problems, but are now considering Ractive to simplify some complex UIs, and it's terrific to see that there's such a great community actively improving it. |
Description:
Took me hours to pin this issue down in my SPA, but finally managed to reproduce it in a few lines of code 😪
Looks like Ractive 0.8 keeps pointing to unused DOM elements (and possibly something else? whole components?) after their component is teardown.
This may result in big memory leaks, depending on the size of your components.
The more the elements, the bigger the memory being leaked.
I started noticing it on components where 4k-20k elements were being removed. Narrowed it down to a minimal example.
Let me know if I can be of any help.
Versions affected:
0.8.11
notably, 0.7.3 is NOT affected
Platforms affected:
Any browser
Reproduction:
http://jsfiddle.net/trc4m077/4/
you should see 3 groups of Detached DOM Tree:
Those 4 entries will be a lot more, if you're building anything more complex than my example.
See also the attached screenshots from my inspector.
All screenshots have been taken after
ApplicationHeader
had been toggled off.Code:
Interestingly, if you remove the
{{name}}
, no memory will be leaked.From my tests, memory leaks even if
ApplicationHeader
is marked asisolated: true
, andname
gets passed via attribute.The text was updated successfully, but these errors were encountered: