Cloud9IDE’s CPU usage

Cloud9 IDE is probably a nice web app, I haven't been able to use it yet since it doesn't load in Opera. While trying to show the "dashboard" it hangs while using 100% of the CPU. I'm still working on figuring out all the reasons why it hangs, here's just the first finding:

The high CPU usage is because they use a postMessage() based hack to avoid the default 4-10ms delay for setTimeout(). Have a look at this code (linewrapped by me):

apf.setZeroTimeout=(apf.isIE&&apf.isIE<9)||apf.isO3||apf.isAIR?setTimeout:(function(){
	var timeouts=[];
	var messageName="zero-timeout-message";
	function setZeroTimeout(fn){
		timeouts.push(fn);
		window.postMessage(messageName,"*");
	}
	function handleMessage(e){
		if(!e){
			e=event;
		}
		if(e.source==window&&e.data==messageName){
			apf.stopPropagation(e);
			if(timeouts.length>0){
				var fn=timeouts.shift();
				fn();
			}
		}
	}
	apf.addListener(window,"message",handleMessage,true);
return setZeroTimeout;
}
)();

Whoever came up with this hack knew about setTimeout()'s minimum delay, and realised that a message event thread as opposed to a setTimeout() thread will run immediately after the script calling postMessage(). So, rather than calling setTimeout() they add the function to run to a queue and call window.postMessage('zero-timeout-message', '*'). The message thread picks up the queue and runs any pending functions.

There are probably interesting uses for this, if you're into performance optimisation. (Aside: is this tested in browsers without postMessage() support? The browser sniffing isn't exactly good-looking, and setTimeout() would likely throw a "wrong this object" error when setZeroTimeout() is called in browsers that do not run the fancy postMessage() code). Interesting use cases are things like animations and games that require a high frame rate. However, while the site is loading, the method(s) they are queuing will simply check if XHR requests are finished, and immediately queue the same check again if they are not..

Bypassing the browser's setTimeout() throttling and making the CPU spin at 100% in order to check that 4 files are done loading, may not be the best performance tradeoff after all. So basically, the fancy hack is used to keep the CPU usage high while waiting for XHR requests to finish ;).

Advertisements

