sleemanj / xinha

WYSIWYG HTML Editor Component (turns <textarea> into HTML editors)
http://trac.xinha.org/
Other
13 stars 2 forks source link

about memory use (Trac #30) #30

Closed sleemanj closed 3 years ago

sleemanj commented 19 years ago

Hi again! I used htmlarea, but when I saw xinha I think this is very cool and faster. I noticed in htmlarea that when I reload a page with the component, the memory was increased in 2mb more, and each refresh that I made add 2mb more to the ie process, I try refreshing the page between six to ten times, and the components hang, maybe when I try two or three times. Well, that was because I use xinha and saw the same problem, I feel Xinha is more faster, but when I refresh the page the memory doesn??t be purged and only increment the ammount of the ie process.

There are some posts in htmltextarea forum (only if you want to see more information) search "memory leak".

thanks for Xinha ;) forgottentowers

Reported by forgottentowers, migrated from http://trac.xinha.org/ticket/30

sleemanj commented 19 years ago

mokhet commented:

I tried to apply bobesch's patch to panels, but it does not break the 4 leaks of the TD. It is because those TD are hidden (display:none), and so are leaking in IE (another of the kind of possible leaks in this browser).

So i've changed a bit HTMLArea.free() to take care of this situation in changeset:533, from here I think the leaks are greatly reduced. Can someone confirm drip is happy now ?

I'm not convinced by the proposal to setup a global tracking of editors with the variable HTMLArea.EDITORS created in makeEditors(). makeEditors() is NOT the only way to create the editor so we can't rely on this method to set such variable which, if this proposal is implemented, become needed to have Xinha work. And i dont understand the idea behind adding another variable. We already have plenty (too much) of variables to rely on (htmareas, this.htmlarea_id_num, this._htmlArea, this._textArea, this.editor)

I dont like either the reordering of the framework. It doesnt seems correct to me to do it this way, despite what MSDN crap can suggest.

sleemanj commented 19 years ago

djeeg commented:

i think the reason behind the global array of editors is so that inner anonyomous functions can have access to the current editor (this) by a string variable in the call stack/state rather than storing the editor itself. this way the memory usage is reduced and can be free'd easier by the GC since its just a string. i'm just grasping at straws with this explaination though, just tried it and it worked.

sorry i didnt read the code enough to see the htmlarea_id_num property, so the better way to do it would be this.

HTMLArea.prototype.setEditorEvents = function()
{
  var editorid = this.__htmlarea_id_num;

  var nested_event = function(event) { 
      return __htmlareas[editorid]._editorEvent(HTMLArea.is_ie ? __htmlareas[editorid]._iframe.contentWindow.event : event);
  }

  __htmlareas[editorid].whenDocReady(
    function()
    {
      // if we have multiple editors some bug in Mozilla makes some lose editing ability
      HTMLArea._addEvents(
        __htmlareas[editorid]._doc,
        ["mousedown"],
        function()
        {
          __htmlareas[editorid].activateEditor();
          return true;
        }
      );

      // intercept some events; for updating the toolbar & keyboard handlers
      HTMLArea._addEvents(
          __htmlareas[editorid]._doc,
        //["keydown", "keypress", "mousedown", "mouseup", "drag"], 
        ["keydown", "keypress", "mousedown", "mouseup"], <-- REMOVED DRAG
      nested_event
      );  

      // check if any plugins have registered refresh handlers
      for ( var i in __htmlareas[editorid].plugins )
      {
        var plugin = __htmlareas[editorid].plugins[i].instance;
        HTMLArea.refreshPlugin(plugin);
      }

      // specific editor initialization
      if ( typeof __htmlareas[editorid]._onGenerate == "function" )
      {
        __htmlareas[editorid]._onGenerate();
      }
      __htmlareas[editorid].removeLoadingMessage();
    }
  );
};
sleemanj commented 19 years ago

mokhet commented:

