Skip to content

Instantly share code, notes, and snippets.

@runspired
Created November 5, 2015 06:35
Show Gist options
  • Save runspired/b5e50a2a70b6308ee560 to your computer and use it in GitHub Desktop.
Save runspired/b5e50a2a70b6308ee560 to your computer and use it in GitHub Desktop.
ES6 Fat Arrow ( => ) functions can cause memory leaks when used with Event Handlers.
class Foo {
constructor(element) {
this.element = element;
this.setupHandlers();
}
doFoo() {}
setupHandlers() {
this.listener = () => {
this.doFoo();
}
this.element.addEventListener('scroll', this.listener, true);
}
destroy() {
this.teardownHandlers();
this.element = null;
}
teardownHandlers() {
this.element.removeEventListener('scroll', this.listener, true);
// without this next line, the `Foo` instance and any other scope present within the instance
// will leak, preventing garbage collection.
this.listener = null;
}
}
@runspired
Copy link
Author

Additionally, this is the exact code involved, and the commit which fixed the issue.
https://github.com/runspired/smoke-and-mirrors/blob/e3de4397ec53746ec935546b1c5a36a3afded814/addon/models/radar.js#L186-L226

Other leaks and other teardown needs were addressed in the commit as well, but all you need to replicate the leak is to remove this line: https://github.com/runspired/smoke-and-mirrors/blob/e3de4397ec53746ec935546b1c5a36a3afded814/addon/models/radar.js#L223

Steps to replicate

  • Clone the repo from this commit html-next/smoke-and-mirrors@e3de439
  • Remove that line
  • set isDevelopingAddon to return true in index.js
  • run the dummy app with ember serve
  • navigate to http://localhost:4200/#/examples
  • take a heap snapshot
  • click on infinite scroll from the demos list
  • scroll around a bit. The more you scroll the more pronounced the leak will be because more objects will be retained (the vertical-collection was the item being retained.
  • navigate back to the demos by clicking the demos link in the navbar
  • wait a moment just to ensure that GC has had way more time than necessary to kick in
  • take a heap snapshot
  • Compare snapshots 1 & 2 by selecting 2
  • then in the drop down that says "All Objects" choose "Objects created between snapshot 1 and snapshot 2". - Sort by retained size
  • Take a peek at that giant Object, that's the leaked object.

As you navigate around it's references, you'll notice a lot of the paths eventually go back to filterMovement as the closest thing to window (I think 5 levels deep if I recall?), this is the function that's invoked (via backburner, which is executing the loop in a requestAnimationFrame).

Now uncomment out the line that nulls the scroll handler, and repeat. No leak. :/

@benlesh
Copy link

benlesh commented Nov 5, 2015

I guess what I was getting at is it's not necessarily a leak with "arrow functions", specifically. It's more of a leak with that pattern.

@jayphelps
Copy link

Have you tried testing this in Chrome, without transpilation? It natively supports classes and arrow functions in strict mode. I would also recommend trying it isolated from the smoke-and-mirrors project, if you haven't already. From what has been described, I believe this should be reclaimed. ¯_(ツ)_/¯

Unless of course you're holding on to a reference of instance Foo or this.listener elsewhere..clarifying, just in case 😏

@runspired
Copy link
Author

Unless of course you're holding on to a reference of instance Foo or this.listener elsewhere..clarifying, just in case

I assumed I was, and spent a huge amount of time digging through the heap and my code making sure things were properly dereferenced. It's still possible, but at this point highly improbable. I will be digging more.

I have not tested without transpilation.

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