Showing posts with label Web Development. Show all posts
Showing posts with label Web Development. Show all posts

Monday, July 26, 2010

Debugging JavaScript memory leak in IE

I was recently working on one of the most difficult bugs in all of my years of programming. It involved a memory leak in IE8 caused by a couple of ASP.NET pages on a user's machine.

The first indication of the problem was the iexplore.exe process in the Windows Task Manager. Intermittently watching the 'Mem Usage' column on the 'Processes' tab, the memory usage reached over 2 gigabytes thereby significantly slowly down the user's machine. The rate of increase was about 9 MB every 30 seconds.

As with most web app bugs in IE, I checked to see if the problem was reproducible in Firefox. No leak. Next, I used two tools to help further identify the leaks: JavaScript Memory Leak Detector (IEJSLeaksDetector) and sIEve. Surprisingly, neither one indicated any memory leaks. This is when I knew this was not going to be easy troubleshooting much less fixing.

I tried running the app under IE7 compatibility view mode in IE8 but again no leak. Being skeptical of the reliability of this mode, I decided to try a true install of IE7 itself which required removing IE8 from the user's machine. Alas no leak. I also attempted from a different machine to connect to the web server running on that same user's machine to view the app (yes, one of the web app's features is to run as a local instance). Again no leak.

The problem web pages perform Ajax callbacks. Looking at the code, they were set to trigger the calls every 30 seconds. This was consistent with what was observed earlier with the rate of increase of memory usage. Naively, I thought could the ASP.NET Ajax Timer Control be broken?

I kept suspecting Microsoft's Ajax Timer Control which was helping to display a "Processing..." progress image to the user every 30 seconds. My ill-conceived rationale was that perhaps the code was using an old version of the Timer Control since the app was running on ASP.NET 2.0 and the code was originally written around the time when the ASP.NET AJAX framework was originally released.

Unfortunately, I temporarily fell under the spell of believing "Select is broken". This disillusion became painfully clear when I later confirmed that the specific Ajax framework was indeed the latest ASP.NET 3.5 web extensions for 2.0. Just to be sure, I created a standalone dummy web app with the same library that provides the Timer Control. Once again, as has been the pattern thus far: no memory leak.

More detailed information was needed. On the suggestion from another developer on the project, configured Task Manager with:
'Processes' tab -> 'View' -> 'Select Columns'
and added the following columns:
'Handles'
    'Threads'
    'USER Objects'
    'GDI Objects'  
Again, closely monitoring any changes, I noted that the GDI objects counts were increasing by 7~8 units every 30 seconds meaning this leak must somehow be graphics display related. Perhaps some image (or images) was not being removed or repeatedly added on each Ajax server request? (Possibly the aforementioned progress image?)

To further investigate the GDI objects findings, I used a few tools. Neither GDIUsage nor GDIObj provided any useful metrics (It's possible I might not have fully understood how to best use these.) However, GDIView allowed me to do snapshots of the memory usages of the GDI objects.

I manually copied the initial output displayed in GDIView and pasted that data into a text file. I subsequently waited until the data refreshed 30 seconds later and then copied that output into a separate text file. Finally, I performed a diff on the two files and it reconfirmed what was shown in Task Manager. The following text was the diff results:
Handle     Object  Kernel
  Type    Address  Extended Information
---------------------------------------------------------------  
0x86040059 Region 0xe2e68008 
0x3a0508e1 Bitmap 0xe45af558 Width: 1042, Height: 586 , Bits/Pixel: 32
0x29040972 Region 0xe55938d8 
0x3b040bb4 Region 0xe42f9868 
0x9a040bd1 Region 0xe1cd5738 
0xb3010bec DC 0xe3f9e9c8 
0x17300c6a Pen 0xe40dce88 Color: 0x02b0b0b0, Width: 0, Style: 0x00000000
0xe2050fb3 Bitmap 0xe2268840 Width: 1046, Height: 150 , Bits/Pixel: 16
0x133011fb Pen 0xe565c868 Color: 0x02b0b0b0, Width: 0, Style: 0x00000000
0x7004123c Region 0xe5685c18 
0x4e0415eb Region 0xe3031688 
0xc504172b Region 0xe183cdb8 
0x94041733 Region 0xe362f2b8 
0xae05180e Bitmap 0xe2c44840 Width: 1046, Height: 607 , Bits/Pixel: 16
0xfe01198b DC 0xe4c22008 
0x60011b7d DC 0xe28af008 

Clearly some bitmap was indeed being added repeatedly but the questions now were which one and why? The other developer helping out noted that the width of 1042 and height of 150 match the size of the web app within the browser. An important detail.

Recalling the purpose of the Ajax timer control was to display on both web pages a processing progress message and image (specifically a gif file), I decided to delve deeper into that area of the code. A 'progress' element containing the gif image is shown to the user via JavaScript. In that same JS function, an IFrame element is created and added to the 'Content' property of the 'progress' element. I noticed that the width and height of this IFrame was set to the document's height and width:
var progressIFrame = document.createElement("IFRAME");
    // ...
    progressIFrame.style.width = document.body.clientWidth;
    progressIFrame.style.height = document.body.clientHeight;
This was consistent with the results from the previously listed diff of the memory usage since the bitmap's dimensions were about the same size as the browser's viewport. Then, the very next line in the function struck me as strange:
progress.Content = progress.parentNode.insertBefore(progressIFrame, progress);
The return value of 'insertBefore' is a reference to the 'progressIFrame' element which it is then set to the 'progress' element's 'Content' property. Not only does it do this but it also adds it as a node sibling to the very same 'progress' element.

Why add the IFrame element to the DOM in two places? It was far from clear what the intent was for using the 'insertBefore' function if the IFrame element was already being added as part of the progress element itself?

Therefore, I changed it to directly set it and not bother with 'insertBefore':
progress.Content = progressIFrame;
and that stopped the memory leak!

Now, admittedly, I am not comfortably well-versed in JavaScript and this is one of those cases that I do not fully understand why the IE browser behaved the way it did in reaction to how the JavaScript is written.

Sometimes this is the outcome of debugging code (particularly when written by someone else). Although you come away learning some things you did not know before, you also might not know how or why something was broken to begin with or why the fix itself even worked.

Like writing code, debugging code is both a skill and art. However, it is certainly a different mindset.

Monday, May 31, 2010

Cross-browser compatibility

My first true deep dive into heavy JavaScript programming was working on a project that required adding Firefox support for a web application that only targeted Internet Explorer. The web application was originally written for IE6 and therefore contained some hairy JavaScript.

I experienced firsthand the same rite of passage as millions of web developers around the world. Fortunately, adding support for Firefox also meant that the web app no longer needed to support IE6 (only IE7+) so fixing the JavaScript to be cross-browser compatible became a bit more reasonable and sane.

Now, some snippets collected for these changes in no particular order:

# 1

Replacing all references to the DOM Level 1 function
top.frames['myId']
, which gave Firefox much trouble, with the DOM Level 2 function:
document.getByElementId('myId')


# 2

Replace the function 'removeNode':
if (x)
{
    x.removeNode();
}
with a conditional check that uses the Firefox friendly function 'removeChild' if parentNode is defined (otherwise fallback on 'removeNode'):
if (x)
{                  
    if (x.parentNode) 
        x.parentNode.removeChild(x);  // Firefox                  
    else
        x.removeNode(); // IE
}

# 3

The 'innerText' property does not work in Firefox but it does in IE. Instead, Firefox does recognize 'textContent' serving the same purpose. Again, use another if-else statement checking if the target element exists:
function setText(elem, textValue)
{
    if (elem.textContent || elem.textContent == "")
    {
         elem.textContent = textValue; // Firefox
    }
    else 
    {
         elem.innerText = textValue; // IE
    }
}

# 4

Dot notation for defining functions is another area where JavaScript errors emerge in Firefox:

"...missing ( before formal parameters..."
function window.onDoSomething()
{
 // do some stuff
}
To fix is to swap around the 'function' keyword:
window.onDoSomething = function()
{
 // do some stuff
}

# 5

One of the web pages had a character that used the Webdings (TrueType dingbat) font for an expressive, functional symbol. It rendered incorrectly (and confusingly) in Firefox. Substituting the equivalent Unicode character resolved the discrepancy.


# 6

All AJAX calls involved the following IE6 object:
var xmlHttp = new ActiveXObject('Microsoft.XMLHTTP');
As mentioned, with no need for supporting IE6 (something web developers dream of someday being true for all the of internet) every instance of the previous line of code is fully replaced with:
var xmlHttp = new XMLHttpRequest();
Certainly, one of the more satisfying cross-browser changes.


# 7

Firefox does not support referencing global event objects specifically 'window.event' and when encounters JavaScript code that attempts to do so it responds with this error message:

window.event is not defined.

Instead, it is necessary to pass the event object as an argument via a function's parameter:

    function myFunction(e) // <-- add 'e' as a parameter for the global event object
    {
        if(!e) e = window.event // if e is undefined then set e using IE event object
        // other code
    }

    <button onclick="myFunction(event);">test events</button>

# 8

A web page had a file browser functionality to attach a file for upload to the server. The 'onclick' event handler for this element's tag and type
<input type="file" ...
was coded to be programmatically triggered. The reason for this was to allow for the user to type in and edit the free form text of the file path and then the JavaScript would create the 'input' and then fire the event on the user's behalf.

Firefox does not allow this requiring the user to manually click on the tag since the file path text is read-only and can not be edited. It is considered a potential security flaw hence the restriction. The code needed to be rewritten to have the user directly fire the event and open the file browser.


# 9

An html table on a page contained rows with hidden nested rows functioning as a tree-like grid. These top level rows when clicked toggled between the style of
display:none
when hiding its children rows and then used
display:block 
when showing the rows.

In Firefox, the rows do not align properly when made visible with 'block' display style. The premature solution was to replace 'block' with
display:table-row
which worked for both IE8 and Firefox.

However, I later discovered that this type of display was not supported in IE7. Instead, substituted the equivalent of no display style at all using empty string
rowObject.style.display = ''
to show hidden rows. Apparently, each browser knows by default how to appropriately render the rows without the need to be specific in the html.


# 10

Consider a deeply nested event firing and then needing to prevent it from triggering other event handlers further up the DOM hierarchy. In IE,
window.event.cancelbubble
should take care of this. Firefox, of course, does not recognize this command. Instead, one must use the
event.stopPropagation
function to exercise the same control over the scope of an event.

To ensure coverage across the different browsers, do this:
function doSomething(e)
{
 if (!e) var e = window.event;
 e.cancelBubble = true;
 if (e.stopPropagation) e.stopPropagation();
}

# 11

Some JavaScript code was not executing at all. No clear indications why the function was not defined. It turns out that using the term 'jscript' as part of the 'type' attribute in the 'script' tag:
<script language="javascript" type="text/jscript" ...
is , not surprisingly, recognized only in IE and not in Firefox. All instances of 'jscript' were replaced with 'javascript':
<script type="text/javascript" ...
This cross-browser issue caused so much grief for such a simple fix. I spent way too much time figuring it out. When I read:

"...Nevermind, I think I found it. I inherited the code and just noticed that the original programmer had specified JScript rather than Javascript as the script language..."

I glanced over to my aspx page and my eyes immediately saw that exact error. Unbelievable.


# 12

Another function not defined in Firefox but used in IE:
window.attachEvent
In Firefox, use this instead:
window.addEventListener
The cross-browser code might look like this:
eventName = 'load';

if (window.addEventListener) // Firefox
{
  window.addEventListener(eventName, myFunction, false);
} 
else if (window.attachEvent) // IE
{
  window.attachEvent('on' + eventName, myFunction);
}
(Note that IE requires the prefix "on" for the event name while Firefox does not.)

All of the above applies to IE's
window.detachEvent
For Firefox, use
window.removeEventListener


# 13

Some image icons when hovering over with the mouse were expected to show tooltip text. However, no tooltips shown in Firefox with the following:
<input type="image" disabled="disabled" text="Hi there"...
Instead, replaced the 'input' element tag with an 'img' element:
<img text="Hi there" src="disabled.gif"

# 14

An html table needed to be dynamically resized by changing it's style. The original IE-only code:
tableObject.style.left=400
    tableObject.style.top=400
No effect in Firefox (the size remained the same), so needed to explicitly add the unit of measurement "px":
tableObject.style.left=400 + "px"
    tableObject.style.top=400 + "px"
Also applies to 'height' and 'width':
tableObject.style.height="55px"
    tableObject.style.width="33px"


# 15
Dynamically adding some new html into a page relied on 'insertAdjacentHtml':
document.body.insertAdjacentHTML('AfterBegin', '<div>foo</div>')
Worthless in Firefox (at least until HTML5 is supported) so fall back on 'insertBefore':
elementHtml = '<div>foo</div>';

if (document.body.insertAdjacentHTML)
{
     document.body.insertAdjacentHTML('AfterBegin', elementHtml)
}
else
{
    element = document.createElement("div");
    element.innerHTML = elementHtml;
    document.body.parentNode.insertBefore(elem, document.body);    
}
(Orignally, used
document.body.insertBefore(element, document.body.childNodes[0])
but that seemed to cause the event (specifically the 'onload' event of an image) to fire repeatedly in Firefox so changed it to use the one listed above.)


# 16

The mouse's position was necessary to figure out where to render a dynamically injected image. In IE, to determine the X and Y coordinates relative to the web page document:
window.event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
    window.event.clientY + document.body.scrollTop + document.documentElement.scrollTop
Functions 'client<X|Y>' tell you the "viewport" position of the mouse which is a smaller, overlapping portion of the entire document but not a true subset of it. To obtain the document's actual mouse position, you need to add to these position values the scroll values by using the other functions shown above. Specifically,
document.body.scroll<Left|Top>
are the older (i.e. quirksmode) DOM syntax to retrieve the scroll values while
document.documentElement.scroll<Left|Top>
are the more modern standard approach. Depending on the browser only one of these will have the actual value while the other will equal zero. Therefore, it's safer and relatively harmless to include both.

In stark contrast, Firefox simply uses these:
e.pageX
    e.pageY
For a comprehensive code snippet that works across most major modern browsers:
function doSomething(e) {
 var posx = 0;
 var posy = 0;
 if (!e) var e = window.event;
 if (e.pageX || e.pageY)  {
  posx = e.pageX;
  posy = e.pageY;
 }
 else if (e.clientX || e.clientY)  {
  posx = e.clientX + document.body.scrollLeft
   + document.documentElement.scrollLeft;
  posy = e.clientY + document.body.scrollTop
   + document.documentElement.scrollTop;
 }
 // posx and posy contain the mouse position relative to the document
 // Do something with this information
}


# 17

To cancel an event just for the local scope only but not stop the event from bubbling up to the rest of DOM tree, in IE set
e.returnValue
to false.

For Firefox, use:
e.preventDefault
Cross-browser function:
if(e.preventDefault)
  {
    e.preventDefault();    // Firefox
  } 
  else
  {
    e.returnValue = false;    // IE
  }

One last (thought) snippet

While extremely beneficial being exposed to JavaScript's historical client-side scripting messiness in different browsers, next time when faced with cross-browser quirkiness I'd use a library like jQuery for simplified and easier web development.

Friday, January 30, 2009

Paging in ASP.NET using NHiberate

NHibernate, the object-relational mapping (ORM) framework for .NET, supports custom pagination for collections. It provides a potential alternative to the built-in paging mechanism and native support found in ASP.NET GridView web controls. NHibernate exposes in the API for IQuery and ICriteria two methods, SetFirstResult and SetMaxResult, that can be used to enable paging:

Collections are pageable by using the IQuery interface with a filter:

IQuery q = s.CreateFilter( collection, "" ); // the trivial filter
q.setMaxResults(PageSize);
q.setFirstResult(PageSize * pageNumber);
IList page = q.List();

Adding Paging to the Base DAO

The design and architecture of my project, where NHibernate style pagination shall be introduced, was deeply influenced by Billy McCafferty's NHibernate Best Practices with ASP.NET resulting in the proliferation of Data Access Objects (DAO) throughout the guts of the application. Each DAO maps one-to-one to a single matching table in the database.

For example, a 'Task' table would have a corresponding 'TaskDao' in the data access layer (DAL) of the application. All of these DAOs inherit from the same base class 'GenericNHibernateDao' responsible for containing commonly shared code including managing NHibernate sessions and providing generic methods for summoning a specific persisted instance by ID and for saving/deleting an existing instance.

The initial step to implement paging was adding the following members to the aforementioned base class 'GenericNHibernateDao':

public abstract class GenericNHibernateDao : IGenericDao
{
public int PageSize
{
get { return _pageSize; } set { _pageSize = value; }
}

public int PageNumber
{
get { return _pageNumber; } set { _pageNumber = value ; }
}

protected int GetFirstResultPosition()
{
return _pageSize * (_pageNumber - 1);
}

protected void SetPagingFor(IQuery query)
{
query.SetFirstResult(GetFirstResultPosition());
query.SetMaxResults(_pageSize);
}

protected void SetPagingFor(ICriteria criteria)
{
criteria.SetFirstResult(GetFirstResultPosition());
criteria.SetMaxResults(_pageSize);
}

/*
other non-paging related members...
*/
}

The property 'PageSize' gets/sets the number of instances expected to be displayed on a web page for a specific strongly typed collection. Essentially, it handles how many rows to return for a embedded control (such as a GridView) on the page. This property is intended to be internally consumed by NHibernate's 'SetMaxResults' method belonging to IQuery or ICriteria. For example:

IQuery query = Session.CreateQuery();
query.SetMaxResults(_pageSize);

'PageNumber' specifies the set of multiple instances to be returned and displayed on a web page as identified and grouped by a page sequence numeric value (This is the equivalent of 'PageIndex' property for GridViews). For example, if you had a total of five pages worth of data rows then displaying the second page would require setting the page number value to '2' (e.g. _dao.PageNumber = 2). Just as with 'PageSize', this property is intended to be used by the method 'GetFirstResultPosition' as will be explained next.

The protected method 'GetFirstResultPosition' calculates the actual row at which to start paging based on the values provided by 'PageSize' and 'PageNumber'. This method's return value is expected to be passed to NHibernate's 'SetFirstResult' method, once again, part of IQuery or ICriteria. For example:

IQuery query = Session.CreateQuery();
query.SetFirstResult(GetFirstResultPosition());

The overloaded method named 'SetPagingFor' performs the actual paging functionality via the 'SetFirstResult' and 'SetMaxResult' methods of IQuery and ICriteria. (As an aside, the use of IQuery to build and execute HQL is much more common on this project in comparison to the almost non-existent use of ICriteria).

The base class 'GenericNHibernateDao' was also modified to have one of its existing methods 'GetAll' call 'SetPagingFor'. (The 'GetAll' method simply returns a list of all strongly type objects in the database without any specified criteria or filtering):

// GenericNHibernateDao class
public IList<T> GetAll()
{
ICriteria criteria = Session.CreateCriteria(persitentType);
SetPagingFor(criteria); // this is newly added!
return criteria.List();
}

As it shall be made clear later, this new line of code will optionally provide paging functionality when 'GetAll' is requested, if needed, but not required per se.

Applying Paging Functionality in the DAOs

Now that the base class 'GenericNHibernateDao' has been updated to manage paging, any of its derived DAO classes are instantly equipped to perform paging themselves. To actually invoke the paging functionality for any of the DAOs, simply set the appropriate values for page size and page number as shown in this example for the 'TaskDao' class:

// In context (such as a Presenter or Controller class
// part of an MVP/MVC applied framework)
TaskDao _taskDao = new TaskDao();
_taskDao.PageSize = 20;
_taskDao.PageNumber = 5;
IList<TaskDao> list = _taskDao.GetAll();

Without paging, 'GetAll' would have returned something in the neighborhood of 100 or so rows. With paging, the returned list will instead be only 20 rows starting at row (i.e. position) # 80. While a hundred rows might not sound too substantial, larger sets of data can have a more noticeable effect on your application's day-to-day operations if your table contains tens or hundreds of thousands of rows. Your performance will be progressively impacted as your application scales with more data.

On the other hand, NHibernate's paging will significantly decrease the size of your result set. This is in stark contrast to the default behavior of the existing paging available in any GridView control. If this native functionality of the control is used, it will return all rows from the database and then page them in memory. As mentioned earlier, this can lead to slower performance as your data grows. More on this later.

If, for any reason, paging is not essential for a particular web page in possession of a control bound to a strongly typed list (for example,a very small static list of data) then setting the page size and number properties is not required at all. Simply call 'GetAll' by itself disregarding the 'PageSize' and 'PageNumber' properties and the DAO should return all rows found in the associated database table. What makes this possible is that the default values for those two paging properties are formally declared in the DAO base class to behave as expected for "non-pageable" collections:

public abstract class GenericNHibernateDao : IGenericDao
{
protected int _pageSize = -1; // default for unlimited page size
protected int _pageNumber = 1; // default for first item in collection

// more members...
}

Typically, a DAO might have other custom methods that return more narrowly focused (i.e. filtered) lists of typed objects than what 'GetAll' offers. For these other DAO methods, the same pattern can be followed by adding the one line of code calling 'SetPagingFor'. For example, the 'TaskDao' might have a method that returns tasks that were completed in 2006 excluding any other tasks not done within that same year:

// TaskDao class
public IList<TaskDao> GetTasksCompletedIn2006()
{
IQuery query = Session.CreateQuery(
"some HQL statement that filters tasks by 2006...");
SetPagingFor(query); // newly added!
return query.List();
}

It is generally good practice not to pass the values for page size and number directly via the parameter list for any of these custom filtered data access methods. A few reasons to avoid this: (a) it can quickly clutter the intent of the method, and (b) it would prevent the paging functionality from being optional and, as a result, become less flexible, less reusable, and more cumbersome to work with.

Consequently, while it would be tempting to write the method's signature as such:

IList<TaskDao> list = _taskDao.GetAllTasksWithSubtasks(
param1, param2, param3, ..., paramN, pageSize, pageNumber);

It is preferable to do the following instead:

_taskDao.PageSize = 20;
_taskDao.PageNumber = 5;
IList<TaskDao> list = _taskDao.GetAllTasksWithSubtasks(
param1, param2, param3, ..., paramN);

The Downside of NHibernate Paging with ASP.NET Controls

As indicated earlier, one weakness of NHibernate's paging when combined with ASP.NET's GridView control involves some loss of "out-of-the-box" functionality. Under more conventional circumstances, when a collection of objects are bound to a GridView control, one of the built-in paging features of that control is to automatically render on the web page the navigation hyperlinks for the pages. For example, you might see following below your control:

" 1 2 3 ... 10 "

The GridView's default behavior assumes that all data bound directly to its DataSource can be paged as long as the number of items of that data is greater than its PageSize property value. Hence, if that condition is met, the control will slice up and present the data as appropriate.

Conversely, this is not true when using NHibernate style pagination. When binding to an NHibernate paged list method, the GridView's PageSize value will usually be set to the same size as the paged data list, a mere subset of the total data found in the database (or data source). Behind the scenes, the PageIndex property of the GridView will reset to zero because the number of items bound to its DataSource is less than or equal to the PageSize. Therefore, the GridView is under the impression that the data it receives is not pageable and, in turn, disables any paging features, unaware that more items do indeed exist but were just not provided at that moment. The paging features lost include not just immobilizing navigation links but also removing the availability of the event handlers linked with changing the page index.

Without the convenience of auto-generating navigation links, two options emerge that might help to produce the same desired behavior:

  1. Inherit from the GridView control and attempt to confirm whether or not if any paging methods can be overridden some how or in some way
  2. Create a custom, reusable user control to implement the navigation features

Initially, the easier path was taken by developing a very rudimentary and simple implementation of option # 2. This entailed providing in extremely basic custom controls functionality for navigating between pages using homemade "Previous" and "Next" buttons. Currently, it is not implemented as a shareable user control nor is it able to display the pages counts. I intend on exploring option # 1 a bit more in the event that option # 2 evolves into something more elaborate and unwieldy. Until then, it is a work in progress.

A third option does exist involving the possible use of the ObjectDataSource control. However, that strategy can lead down a less than desirable path for the following reasons:

  1. loss of control of how the data access is managed
  2. ease of maintainability diminishes if any widespread changes were to emerge in the future within areas of the application relying on paging
  3. disrupts and conflicts with how the MCP/MVC methodology is currently applied on the project
  4. increased difficulties in writing and running reliable automated unit tests

All things considered, despite some trade offs and a little bit of work, leveraging NHibernate's ability to do paging can be an area that could contribute significantly in optimizing and improving the performance of a data-intensive web application.