Clever. So basically, because i need to understand :) , this has nothing to do with leaks but it's about amount of memory used over the different call stack, right ? I wasnt able to see the valuable behind this update, now I think I understand it loud and clear, thanks for the explanation (please correct me if i'm wrong). I think this patch should do the trick, i'll look more at it (and test) later probably sunday, if nobody else jump on it. But for now it's about time to go bed :) despite the valuable informations i've learned tonight, thank to you.

Index: htmlarea.js
===================================================================
--- htmlarea.js   (revision 536)
+++ htmlarea.js   (working copy)
@@ -2340,18 +2340,19 @@

 HTMLArea.prototype.setEditorEvents = function()
 {
-  var editor=this;
-  var doc=this._doc;
-  editor.whenDocReady(
+  var id = this.__htmlarea_id_num;
+  this.whenDocReady(
     function()
     {
+      var editor = __htmlareas[id];
+      var doc = editor._doc;
       // if we have multiple editors some bug in Mozilla makes some lose editing ability
       HTMLArea._addEvents(
         doc,
         ["mousedown"],
         function()
         {
-          editor.activateEditor();
+          __htmlareas[id].activateEditor();
           return true;
         }
       );
@@ -2362,7 +2363,7 @@
         ["keydown", "keypress", "mousedown", "mouseup", "drag"],
         function (event)
         {
-          return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
+          return __htmlareas[id]._editorEvent(HTMLArea.is_ie ? __htmlareas[id]._iframe.contentWindow.event : event);
         }
       );

@@ -2379,7 +2380,7 @@
         editor._onGenerate();
       }

-      HTMLArea.addDom0Event(window, 'resize', function(e) { editor.sizeEditor(); });
+      HTMLArea.addDom0Event(window, 'resize', function(e) { __htmlareas[id].sizeEditor(); });
       editor.removeLoadingMessage();
     }
   );

Why is removing the "drag" event should change anything in the leak situation ?

