Menu
Aug 25, 2011

Finding and fixing memory leaks in a SmartClient based application

Finding memory leaks in a Web Application can be difficult, fortunately the tools for Web Development are getting better and better. You can read the post Finding memory leaks by Tony Gentilcore, where he explains how you can use Chrome Dev Tools Timeline and Heap Profiler to diagnose if your application is leaking memory.

In the case of a SmartClient application, you should know that when you create a new object, instance of a Class, the library pollutes the global namespace (window) with a global variable using the pattern: isc_TypeOfClass_Index e.g. If you execute the following code, you’ll end up with a new global variable isc_VLayout_0

var l = isc.VLayout.create({width: '100%', height: '100%'});

When you destroy an object, the global variable gets nullified:

l.destroy();
isc_VLayout_0 === null // true - still exists but its value is null

Taking this as a base, you could identify if your SmartClient application is leaking memory with the following procedure:

  • Take a snapshot of the current state of the global namespace
  • Perform the action you think causes the leak
  • Take a second snapshot of the global namespace and compare them
  • Every not null entry in the second snapshot that is not present in the first one, is a leak

Keep this strategy in mind.

Although most of the cases SmartClient handles objects lifecycle automatically, there are cases in which you need to destroy objects manually. Let’s take a look when this is required.

SmartClient handles child objects automatically

If you check the documentation, SmartClient has the concept of AutoChild that are subcomponents part of of a main object that gets automatically managed.

An example is the Window component and its subcomponent the “header” …
By default any auto-children created by canvas.addAutoChild or
canvas.createAutoChild will be destroyed when the canvas that created them is destroyed

Your child objects will be automatically destroyed if SmartClient knows about them, e.g. Using

Some code:

var l = isc.Window.create({
  width: '600',
  height: '400',
  items: [
    isc.Label.create({
      contents: 'Hello World'
    })
  ]
});

When you execute this code, you’ll have a lot of new global variables:

isc_Label_0
isc_Window_0
isc_EdgedCanvas_0
isc_Window_0_shadow
isc_Window_0_header
isc_Window_0_headerBackground
isc_Window_0_headerIcon
isc_Canvas_0
isc_Window_0_headerLabel
isc_Window_0_minimizeButton
isc_Window_0_closeButton
isc_Window_0_body

If you call the destroy method of the Window, the isc_Window_0 variable remains in the global namespace but with null value. The rest of variables are deleted.

isc_Window_0 === null // true

SmartClient doens’t handle objects that are not AutoChild

When dealing with complex composed widgets and if those subcomponets are not added using the AutoChild pattern, is quite easy to create memory leaks. An Openbravo example of this is the SelectorItem, a widget commonly used (Business Partner, Product selectors). This widget is a composite of a ComboBoxItem, a magnifier image and a ListGrid embeded in a Window that, is hidden by default, and shown when clicking the magnifier icon.

When destroying a SelectorItem you need to manually take care of destroying the associated objects.

SmartClient doesn’t destroy the dataSource object associated to a DataBound component

Another case is when SmartClient doesn’t destroy the associated dataSource object of a ListGrid.
This case is also present in the SelectorItem, since the selector Window contains a ListGrid with an associated DataSource.

The example below creates a new DataSource and a ListGrid bounded to it. The ListGrid is then added as item of a Window which is a member of a Layout that also contains a destroy Button. When the user clicks the button, the expected result is that all objects get destroyed in cascade.

As you can see the dataSource requires to get destroyed explicitly.

isc.DataSource.create({
  ID: "countryDS", // manually defining the global ID
  fields:[
    {name:"countryCode", title:"Code"},
    {name:"countryName", title:"Country"},
    {name:"capital", title:"Capital"}
  ],
  clientOnly: true,
  testData: countryData // sample data previously defined
});

isc.ListGrid.create({
  ID: "countryList",
  width: '100%', 
  height: '100%', 
  alternateRecordStyles:true, 
  showAllRecords:true,
  dataSource: countryDS,
  autoFetchData: true,
  destroy: function () { // 'overriding' destroy method
    if(this.dataSource) {
      this.dataSource.destroy(); // needs to be done manually
  }
  this.Super('destroy', arguments);
  }
});

isc.VLayout.create({
  height: 400, width: 600
})
.addMember(isc.Window.create({
  width: '100%',
  heigth: '100%',
  items: [countryList] // ListGrid gets destroyed
}))
.addMember(isc.Button.create({
  title: 'destroy',
  action: function () {
    this.parentElement.destroy();
}
}));

The Openbravo Case

In Openbravo 3 there were few places with some composed components, that child/related objects were not destroyed when destroying the main component.

Previous to the upcoming 3.0MP3 if you close a window (e.g. Sales Order), some objects were not getting destroyed, resulting in memory consumption increase. After working with the application a couple of hours (opening and closing several windows) the user got strange behavior like: slow reponse to user actions, slow repaint process, etc.

Some of the components that were not properly managed are:

  • LinkedItems section (section in Form view that shows you the related records)
  • SelectorItem (the component behind Product, Business Partner selector)
  • StatusBar (the component in the upper part of the Form view)
  • Loading Tab (temporary tab shown when requesting a View definition to the server)

Linked Items

Linked Items

The Linked Items section contains two ListGrids and a DataSource each one of them. When destroying the Form, you need to manually destroy the ListGrid and DataSource associated.

Selector

Selector

As explained before, the Selector is one of the most commonly used widget. When destroying the Form, you need to manually destroy the Window that contains a ListGrid and the associated DataSource.

Status Bar

Status Bar

The StatusBar contained some images not added using the AutoChild pattern and required to be manually destroyed when destroying the StatusBar.

Loading Tab

Loading Bar

When opening a window, a Loading Tab is shown in the TabSet. After the window gets created the Tab content is replaced using TabSet.updateTab. The documentation clearly states, “NOTE: the old pane for the tab is not destroyed”, you need to manually destroy the Loading Tab.

The Fix

Using scopeleaks

If we use the strategy described at the begining, we need to take a snapshot of the global namespace, perform the leaky action (open/close a window), take a second snapshot and compare.

We have used and modified scopeleaks, a utility tool created by Rui Lopes. The tool is intended for detecting leaks of variables to the global namespace. Since we now that SmartClient always leaks variables, we have modified it to only check for instances of a class, meaning any new global variable starting with isc_ that is not null.

Detecting leaks

The steps to detect a leak:

  • Login into the application

  • Open Web Dev Tools

  • Take a first snapshot of the global namespace

    var s1 = scopeleaks.snapshot();
  • Open and close a window (e.g. Sales Order)

  • Take a second snapshot and compare

    var leaks = scopeleaks.leaks(s1); // leaks will have an array with all the global variables that are not null

Repeating this flow, you could detect if a user interaction, creates new objects that are never released.

If you are interested in checking all the changes made on the components mentioned before, check the Issue 18227.

Conclusions

Solving simple memory leaks in a SmartClient based application is straight forward if you use this strategy. Note that we are only fixing the most obvious ones.

SmartClient is a great library, but even SmartClient code could cause memory leaks.

You need to learn and know the framework you’re using. You cannot go blindly creating objects here and there and expecting that everything will automatically collected when is not needed.

Use all tools available and measure memory consumption when using the application.

Categories: Other | Leave a comment

Leave a Comment

(required)

 

Legal Note: Your comment is subject to our privacy policy and website terms.

© 2008-2015 Openbravo, S.L.U. All rights reserved | Privacy Policy | Website Terms