Destructor support
Basics
To destruct existing objects at the end of your application is an important feature in the ever growing area of web applications. Widgets and models are normally handling a few storage fields on each instance. These fields need the dispose process to work without memory leaks.
Normally, JavaScript automatically cleans up. There is a built-in garbage collector in all engines. But these engines are more or less buggy. One problematic issue is that browsers differentiate between DOM and JavaScript and use different garbage collection systems for each (This does not affect all browsers, though). Problems arise when objects create links between the two systems. Another issue are circular references which could not be easily resolved, especially by engines which rely on a reference counter.
To help the buggy engines to collect the memory correctly it is helpful to dereference complex objects from each other, e.g. instances from maps, arrays and other instances. You don’t need to delete primitive types like strings, booleans and numbers.
qooxdoo has solved this issue from the beginning using the included “dispose” methods which could be overridden and extended by each class. However through the rapid growth of qooxdoo and the classes it got more and more verbose and this way also a bit confusing to manage the dispose handling.
qooxdoo 0.7 introduces a new class declaration. This class declaration supports real “destructors” as known from other languages. These destructors are part of the class declaration. The new style makes it easier to write custom destructor/disposer methods because there are many new helper methods and the whole process has been streamlined to a great extend.
Compare for yourself
qooxdoo 0.6
qx.Proto.dispose = function() { if (this.getDisposed()) { return; } this._data = null; this._moreData = null; if (this._buttonOk) { this._buttonOk.dispose(); this._buttonOk= null; } if (this._buttonCancel) { this._buttonCancel.dispose(); this._buttonCancel= null; } if (this._childs) { for (var i=0; i<this._childs.length; i++) { this._childs[i].dispose(); this._childs[i] = null; } this._childs = null; } my.superclass.prototype.dispose.call(this); }
qooxdoo 0.7
destruct : function() { this._disposeFields("_data", "_moreData"); this._disposeObjects("_buttonOk", "_buttonCancel"); this._disposeObjectDeep("_childs", 1); }
The result
The resulting code is much more appealing and hopefully leads to more classes with correct destruct handling than in the past.
At a glance
- An “is this instance already disposed” check is not needed anymore
- Also no need to call the superclass
- Finally in most cases the existing helper methods
_disposeFields,_disposeObjectsand_disposeObjectDeepcan replace existing, more complex statements.
What the functions do
_disposeFields: Deleting each key name given from the instance. This is the fastest of the three methods. It basically does the same as the nullify used in qooxdoo 0.6._disposeObjects: Dispose the objects (qooxdoo objects) under each key and finally delete the key from the instance like_disposeFields._disposeObjectDeep: This methods just works for one key. It does not iterate through the arguments, like the other two. The second parameter defines the depth to follow inside the object structures. To just delete the object and each reference inside the object define a second parameter of0. With a value of1each object inside gets disposed, too, not just dereferenced. This also works on deeper structures, e.g. on an array of maps which contain references to widgets).
How to test the destructor
Enable the debug code
The destructor code of 0.7 allows you an in-depth analysis of the destructors and finds fields which may leak etc. The DOM tree gets also queried for back-references to qooxdoo instances. These checks are not enabled by default because of the time they need on each unload of a typical qooxdoo based application.
To enable these checks you need to select a variant and configure a setting.
The variant qx.debug must be on. The setting qx.disposerDebugLevel must be at least at 1. Higher values mean more output. For a general analysis 1 should be enough. You need to pass the following flags to the generator:
--use-variant qx.debug:on --use-setting qx.disposerDebugLevel:1
If you are a user of the Makefile based skeleton system you can easily configure these values by adding the above options to the build and/or source options like shown below:
APPLICATION_ADDITIONAL_SOURCE_OPTIONS = --use-variant qx.debug:on --use-setting qx.disposerDebugLevel:1 APPLICATION_ADDITIONAL_BUILD_OPTIONS = --use-variant qx.debug:on --use-setting qx.disposerDebugLevel:1
If you are not only using the qooxdoo framework, but work with the framework source itself, you may alternatively change the setting qx.disposerDebugLevel in the source file of class qx.core.Object. For regular application development. though, modifying the local framework code is not recommended.
Log output from these settings should look something like this:
35443 DEBUG: testgui.Report[1004]: Disposing: [object testgui.Report]FireBug.js (line 75) Missing destruct definition for '_scroller' in qx.ui.table.pane.FocusIndicator[1111]: [object qx.ui.table.pane.Scroller]Log.js (line 557) Missing destruct definition for '_lastMouseDownCell' in qx.ui.table.pane.Scroller[1083]: [object Object]Log.js (line 557) 036394 DEBUG: testgui.Form[3306]: Disposing: [object testgui.Form]FireBug.js (line 75) Missing destruct definition for '_dateFormat' in qx.ui.component.DateChooserButton[3579]: [object qx.util.format.DateFormat]Log.js (line 557) Missing destruct definition for '_dateFormat' in qx.ui.component.DateChooserButton[3666]: [object qx.util.format.DateFormat]Log.js (line 557)
The nice thing here is that the log messages already indicate which dispose method to use: Every “Missing destruct...” line contains a hint to the type of member that is not being disposed properly, in the “[object ...]” part of the line. As a rule of thumb
- for native Javascript types (Number, String, Object, ...) use
_disposeFields - for qooxdoo objects (e.g. qx.util.format.DateFormat, testgui.Report, ...) use
_disposeObjectsor_disposeObjectDeep
Using a harness to check for leaks
Another way of testing destruction is to construct and destruct a widget a number of times and observe objects which are leaked. For example, the harness shown below will create 5 instances of widgets.widget1, waits 20 seconds and then destructs them again. Object counts are shown before and after the test.
(The timer is useful if your widget does asynchronous fetches of data – if it doesn’t then you won’t need to be quite so sophisticated)
var container1 = new qx.ui.layout.VerticalBoxLayout; container1.addToDocument (); alert(qx.dev.ObjectSummary.getInfo()); var container2 = new qx.ui.layout.VerticalBoxLayout; container1.add (container2); for (var i = 0 ; i < 5 ; i++) { var widget = new widgets.widget1 (); container2.add (widget); } qx.client.Timer.once (function () { container1.remove (container2); container2.dispose (); alert(qx.dev.ObjectSummary.getInfo()); }, this, 20000);
When you’ve seen how many Objects have leaked, try to put into perspective how many will build up over the life of a running application. So, for instance, if you have a simple application which creates all the widgets up front, runs for a while and then lets the user quit, then the cleanup isn’t too important.
If you have an application which remains open for a few hours and creates and destructs a screen of widgets every 10 minutes or so, then if you leak a few dozen objects each time, then the user’s PC may start to creak as the day goes on.
It’s also possible to quickly consume a PC’s memory by creating big trees or tables which you don’t clean up properly, or fast moving animations where you leak a few objects every update.
Being able to run the ObjectSummary shown above from a running application periodically whill allow you to guage if you have a problem on your hands.
As a slight digression, if all the widgets that you create are attached as children to other widgets, disposing a parent widget will clean its children. Certain widget types do not sit as parent/children so you may need to take extra care with them. Some examples are:
- rpc widgets
- managers
- tooltips
- (other contributions gladly accepted)
There are a few ways to deal with these, but one way that works well is to accumulate them in a private array, and then in the destructor dispose the array with _disposeObjectDeep.
Disposing a application
You can dispose any qooxdoo based application by simply calling qx.core.Object.dispose(). The simplest possibility is to use the command line included in Firebug. Another possibility is to add a HTML link or a button to your application which executes this command.