15 thoughts on “Cloud9IDE’s CPU usage

  1. David Baron wrote about this awhile ago, so they probably found out about the hack directly or indirectly through that post. It's the sort of thing that seems usable for good, but the potential to use it for "bad" was enough that I personally wasn't going to broadcast the trick's existence for any random developer to pick up, with some fraction inevitably misusing it.

  2. Henri Sivonen writes:How sad to make this trigger on browser sniffing instead of window.postMessage sniffing.

  3. Originally posted by hallvors:

    setTimeout() would likely throw a "wrong this object" error when setZeroTimeout() is called in browsers that do not run the fancy postMessage() code)

    setTimeout will default to executing in the global scope… where it always executes. No problem really.Almost related: Here's an oddity with Opera's postMessage implementation not really being async:http://dl.dropbox.com/u/2400/tc/postmessage-async.htmNot sure if I filed this one. I couldn't find a spec to support or disprove my expectations.

  4. Originally posted by fearphage:

    setTimeout will default to executing in the global scope… where it always executes. No problem really.

    Not quite sure if I follow you. If I do

    var x={};
    x.sTo=window.setTimeout;
    x.sTo(function(){}, 10);
    

    The (few) browsers I tested do indeed throw an error about an unexpected this object for setTimeout(). I don't think the Cloud9 code as-is will work in older browsers.

  5. The deeper ramifications of some hacks are still just "black magic" to me, so I tend to avoid experimentation…Of course if more browsers >.> had proper support for workers, this probably could have been avoided entirely? Or is there a worker context switch or something else that again slows it all down?

  6. >I haven't tested much but I have run into a case where Opera seems to run postMessage'ed async code parallel with normal code?I was wrong. That wasn't the case as long as I tested.

  7. Opera executes postMessage super fast. So fast that it doesn't redraw screen for about 10-50 postMessage async iterations. I wrote a blog post in Japanese, but the table is language-free.http://javascript.g.hatena.ne.jp/edvakf/20100227/1267246371I haven't tested much but I have run into a case where Opera seems to run postMessage'ed async code parallel with normal code?For animation purpose, I would recommend using img.onerror hack for standard browsers and script.onreadystatechange for IE. Of course, there is an option to use mozRequestAnimationFrame and webkitRequestAnimationFrame too (vendor specific, but better than hacks I guess).

  8. Originally posted by hallvors:

    The (few) browsers I tested do indeed throw an error about an unexpected this object for setTimeout().

    Whoops, i didn't see the namespacing. I thought they were just essentially renaming it. You're 100% correct.

  9. Hi Hallvord,First of all: thanks for pointing us to this Opera issue! :)We made a few changes to the code that might fix it for you, but at least gets rid of the ugly browser sniffing: https://github.com/ajaxorg/apf/commit/eb9d01fa51c9c14ca61e5ce378de814441a57507This is not in production just yet, but will be very soon.Our setZeroTimeout function has some definite use cases, otherwise it wouldn't be in the APF codebase. However, it's mainly used inside the core of the framework and is not advocated to be used outside of it by developers who use APF to create applications, like Cloud9. It is best practice to use feature detection over browser sniffing and over the years (the project is already over 8 years old) we were able to get rid of most of the browser sniffing code. But not everywhere, as this example illustrates.At the moment we do not officially support Opera and Internet Explorer 9, unfortunately. We would, however, like to fix this issue, if possible with your help. First of all I'm having difficulties reproducing this issue on the latest Opera version… how are you able to reproduce this issue?Thank you for the time you are spending on this, I'm an absolute fan of Opera!Cheers, on behalf of the Cloud9 team,Mike de BoerLead Developer – Ajax.org

  10. Hi Mike, thanks for removing that browser sniffing :)I've spent some time looking at Cloud9 issues recently, so I can give you some hints on where to fix things. The big problem is that you rely on CSS3 FlexBox which Opera doesn't support yet. However, there is some code (guarded by some more browser sniffing ;)) that indicates you've tried to or planned to write a non-FlexBox mode. Unfortunately this alternative code path causes exceptions because it calls some functions that aren't defined.To reproduce I simply try logging in to Cloud9.com in Opera 11.10ish (I use random internal builds). Usually I get into a state where the site hangs and Opera uses 100% CPU – the hang is caused by some XHR-never-finishes bug, the CPU usage by your postMessage(). When I sometimes get out of this state, I get an exception: "Uncaught exception: TypeError: 'apf.n' is not a function" and we go down that path because this:

    this.hasFlexibleBox=apf.versionGecko>2||apf.webkitRev>2;

    means hasFlexibleBox is false.

  11. I have a hard time believing that setZeroTimeout has a bona fide use case, per se.

    I suspect that you're using this for a "do this when X is done". That approach — if that is in fact what you are doing — relies on unspecified behavior. Such approaches are doomed to fail. They'll make it seem like keeping up with browser updates is hard, when it's actually easy.

    Thinger.blang = function() {
      _blang()
      Thinger.blangDone();
    };
    

    Not officially supporting Opera and Internet Explorer 9 may be an indicator that the cloud9 application is badly designed, as those are two very good browsers. Especially IE9. All of the scripts aimed at addressing specific browser sets failed when IE9 came out while scripts that used proper capability checks and fallbacks fared much better, having little to no needed changes, even as different paths were used in an IE that passed tests like `if(document.addEventListener)`.

    And your github patch exacerbates the very problem Hallvord described in this very post! Look here, from your fresh github patch:

    +apf.setZeroTimeout = !window.postMessage
    
       ? setTimeout
    

    That says that if window.postMessage is falsey, then apf.setZeroTimeout is setTimeout, and so when you call apf.setZeroTimeout in one of those environments then setTimeout is called with the base object being `apf` which will result in the errors that have been carefully described in this blog.

    Now what I'd rather look at is not how to fix this function, but why you're using it in the first place. If you're using it to set up a queue call, as Hallvord said you were, then that's easy to fix.

    Instead, use "I'm done" type of notifications. The object performing the action notifies when it is don. This obviates the need for the bad solution. Whatever situation or problem that your setZeroTimeout approach was used.

    I'm available for such consultations, BTW. I don't work full time but I save a lot of time.

  12. What is the progress on making cloud9ide work in Opera? I really like it, but I'm not going to use Chrome just because of this site 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s