.

Tuesday, April 25, 2006

be careful to free leak when changing innerHTML of an element #

Douglas Crockford points out that if you assign a closure to the event handler of an element and then remove this element from the DOM tree, IE's garbage collector cannot deallocate memory properly and hence a memory leak occurs.

Well, it is not a recent issue. And I have mentioned it before both in here and in my codeproject article as well.

Though things may not be always obvious:

Let us say you have filled a div with fifty links using javascript/ajax. And when you click "next page" link, you change the innerHTML of the div and fill it with a brand new set of links.

Moreover, let us assume that after you set the innerHTML (well I'm not discussing whether innerHTML is good or evil, but it generally makes lazy peoples' life easier) you attach every single link in this div an onclick event handler like this:

for(i=0;i<len;i++)
{
links[i].onclick=function(){...dummy stuff...};
}

That is, you define a closure for each link. Which generates a circular reference.
And then when you click the "next page" link and re-fill the innerHTML, those closures will no be reclaimed.

Welcome to our good old "circular reference causing memory leak in IE" story.

To sort this out you can break the circular chain one by one:


function nextbtn_click(evt)
{
/*good boys clean up their mess first*/
for(i=0;i<len;i++)
{
links[i].onclick=null;
}
/*
*now we can set the innerHTML
*since there are no leaking elements around
*/
document.getElementById("targetLayer").innerHTML=
newSetOfLinksThatPopOutOfAnAJAXResponse;
}

or use a custom sweeper function as Douglas offers:

/*
* Code directly taken from Douglas' web site:
* http://www.crockford.com/javascript/memory/queuetest3.html
*/
function purge(d) {
var a = d.attributes, i, l, n;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
n = a[i].name;
if (typeof d[n] === 'function') {
d[n] = null;
}
}
}
a = d.childNodes;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
purge(d.childNodes[i]);
}
}
}


Cheers.

 bu yaziyi sevdin mi?  hemen una ekle!
 

Monday, April 24, 2006

another way to prevent memory leak in IE when using XMLHTTPRequest #

This post is a follow up of my former leak-free XMLHttpRequest Wrapper post. So if you have not gone through it, read it to get an initial idea.

By the way, I'm improving XHRequest of sardalya (and many other objects), a brand new release will come before this weekend.

Anyway, I was seriously thinking that somehting was wrong about the XHRequest._cleanup method:

_this._cleanup=function()
{
if(!this._xhr)return;
delete this._xhr.onreadystatechange;
this._xhr=null;
};


So each time I use a new get request, I was re-creating a new XMLHttpRequest member for (this._xhr), since I was setting the object to null in the last line.

Removing the last line would theoretically be sufficient. But I was secretly wanting to set this._xhr.onreadystatechange=null;

_this._cleanup=function()
{
/*if you try to set the event handler to null to break the circular reference.
This will not work in IE, and it will throw a type mistmatch exception.*/

this._xhr.onreadystatechange=null;
};
which I could not. Since IE was generating a type mistmatch error if I try.

I'll come to this point later. But just before that let me introduce a small test script explaining closures and scopes:

/body\
/script\
var Constant={
NULL:function()
{
/*
* This method does not close over testMember.
* testMember is out of scope.
* This will just alert "undefined".
*/
alert(typeof testMember);
}
}
var TestObject=function()
{
this.initialize();
};

TestObject.prototype.initialize=function()
{
var testMember=document.getElementById("TestLayer");
var self=this;

if(arguments.length)
{
testMember.onclick=Constant.NULL;
}
else
{
testMember.onclick=function()
{
/*
* Here, this anonymous function closes over testMember;
* testMember is within the scope of it.

* this will alert [Object].
*/
alert(typeof testMember);
self.initialize(true);
};
}
};

window.onload=function(evt)
{
var to=new TestObject();
};
//script\
/div id="TestLayer"\Test Layer//div\
//body\

When you run the script on body load, TestObject.initialize() method is called for the first time. So when you click on TestLayer it will alert [Object] and then assings a new function to the onclick. Therefore when you click TestLayer for the second time it will alert undefined.

Do you see where I am getting?

var Constant=
{
...,
NULL:function{}
};

...

_this._cleanup=function()
{
this._xhr.onreadystatechange=Constant.NULL;
};

Just one line of code!

This syntax completely removes the closure, whereas it does not destroy this._xhr, so you do not have the memory overhead of recrating a new XMLHttpRequest object (since this._xhr is not set to null, we just need re-assign this._xhr.onreadystatechange).

It is better than the first one. It is also cooler, I guess ;)

Describing it with a code sample would be more clear I guess:

...
_this.getObject=function()
{
if(this._xhr)return this._xhr;
else if(window.XMLHttpRequest)return new XMLHttpRequest();
else if(window.ActiveXObject)return new ActiveXObject("Microsoft.XMLHTTP");
else return null;
};

In the former version of the script, we were always creating a new XMLHttpRequest object because we were assigning this._xhr=null; after each cleanup. Now, we do not set it to null. So the code-flow will enter the highlihted part only once (in object's creation). Only the first line will execute thereafter, which is better in terms of performance (creating a new XMLHttpRequest at each HTTP GET request to server will be a costly operation, especially if you are creating and AJAX-heavy application and you are using many many of those objects around).

NILL is an object

Secondly, this approach makes NULL an object!. I mean NULL is now an object, just as nill is an object in Ruby.

Most of you have seen "null is null or not an object" error message at least once in your life time ;)

