Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #10782 (closed defect: fixed)

Opened 4 months ago

Last modified 5 days ago

Protoype 1.6 introduce memory leaks on Firefox

Reported by: wpc Assigned to: sam
Priority: high Milestone: 2.x
Component: Prototype Version: edge
Severity: major Keywords:
Cc:

Description

I wonder is there anyone else having same memory problem with us for prototype 1.6. Our app has massive memory leak on firefox after upgrade to 1.6.0. I have done a little bit analysis and find a way reproducing it consistently. I found once a Object having reference to DOM elements(directly or indirectly), and the object have been bind to some function using bind() or bindAsEventListener(), DOM elements referenced by the object will never get garbage collected. Here is a simple example

      <div id='container'> massive data that occupying memory... <div>
      <script type="text/javascript">
        Foo = Class.create({
          initialize: function(containerId){
            this.container = $(containerId);
          },
  
          bar: function(){
            var dummyBind = function(){}.bind(this);
          }  
        });

        new Foo('container').bar();
      </script>

Then you find memory get increased every-time you do a refresh. The amount of memory that increased is depending on how much data you put in the 'container' div.

With my test data(2741 links in the container div), refresh this simple page for 100 times, make memory goes up to 183.59 mb. For prototype1.5, in the same condition browser only use 51.09 mb. I'm on Mac OSX tiger and my firefox version is 2.0.0.11

I am pretty sure it is the DOM element not get collected. Because if I do a window unload hook to null out object's reference to the 'container' div, the memory get collected backs.