Is the "nested_event" declaration a must have ? I dont see the point to do it (yeah, again :( sorry to ask explanation but i dont understand why to do it), despite perhaps to have the event handler more readable, ... hummm but after making the patch, i think i start to see the point behind. Is it again a trick to reduce the memory usage ? is it really doing something to the memory usage ? damn it, i need to bench this new info. Basically, it seems to means we could save a lot of usefull memory (especially for IE) all over the code and in plugins. Interesting...

sleemanj commented 19 years ago

djeeg commented:

when you declare a function inline like this

       HTMLArea._addEvents(
         doc,
         ["mousedown"],
         function()
         {
           editor.activateEditor();
           return true;
         }
       );

it is called a closure apparently. the issue is IE doesnt do a good job of garbage collecting the objects when the page refreshes due to some referencing problems. a way around this in this instance is to try to make the call stack memory as small as possible. that is, instead of having a reference to the editor, which is an object, which propably has a reference to every other object. have instead only the id of the editor on the call stack. this way the memory used is only very small. it also means that the garbage collection for the closure can complete because there is only a primative.

sleemanj commented 19 years ago

guest commented:

Are you really sure, djeeg, that a string occupies less memory on a stack frame than a reference to an object? When you pass a 'var editor' to myFunction(), you pass in a reference, a sort of pointer (although javascript references aren't C pointers, see the Definitive Guide 4th edition, p. 166), not the the object itself. I don't know how big javascript references can get, but I guess there's no common mesure between the size of an object reference and the size of the object itsself. So I heavily doubt that not passing around 'editor' will save us a noticeable amount of memory on the stack. Otherwise all the Ajax library stuff out there would be heavily flawed in this respect, since they pass objects around all the time.

The real problem, however, isn't about the size of memory occupied by strings vs. references, the real problem in this context is about closures. But sure, I admit, your proposal is a smart way to work around the problem, because leaking a string is a small problem, while leaking an object can be a huge one. So I finish to contradict myself? No, the memory occupied by the object reference is small, but the memory not freed because the JS engine does not release the reference is big. Because on unload it's not only the memory occupied by the reference that is important, but the memory of the whole object it references. This amount of memory has to be freed, and failing to do so adds the size of the whole object to the memory footprint of the application, on each reload.

sleemanj commented 19 years ago

mokhet commented:

Yes, i think like "guest" that there's no benefice to use a string or an object all over the calling stack. I am not able to find any significant enhancement in the amount of memory used by one or another method. But i'm not an expert.

I'm thinking we should give up on the HTMLArea.free and HTMLArea.freeLater and use instead some kind of disposer which could be execute for every Xinha generated and every plugins. Would be easier to know what can leaks and what is not, since everything would be tied up on the same place instead of having a lot of HMTLArea.freeLater all over the code.

sleemanj commented 19 years ago

bobesch commented:

Just for the record: The 'guest' commenting on djeeg and the closure issue was me (sorry, forgot to change the email/username field).

Yes, a kind of disposer would be great, but I don't see how we can implement it ad hoc, given the complexity of Xinha. The best thing I see for now is to really evince this leakage problem. This also helps us to understand the problem points and -- who knows -- figure out how to implement the desirable disposer.

As for the panel/stylist issue, to prevent the panel TDs from leaking, the following is still necessairy (according to drip 0.4)

Index: htmlarea.js
===================================================================
--- htmlarea.js   (revision 536)
+++ htmlarea.js   (working copy)
@@ -200,6 +200,7 @@
       panels[i].container.className = 'panels ' + i;
       HTMLArea.freeLater(panels[i], 'container');
       HTMLArea.freeLater(panels[i], 'div');
+      HTMLArea.freeLater(panels[i], 'panels');
     }
     // finally store the variable
     this._panels = panels;
sleemanj commented 19 years ago

djeeg commented:

this is absolutely correct

No, the memory occupied by the object reference is small, but the memory not freed because the JS engine 
does not release the reference is big. Because on unload it's not only the memory occupied by the reference that is important, 
but the memory of the whole object it references. *This* amount of memory has to be freed, and failing to do so adds the size of 
the whole object to the memory footprint of the 
application, on each reload. 

there are two ways to fix this problem: either fix the closure issue, which seems that it will never be 100% good, or change the amount of memory that isn't garbage collected in the closure. That is the whole editor ~2mb, to just an int variable for the editor id. ~4 bytes

the current logic for the attach and detach of events is flawed too and needs to be changed to my recommentdation on "Fri Jul 21 21:31:46 2006"

any ideas about what to do with the drag events?

sleemanj commented 19 years ago

bobesch commented:

Djeeg, I don't understand why drag events are special. Could you please explain in some detail? As for the other events, I don't understand (yet) whether the detach logic is flawed or not (drip doesn't show any leakages, but memory consumption goes up by 1-2MB on each reload).

For the moment I'm looking into the event handlers themselves. For instance, at each keystroke memory consumption goes up by 20-30KB. Is this normal?

And yes, your workaround does the right thing in practical terms. Still it's sort of a kludge. On the other hand you could argue, that IE does not merit better than kludges, and you're probably right on this one. Perhaps I'm simply too ambitious when wishing to get it done the "clean way".

Warmest regards, Bodo

sleemanj commented 19 years ago

bobesch commented:

Just wanted to confirm the bench by mokhet:

In _addEvents, the original

return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);

does not leak more (if at all) than djeeg's proposed

return __htmlareas[id]._editorEvent(HTMLArea.is_ie ?__htmlareas[id]._iframe.contentWindow.event : event);

With drip showing no more leaks, fixing the remaining 1MB or so leak is getting difficult. Any ideas?

sleemanj commented 19 years ago

bobesch commented:

Another freeLater() patch for the SuperClean plugin (courtesy of drip 0.4)


Index: super-clean.js
===================================================================
--- super-clean.js    (revision 528)
+++ super-clean.js    (working copy)
@@ -267,6 +267,8 @@
   // Now we have everything we need, so we can build the dialog.
   var dialog = this.dialog = new HTMLArea.Dialog(SuperClean.editor, this.html, 'SuperClean');

+  HTMLArea.freeLater(this, 'dialog');
+
   this.ready = true;
 };
