Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Andy Weber and Gunnar Lieb

Exploring Race Conditions In Javascript With SetInterval(), SetTimeout(), And AJAX

By Ben Nadel on

In a server-side programming language like ColdFusion, use of parallel constructs like CFThread can easily lead to race conditions. Heck, even something as seemingly atomic as the ++ operator can fall subject to race conditions. But what about the client-side? In Javascript, we have things like setTimeout(), setInterval(), and the XMLHTTPRequest (aka AJAX) object; do these create race conditions? Does the asynchronous nature of their creation negate the blocking of their execution?

To test this, I created a demo page that contained a fixed-position image. Then, I created a function that did nothing more than nudge the image to the right a bunch of times and then nudged it back to the left a bunch of times. With each nudge, the function re-evaluated the position of the image in order to execute the nudge.

Once I had this in place, I then tried to execute this function several hundred times in "parallel" using a combination of setTimeout(), setInterval(), and AJAX success callbacks:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Timers And Race Conditions</title>
  • <script type="text/javascript" src="../jquery-1.4.4.js"></script>
  • </head>
  • <body>
  •  
  • <!--
  • Define our image and set its initial position to be at
  • the left of the page.
  • -->
  • <img
  • src="http://farm5.static.flickr.com/4071/4714584358_16602c47b2_b.jpg"
  • width="300"
  • height="200"
  • alt="Cute couple, giggling."
  • style="position: fixed ; left: 0px ; top: 25% ;"
  • />
  •  
  •  
  • <script type="text/javascript">
  •  
  • // Get a reference to the img.
  • var img = $( "img:first" );
  •  
  •  
  • // I will move the image to the right and then back to
  • // its original position. Since the loop always uses the
  • // image's current position, it should always end up back
  • // in its starting position... unless there is a race
  • // condition that occurrs.
  • var mover = function(){
  •  
  • // Set the current position.
  • var currentPosition = 0;
  •  
  • // Move the image to the right.
  • for (var i = 0 ; i < 800 ; i++){
  •  
  • // Nudge right.
  • img.css(
  • "left",
  • ((img.position().left + 1) + "px")
  • );
  •  
  • // Check to make sure the current position matches
  • // the expected post-nudge position.
  • if (++currentPosition != img.position().left){
  •  
  • // Log the unexpected position.
  • console.log(
  • "Unexpected position of",
  • img.position().left,
  • "- expected",
  • currentPosition
  • );
  •  
  • }
  •  
  • }
  •  
  • // Move the image back to the left.
  • for (var i = 0 ; i < 800 ; i++){
  •  
  • // Nudge left.
  • img.css(
  • "left",
  • ((img.position().left - 1) + "px")
  • );
  •  
  • // Check to make sure the current position matches
  • // the expected post-nudge position.
  • if (--currentPosition != img.position().left){
  •  
  • // Log the unexpected position.
  • console.log(
  • "Unexpected position of",
  • img.position().left,
  • "- expected",
  • currentPosition
  • );
  •  
  • }
  •  
  • }
  •  
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Now that we have our mover function in place, we want to
  • // try and hit it a bunch of times to see if we can get the
  • // position of the images to ever fall in places that we
  • // didn't expect, which would indicate a race-condition.
  • for (var i = 0 ; i < 100 ; i++ ){
  •  
  • // Kick off mover after a short delay.
  • setTimeout( mover, 25 );
  •  
  • }
  •  
  • // Add some intervals as well, just to make sure that
  • // timeouts and intervals aren't fundamentally different.
  • for (i = 0 ; i < 100 ; i++ ){
  •  
  • // Create a self-executing function so that the timer
  • // will have a reference to itself for self-termination.
  • //
  • // NOTE: I know now why you cry. But it's something I
  • // can never do.
  • (function(){
  •  
  • // Kick off movier after a short delay.
  • var timer = setInterval(
  • function(){
  • // Invoke mover.
  • mover();
  •  
  • // Self-terminate after first run.
  • clearInterval( timer );
  • },
  • 26
  • );
  •  
  • })();
  •  
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Now, let's try the same thing with some AJAX calls to see
  • // if their asynchronous nature messes anything up.
  • for (i = 0 ; i < 100 ; i++){
  •  
  • // Launch an AJAX request and then trigger movier as our
  • // success function.
  • $.get( "./dummy.txt", {}, mover );
  •  
  • }
  •  
  • </script>
  •  
  • </body>
  • </html>

Notice that after each nudge, I check to see if the current position of the image is at the expected value. This would always be true unless two parallel executions of the function were nudging the same image at the same time (thereby nudging faster than the expected position).

This page takes about 5 minutes to run with all the position calculations and AJAX requests, so I won't bother trying to demo it in a video. What I can say, however, is that I never once logged a single unexpected image position. I don't know if this is conclusive in any way; but, it appears on the surface that setTimeout() and setInterval() do not create race conditions.

If anyone sees anything glaringly wrong with this logic, please let me know. Each execution of the mover() function seems to take long enough to ensure that timers of equal delay won't coincidentally finish executing in serial. As such, I can only conclude that the serial execution is enforced by the browser.




Reader Comments

Well if you think about it, the JS VM in browsers are single threaded (event loops ftw). Each call gets pushed onto the call stack. Even for non blocking calls like setTimeout,setInterval and XHR. They just get queued up on the event loop and will run after one another, which means they will never ever have a race condition. (As far as I know)

Reply to this Comment

@Ben,

The browser JavaScript runtime has a single execution thread. You cannot create threads in JavaScript. For asynchronous or event-driven programming, you can schedule callbacks which the JavaScript runtime thread will execute in response to events. But the callbacks will be executed in the main (and only) JavaScript execution thread.

When a callback is supposed to be executed (via a timer, click event, ajax event, etc), it is enqueued for execution. The JavaScript runtime runs in a loop, dequeuing callbacks from the work queue and executing them.

This is known as *evented programming* or *an event loop*, in contrast to *threaded programming*. This is in part what makes JavaScript asynchronous programming very easy to do, and is in part why Node.js (server-side JavaScript) is gaining popularity.

Cheers,
Jay

Reply to this Comment

Ben et al,
It turns out that you can setup race conditions if you're using AJAX and one server response happens faster than the next. Instead of requesting a file that is plain text, request a ColdFusion file that has some sort of delay built into its response that is random. This may trigger a different result.

Reply to this Comment

@Garrett, @Justice,

It's starting to sure up in my mind. @Justice, I believe we've actually talked about this briefly before in the comments of another post (though I was not able to find it). Good thoughts on the eventing.

I've played around a bit with Node.js and I get the event loop they talk about; but, as far as execution per-loop, I don't think I fully understood how things happened.

@Randy,

That's a different kind of race condition - that has to do with the order in which AJAX requests are returned.

Reply to this Comment

One one you can run into race conditions like this in JS is using Web Workers:

http://www.whatwg.org/specs/web-workers/current-work/

With multiple web workers doing the same thing, you could certainly run into race conditions because the web workers work outside the single threadedness of your normal JS (which is the whole point--to allow you to do expensive processing that doesn't interfere with the normal JS execution process.)

Reply to this Comment

@Dan,

At this point, I've only read about Web Workers but have not used them. But, they work through messaging, right? So, at some point they have to dip back into the single-threaded main page; so, would they just fall back into the event loop like an AJAX callback?

Reply to this Comment

@Ben,
Right, I didn't pick up from your post which kind of race condition you were trying to recreate. The event loop will otherwise handle things in a consistent order. I have run into crazy race conditions with AJAX returns so I just warned about it because it has bitten me more than once :)

Reply to this Comment

@Randy,

No worries; I've definitely heard of people having AJAX order problems :) Someone hinted to me the other day that jQuery 1.5 may have actually added some serial-AJAX queuing situation; but, I've not actually seen or ready anything about it.

Reply to this Comment

@Dan

But don't workers only have access to worker global scope (not window global scope) they won't be able to effect that aspect? I would also assume the messaging API listening on the window for worker messages would also be event based and not have any race conditions.

Reply to this Comment

@Ben,

Correct, web workers may run in their own fibers/threads/processes; but because they communicate with each other only via message-passing, and because web workers are not permitted to touch the DOM, you will not see odd race conditions with them.

Each callback being executed on the main thread in response to a message from a worker will execute *to completion* before another callback may be executed on the main thread.

Cheers,
Justice

Reply to this Comment

Too bad we can't see in the browser engine, how it's handling each request, and if it's taking too many cpu requests, as we can with windows task manager.

But I think your right about being concerned.

These days, everyone wants an ajax powered something or other, and have we really given as much concern to the eventual security and performance bottlenecks?

Are there better tools or ways or methods to debug jquery/ajax?

Reply to this Comment

@Craig,

If you use google-chrome:
* you can browse to about:memory to see memory usage per-tab
* you can right-click the top and click Task Manager to see CPU usage per-tab
* and you can hit ctrl+shift+j to see network requests (including timing, caching, etc) for the current tab and you get a console and a debugger.

JavaScript is very fast across all modern browsers (Firefox, Safari, Chrome, Opera, IE). Using AJAX correctly can make your page feel much faster and can make your website as a whole much more robust (your JavaScript can request a small piece of data in JSON format using very little server resources and then update the HTML on the page, rather than refreshing the whole page which can use a lot more server resources).

The security concerns arising from use of AJAX are already handled by good web-development frameworks.

Cheers,
Justice

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.