Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular.js memory leaks, when should I start worrying

So I have this rather large application built on angular, alot of nested states, alot of directives, data tables and stuff.

We recently decided to switch to full single page, instead of having few small single apps sections(say articles, people, dashboard were each a small single page app previously), so I started dealing with performance a bit more. In chrome you don't visually notice a thing, on firefox on the other hand I THINK it becomes slower over time.

So I started with three snapshot technique to see what's up. But I'm not entirely sure what to make of it.

IMAGE enter image description here

  • snapshot size doubles its size each snapshot taken(1st 15mb, 2nd 67mb, 3rd 120mb), does this mean anything?
  • there's a LOT of red dom, 4000 of red divs for example

Now I feel that those red divs, spans and anchors are mainly my fault, I'm doing some not-so-usual stuff to render those data tables, using this directive I made, and I also feel that some of heap objects are result of those dom elements not being removed properly.

This is what the table directive essentially does

var rows = '<div class="row" ng-repeat="item in items">';

_.each(columns, function(column) {
   // pass cell as a string from $templateCache, instead of having <table-cell type="column.type"> which loaded correct templateUrl depending on what was passed via type attr
   var cellTemplate = $templateCache.get(column.type);
   rows += '<div class="column">' + cellTemplate  + '</div>';
});

rows += '</div>';

// el is from directive link function
el.html(rows);

$compile(el.contents())(scope);

Reason why I'm doing this at all is because when I tried to use nested ng-repeat for rows and columns and <table-cell> directive for cells, it took way too long to render, even with only about 6 columns and 50 rows.

So what I think is happening is that none of those divs inside this table gets removed properly so they keep stacking up everytime this table directive loads.

Now even if I deal with that detached dom tree.. what about all the other stuff, how do I know which I should try and deal with, and which are usual for angular and doesn't really affect the performance?

// edit table directive on plunker http://plnkr.co/edit/1fZi6mVn2jBIGF0Q2a40?p=preview

like image 603
fxck Avatar asked Apr 25 '14 09:04

fxck


2 Answers

The leak was actually caused by another, completely harmless looking directive that was used in the header of the table and was doing nothing but creating array of "sort items" and then printing it using ng-repeat.. and what's worse it was not caused by anything I did inside that directive but by its replace: true.. god knows why, I shall try and reproduce it on plunker and report on github.

Since it was almost impossible to find which part of the app was causing it just by looking at the heap report, I went on and deleted all other parts of the app but the one I suspected was causing it, then I found out it wasn't that one, so I went on and kept readding all others.

Once I found out the real problematic directive, I did the same, kept deleting parts of its code until there was literally nothing inside.

Then it was obvious it was one of directive's options, then I found out replace was causing it.

like image 101
fxck Avatar answered Sep 21 '22 22:09

fxck


That directive isn't leaking anything. I forked your plnkr, added way more objects to make it noticeable if that were leaking, and added a button which reloads the table. It waits one second and then populates it again.

Nodes are eliminated and recreated. The steps to try to locate the leaks are:

  • Start recording
  • Wait 2 seconds
  • Press the button
  • Wait 10 seconds
  • Stop recording

Doing that exactly 3 times (so you have the same state of memory), you'll see that the same memory is being used:

enter image description here

You can check the forked plnkr

After this, I can only try to guess what could be the issue. I'm going to give you a couple of suggestions based on what I discovered here.

  • Check directives, they are usually the cause. The frickingTable doesn't seem to be doing anything harmful in it's own but other customs ones could, even if they're not yours, shit just happens.
  • If the table is being populated with some sort of "polling", empty the object before reassigning. So, if you see the forked plnkr, you'll see that I first put items to null and then assign. That should be the way. In the plnkr I did it for it to be noticeable (that the table is being re-populated) however, I've found that browsers tend to keep references somehow if you don't clean objects/arrays. I clean arrays with .length = 0 and objects with null, that makes all possible references to that object ready to be GCed and won't cause any leaks. This sounds silly, but I've seen it in Angular and also in Backbone so it has to be a browser thing.

I can't really think of anything else without seeing any more code. Hope this points to you in the right direction, leaks are annoying to say the least.

like image 20
Antonio Laguna Avatar answered Sep 20 '22 22:09

Antonio Laguna