Hi all


Apologies for a rather long post, please bear with me :)



I’ve been thinking a lot lately about memory management in Qooxdoo apps, 
specifically how to reliably remove the need to call dispose() (without causing 
leaks) and reducing complexity to our applications — I have a proposal for how 
to achieve this but it requires some subtle changes to the Qooxdoo framework.  
In theory this would be largely backward compatible (with a few specific 
exceptions) and would make it significantly easier to write long-running or 
memory sensitive applications.



Although the mantra is that “if you created it, you should dispose it”, this is 
increasingly hard to follow as an application becomes more complex.  There are 
specific examples of why this is virtually impossible baked into Qooxdoo, for 
example, some classes override setXxxx methods to *copy* the passed in object 
rather than take ownership of it - which means that the calling code has to 
understand the private implementation of the object in order to correctly 
handle cleanup.  qx.ui.form.SelectBox.setSelected is an example of this - the 
caller has to “know” that setSelected will copy the array it is given and not 
take it over and be responsible for disposing it.



Another common example with qx.data.Array is that methods such as splice and 
slice return an array of the removed items, but the returned object is an 
instance of qx.data.Array so you *must* dispose it.  To illustrate my point, 
take a look at these samples of code and try to determine which is the 
“correct” one:



Sample 1A:

var selection = // … snip: get an array (.. or is it a qx.data.Array ..?)

selection.slice(0,1); // delete the first element



Sample 1B:

var selection = // … snip: get an array (.. or is it a qx.data.Array ..?)

selection.slice(0,1).dispose(); // delete the first element



In order to figure out which piece of code is correct, you must understand the 
“…snip: get an array” and know if it is a native array or a qx.data.Array



How about these examples:



Sample 2A:

var widget = // … snip: get a widget

var selection = new qx.data.Array();

widget.setSelection(selection);



Sample 2B:

var widget = // … snip: get a widget

var selection = new qx.data.Array();

widget.setSelection(selection);

selection.dispose();



Sample 2C:

var widget = // … snip: get a widget

var selection = new qx.data.Array();

var oldSelection = widget.getSelection();

widget.setSelection(selection);

if (oldSelection)

oldSelection.dispose();



In order to choose from those samples, you must understand what the widget 
object is, where it came from, _and_ how it implements setSelection to know 
whether and what to dispose.  This is not good - not only is the answer 
obscure, but if the implementation of widget.setSelection changes, your code 
may break.  Sample 2C is also much longer than 2A, so you’re more likely to 
create a bug when typing it, and of course more code === more testing work.



My final example is simple, predictable layouts, EG:



Sample 1:

var widget = // .. snip get a widget

widget.setLayout(new qx.ui.layout.VBox());



Sample 2:

var widget = // .. snip get a widget

var oldLayout = widget.getLayout();

if (oldLayout)

oldLayout.dispose();

widget.setLayout(new qx.ui.layout.VBox());



Here, Sample 2 is arguably safer (given what we know about the implementation 
of setLayout) but is those 3 extra lines something that we will reliably type 
(and not make typos in, and actually test) every time we want to set the 
“layout” property value?  What about properties less well known than “layout”?



In all of these examples, there is no clear answer when reading the code as to 
who should handle the memory management and in many cases the work required to 
really satisfy the safe disposal of objects is difficult to know.  To me, this 
is exactly why garbage collection features so prominently in modern languages.  
Why then does it seem like we’ve gone back a step with Qooxdoo and its reliance 
on the ObjectRegistry?



Searching through the code reveals some possible explanations, but the comments 
for ObjectRegistry kind of says it all: 

/**

 * Registration for all instances of qooxdoo classes. Mainly

 * used to manage them for the final shutdown sequence and to

 * use weak references when connecting widgets to DOM nodes etc.

 *

 * @ignore(qx.dev, qx.dev.Debug.*)

 */



This says that the ObjectRegistry is there so that all un-disposed objects can 
be disposed, and so that there are weak references between DOM nodes and the 
Widgets they represent.  IMHO it’s possible to show that the weak links from 
DOM objects are the only reason that matters.



Searching the code, and ignoring any debug specific code (e.g. qx.core.Logger 
and qx.dev.*), the only things that depend on ObjectRegistry having a lookup of 
every object are qx.ui.core.Widget and qx.data.controller.Tree.  Code elsewhere 
depends on a widget having a hashCode, but crucially *not* depending on that 
hashCode being listed in the ObjectRegistry in a big lookup table of everything.



In my own code, I use an object’s toHashCode and use the result in a lookup 
table - but only as a key in my own map, and rarely (if at all) by passing to 
qx.core.ObjectRegistry.fromHashCode().



If true for others then this is an important point because it means that by 
only registering objects with ObjectRegistry that _need_ to be registered (i.e. 
Widgets), memory management issues can almost completely disappear.  To test 
this, I modified qx.ui.core.Widget.construct, qx.html.Element.__flush, and 
qx.data.controller.Tree.bindProperty so that all qx.core.Object’s have a 
hashCode, but only Widgets are registered with ObjectRegistry.



…and then re-ran the Qooxdoo framework test suite.  At the end of the run only 
413 objects were not disposed.  Not much, right?  But without this patch there 
were 4,500 objects unfreed.  (OK it’s fair to say that unit tests are not 
required to free up resources that they consume, but it is striking how easy it 
is to have significant resource leaks that can be so easily countered by 
garbage collection)



A potential issue is that with garbage disposal, there are no destructors.  
That’s not to say that Qooxdoo destructors will not work, but they are only 
called if the objects are explicitly disposed() - and if automatic memory 
management is the norm, its increasingly less likely that the destructors will 
ever get called.



The disadvantages of this proposal is that it includes a change to a core part 
of the library and that therefore brings risk of issues; that destructors are 
no longer possible and that fromHashCode cannot be used without extra code are 
small but potentially important details.  On the other hand, the potential 
benefits are significant in reduced development time and reduced coding errors 
- the work necessary to manually manage memory can be considerable, and very 
difficult to test especially when it’s dependent on user interaction.  



I’ve created a branch in my fork of Qooxdoo to experiment with this, which you 
can see here: 
https://github.com/johnspackman/qooxdoo/tree/automatic-memory-management The 
automatic-memory-management branch is from the current master so if you’re 
using Qooxdoo 5.x you should be able to switch to it and experiment without any 
other side effects.



Any thoughts are welcome :)



Regards

John


------------------------------------------------------------------------------
_______________________________________________
qooxdoo-devel mailing list
qooxdoo-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel

Reply via email to