I also test the page with leak-gauge, which proves the result I see through Activity Monitor. The process I take is I open the test page, then go to about:blank, then closed window. The result shows me both the inner window and document leaked. ( leak report for about blank is a mistake, that's simply because its the last page before the browser closed ). The report is like this:

prototype 1.5

Leaked 0 out of 13 DOM Windows
Leaked 0 out of 42 documents
Leaked 0 out of 5 docshells

prototype 1.6

Leaked inner window 171c2f50 (outer 16a03aa0) at address 171c2f50.
 ... with URI "file://localhost/Users/wpc/code/memory_leak/test.html".
Leaked inner window 17bfca90 (outer 16a03aa0) at address 17bfca90.
 ... with URI "about:blank".
Leaked outer window 16a03aa0 at address 16a03aa0.
Leaked document at address 25b1200.
 ... with URI "file://localhost/Users/wpc/code/memory_leak/test.html".
Summary:
Leaked 3 out of 13 DOM Windows
Leaked 1 out of 41 documents
Leaked 0 out of 5 docshells

The attached files include the test page I use and js files both prototype1.6 and 1.5. When you try to switching to prototype 1.5, just change the script tag in the test.html to use prototype15.js. And a firefox runtime log also included as well.

Attachments

memory_leak.zip (55.1 kB) - added by wpc on 01/12/08 10:27:10.
test page for show prototype 1.6 memory leak on firefox
patch.diff (0.6 kB) - added by wpc on 01/13/08 19:08:53.
I still haven't figure out the reason of leaking on firefox, but this patch is good enough for fixing it.
perf.zip (14.8 kB) - added by wpc on 01/25/08 15:19:05.
testcase and performace test framework for prototype -- please download selenium_server.jar first

Change History

01/12/08 10:27:10 changed by wpc

  • attachment memory_leak.zip added.

test page for show prototype 1.6 memory leak on firefox

01/13/08 02:34:54 changed by Tobie

Thanks for the report.

The initial tests I (quickly) made seem to imply that only the legacy (deprecated) class creation system leaks.

Try:

      Foo = Class.create({
        initialize: function(containerId){
          this.container = $(containerId);
        },
  
        bar: function(){
          var dummyBind = function(){}.bind(this);
        }  
      });

which seems to be unaffected by the issue you mention.

01/13/08 02:52:19 changed by wpc

Thank you for your responds.

Unfortunately it is not the reason. I have tried new syntax before create this ticket, it still goes to near 200mb after 100 refresh. I use old syntax to in the sample to make people be able to switch between 1.5 and 1.6 smoothly.

01/13/08 06:31:51 changed by wpc

hi, Tobie:

I think I find where causing the problem in 1.6.0 prototype code, line 252

Function.prototype.defer = Function.prototype.delay.curry(0.01);

This make big difference on memory print, even I do not use the defer() function at all. I simply remove this and reimplement defer without using curry:

Object.extend(Function.prototype, {
  ...
  defer: function() {
    var args = [0.01].concat($A(arguments));
    return this.delay(args);
  }
  ...
});

It works -- falling back to the memory usage of 1.5.1

But, I still can not understand why this could cause such a big memory leak. If you can explained me, it will be very appreciate.

I can submit a formal patch if you prefer. And if you guys interested in, I could clean up and contribute the tool-set for testing the memory leak also. It's basicly a ruby script that start a webrick server to serve the test pages and then use selenium server open a browser and refesh a page for 100 times.

01/13/08 15:16:09 changed by wpc

Oh, Sorry, the implement should be

Object.extend(Function.prototype, {
  ...
  defer: function() {
    var args = [0.01].concat($A(arguments));
    return this.delay.apply(this, args);
  }
  ...
});

01/13/08 19:08:53 changed by wpc

  • attachment patch.diff added.

I still haven't figure out the reason of leaking on firefox, but this patch is good enough for fixing it.

01/14/08 10:54:39 changed by Tobie

Thanks for the work so far.

I'm a little bit concerned about applying that patch without figuring what the underlying issue is.

Could Function#curry be the one leaking ?

Is Class.create the offender, etc.

01/16/08 11:32:28 changed by Tobie

Hi,

Just checking in to see if you've been able to isolate the leak issue more clearly.

Also, would like to know if you've been able to reproduce this in FF 3b2

Best,

Tobie

01/25/08 14:05:22 changed by wpc

Hi, Tobie:

Sorry for slow responding. I was crazy busy this and last week. I will try give some work on the problem this weekend.

Last night I got a little bit time, so I created a ruby test case which can accurately shows the memory leak. I gonna attach it after this reply. Basically its the script I mentioned before, I rewrite it into a test which will failed when memory increment is more than 40mb. For now this test only works on unix system, because I use "ps" to exam memory usage.

how to run the test:

make sure you have java >= 1.5, ruby 1.8 installed

* extract the pref.zip to prototype's code test directory ( trunk/test/perf )

* if you are on linux, add firefox-bin's directory to $PATH. Normally its in '/usr/lib/firefox'

* start selenium server.

    test/perf/$ java -jar bin/selenium-server.jar 

* use ruby to run the ticket10782_test.rb

The test will * start a webrick server with test pages (under 'test/perf/pages') and prototype file( under 'dist'), * open a firefox browser, * open the test page 100 times, * verify the memory usage.

For its using the prototype.js from 'dist' dir from code root directory, make sure you have run rake dist before run test to have your new changes goes into the merged file.

On my machine the test result is this:

HAL9000:~/code/prototype/test/perf wpc$ ruby ticket10782_test.rb 
start test server at localhost:4001
Loaded suite ticket10782_test
Started
F
Finished in 52.940253 seconds.

  1) Failure:
test_memory_leak(Ticket10782Test) [ticket10782_test.rb:15]:
hey, 142.4765625mb memory increase seems like a memory leak!.
<false> is not true.

1 tests, 1 assertions, 1 failures, 0 errors
stop test server

And after apply the patch, the memory increments goes down to 2xmb.

I hope this test would help and enable us to resolve this problem together. And I also think prototype project really needs some framework like this to do performance regressive tests.

Best Regards

01/25/08 15:19:05 changed by wpc

  • attachment perf.zip added.

testcase and performace test framework for prototype -- please download selenium_server.jar first

01/25/08 15:23:10 changed by wpc

OOPS, looks like trac do limitation on file size, so I remove the selenium_server.jar from the perf.zip. You can find it at http://selenium-rc.openqa.org/download.jsp

please extract selenium_server.jar out and put it at test/perf/bin directory

04/10/08 19:44:03 changed by kangax

I have a feeling it's caused by an anonymous function in setTimeout in Function#delay. I wonder if declaring function first, then passing it into a setTimeout would fix the leak.

05/07/08 19:08:57 changed by jdalton

  • status changed from new to closed.
  • resolution set to fixed.