null is not an object but NULL is. So we may even extend it a bit:

Constant.NULL._isNull=true;
Constant.NULL.prototype.toString=function(){return "NULL"};
Constant.NULL.prototype.equals=function(someObject)
{
//return someObject===null||(someObject&&someObject._isNull);
return (someObject&&someObject._isNull);
}
Well it makes things overcomplicated I know (why not use myvar===null instead of Constant.NULL.equals(myVar) -- it's impractical).

I'm just thinking aloud though.

But I'm sure ruby guys (and gals) around will like this approach.

Cheers.

 bu yaziyi sevdin mi?  hemen una ekle!
 

Monday, April 10, 2006

how to run linux without risking your hard drive #

It's my first serious attempt to dive into the *nix world and hence I wanted to make the transition as painless as possible.

Browsing the web I found that the easiest and harmless combination would be using a virtual machine. This way, I would not be creating a physical linux partition. Hence less risk on my side.

Of course it is not as good as running a live linux, but if run in full-screen mode there's no way to differentiate the virtual from the real one.

... somehting like jumping into the matrix :) Work for an hour on your guest linux OS and I bet you'll forget that you're running on a windows host OS.

If you bear with me and follow the steps I do, you'll understand me better.

1. Get VMWare Player

The first thing is setting up a virtual machine. You can get the free VMWare player
(or purchase the VMWare server, however the free viewer is adequate for our purposes)

2. Get a virtual machine

I strongly suggest you get the virtual machine from a torrent. Torrents are way too faster than FTP downloads and if your connection is good enough you can get your linux virtual machine in less than a day.

You may use this torrent (there are more on the site as well) .
Of course, you need to set up something that understands torrents.
Here is the best torrent manager I've ever seen.

As an alternative, you can download the actual installation DVD image. However this time you will need VMWare Server (which is not free) to run your linux.

3. Run your virtual machine and set it up

That's the easy part. After the torrent downloads, you unzip the contents of it and open it with VMWare Player and Voila your linux is ready in minutes!

Here is a screenshot after changing bits and pieces of it.



I was even able to connect to the internet. I installed postgreSQL and a graphical client for it. So far so good.
However I could not manage to increase the scren resolution larger than 800*600 px.

So I thought some way around. If I could run VNC server on my virtual machine then I could connect to it with as high resolution as I wish.

Though if you're working with linux (and unix for that matter) setting up things most of the time requires editing tons of config data. And my case was not an exception.

Here is how I managed to run VNC Server on my Fedora core 5 linux and connect to it from my windows Host OS.

4. Install VNC Viever

Download and install VNC viewer for your windows OS If you have not done already.

5. Set up guest OS for VNC connection

Well, that's a bit tricky since you will need to edit certain files.

First make sure you have VNC Server installed

# rpm -q vnc vnc-server
vnc package not installed
vnc-server-4.1.1-36


That means, I don't have VNC Viewer installed. But I have a VNC server.

Well, having a server is good enough for me. Since I will be using the viewer on windows to connect to my linux.

Since we will be editing some config data, it would be good to stop the VNC server if its already running.

#sudo /sbin/service vncserver stop


Now let us edit /etc/sysconfig/vncservers

But before that, make sure your file browser is set up to view hidden files.
This can be done through computer - file system - view - show hidden files from the GUI.

Going back to vncservers:

...snipped...
#User "-localhost" to prevent remote VNC clients...
...snipped...

VNCSERVERS="1:root"
# VNCSERVERARGS[1]="-geometry ... snipped ...
VNCSERVERARGS[1]="-geometry 1280x1024


The highlighted parts were commented out in the original file.
I removed no-listen and no-httpd from the arguments to be able to log in outside the local network from my windows OS.

But that was not enough. I needed to open etc/sysconfig/iptables as well to add the following line:

...snipped...

-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp
--dport 80 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp
--dport 5901 -j ACCEPT

...snipped...


Tbe highlighted rule is newly added by me. That line simply opens port 5901 to the outside world so that the server may accept VNC requests coming from outside.

And one final thing was to allow the following lines in ~/.vnc

unset SESSION_MANAGER
exec /etc/X11/xinit/xinitrc


Now let us set a password:

#vncpasswd


And after doing all these it will be good to restart the VNC service and network configuration:

#sudo /sbin/service iptables restart
#sudo /sbin/service vncserver stop
#sudo /sbin/service vncserver start


And here we go!

All we need to do is open VNC Viewer from our windows OS and log in
giving the virtual server's IP and port number
(it is 10.0.0.6:5901 for my setup)

Enjoy your Fedora with full resolution.

... and now its time to roll some RoR in Fedora.

Cheers!

 bu yaziyi sevdin mi?  hemen una ekle!
 

Monday, April 03, 2006

I will be naked for one full day. #

Not kidding!

I will be celebrating the first annual webmasters naked day.

All my sites will be without css for one day.

Since I have several blogs, it will be hard to automate things with a server-side function.

So, I will

  • first move all inline CSS in the blogger templates to somewhere on my web server.
  • then rename the folder via ftp at April 4th 23:59:59 GMT+2
  • at April 5th 23:59:59 GMT+2 I will re-name the folder back to its original state.
Cheers.

 bu yaziyi sevdin mi?  hemen una ekle!
 



Recent Posts

RSS

RSS register icon

Other Blogs

Archive

Various

Sponsor

Profile Information

Browser I Suggest

Sponsor

Dikkatimi Çekenler