sleemanj commented 19 years ago

bobesch commented:

In an earlier post in this thread, I pointed to the fact that memory consumption in IE goes up by 20-30 KB on each key stroke. The culprit seams to be the first part of updateToolbar() where the breadcrumbs representing the DOM tree in the status bar bet assigned event handlers which backreference the javascript object. These handlers get (re-)created along with the A-Tag nodes on each and every keystroke but only get nullified when flushEvents() is called upon unload. To prevent IE from leaking we have to nullify the old ones before creating new ones, right? The following patch attempts to do this with quite noticeable results.

Index: htmlarea.js
===================================================================
--- htmlarea.js   (revision 536)
+++ htmlarea.js   (working copy)
@@ -1407,6 +1408,8 @@
     statusbar.style.display = "none";
   }

+  this._statusBarItems = [];
+
   return statusbar;
 };

@@ -2972,11 +2975,23 @@
   var doc = this._doc;
   var text = (this._editMode == "textmode");
   var ancestors = null;
+
   if ( !text )
   {
     ancestors = this.getAllAncestors();
     if ( this.config.statusBar && !noStatus )
     {
+      while (this._statusBarItems.length) { // chop circular reference for IE
+        var item = this._statusBarItems.pop();
+  item.el = null;
+  item.editor = null;
+  item.onclick = null;
+  item.oncontextmenu = null;
+  item._xinha_dom0Events['click'] = null;
+  item._xinha_dom0Events['contextmenu'] = null;
+  item = null;
+      }
+
       this._statusBarTree.innerHTML = HTMLArea._lc("Path") + ": "; // clear
       for ( var i = ancestors.length; --i >= 0; )
       {
@@ -2993,6 +3008,9 @@
         a.href = "javascript:void(0)";
         a.el = el;
         a.editor = this;
+
+        this._statusBarItems.push(a);
+
         HTMLArea.addDom0Event(
           a,
           'click',
sleemanj commented 19 years ago

ray commented:

applied patch from last comment in rev 780

sleemanj commented 19 years ago

ray commented:

I spent Saturday afternoon pressing F5 and doing some statistcs, mainly to find out if calling Xinha.collectGarbageForIE() on unload is any good in Mozilla. Answer is no; it takes really some time to unload the page, to no avail. Disabled it for Mozilla in rev 807 http://xinha.raimundmeyer.de/attic/XinhaMemoryUsage/XinhaMemoryUsage-Dateien/image001.gif What you see, is that there is memory leaking in Mozilla, but equally with !JavaScript or static HTML pages. In IE, memory usage is almost 100% stable with HTML, but leaking in ridiculous amounts with JS. It's better with our own garbage collecting, but still far worse than Firefox.

http://xinha.raimundmeyer.de/attic/XinhaMemoryUsage/XinhaMemoryUsage-Dateien/image002.gif

sleemanj commented 19 years ago

ray commented:

Microsoft claims to have improved their memory handling in IE8. I wondered if this meant we can remove the irritating manual garbage collection for IE8. To find out I continued the test from my last post to include IE8. The findigs were that memory leaking is now linear as opposed to the ridiculous roller coaster curves in previous versions, though in no way as flat as in Firefox and Safari AND the curves show that our garbage collection gives a small advantage, but not a significant one. [[Image(http://xinha.raimundmeyer.de/attic/XinhaMemoryUsage/XinhaMemoryUsage-Dateien/image002.gif)]]

sleemanj commented 19 years ago

ray commented:

disabled in rev 980

sleemanj commented 19 years ago