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

Posted February 9, 2011 at 10:29 AM by Ben Nadel

Tags: Javascript / DHTML

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

Feb 9, 2011 at 11:10 AM // reply »
13 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)


Feb 9, 2011 at 11:34 AM // reply »
113 Comments

@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


Feb 9, 2011 at 12:07 PM // reply »
3 Comments

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.


Feb 9, 2011 at 12:35 PM // reply »
10,743 Comments

@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.


Feb 9, 2011 at 1:04 PM // reply »
164 Comments

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.)


Feb 9, 2011 at 1:08 PM // reply »
10,743 Comments

@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?


Feb 9, 2011 at 1:08 PM // reply »
3 Comments

@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 :)


Feb 9, 2011 at 1:14 PM // reply »
10,743 Comments

@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.


Feb 9, 2011 at 1:30 PM // reply »
13 Comments

@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.


Feb 9, 2011 at 1:32 PM // reply »
113 Comments

@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


Feb 10, 2011 at 9:47 AM // reply »
23 Comments

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?


Feb 10, 2011 at 10:14 AM // reply »
113 Comments

@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


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 16, 2012 at 8:18 PM
Best Of ColdFusion 10 Contest Entry - HTML Email Utility
Just found this, looks good! I'm trying to run it on local, it's the 64bit version and I'm experiencing horrible lag. On average the generate.cfm processes the content change in 60-90 seconds. I've ... read »
May 16, 2012 at 6:40 PM
Maintaining Sessions Across Multiple ColdFusion CFHttp Requests
I am trying to integrate this CFHTTPsession into an application that will log into zeekrewards.com to post ads and I am not having any luck. The code works perfectly for logging into other websites, ... read »
May 16, 2012 at 2:44 PM
Creating A Sometimes-Fixed-Position Element With jQuery
Thank you, very useful technique! Worked like a charm. ... read »
May 16, 2012 at 1:58 PM
Movies As A Religious Experience
Acting can, in a way, ruin the movie-goer's experience. I used to be able to get so caught up in movies and their plots, and totally engaged. But lately, I haven't been able to as much with a lot o ... read »
May 16, 2012 at 1:52 PM
The Science Of Optimal Post-Exercise Nutrition
children of this age eat very less vegetables so u can opt for salads they will like it also carrot ,cucumber,onion and as far as pulses are concerned u can boil them ,give him along with mashed rice ... read »
May 16, 2012 at 1:34 PM
Strange ColdFusion JRUN Stack Overflow Error
Hey, Recently I updated my jrun4 using the latest updater 7 and now i am having memory issues :(:(:( any help is appreciated ... read »
May 16, 2012 at 9:56 AM
ColdFusion 10 Beta, Apache Tomcat, And Symbolic Links On Mac OSX
Hi, Now that ColdFusion 10 is out I have stumbled over this as well and I cannot figure out the proper solution. We're running virtual hosts via Apache2; the ColdFusion-applications store their fil ... read »
May 15, 2012 at 6:03 PM
Movies As A Religious Experience
@Ben, I don't know whether you'd consider this a religious observation, but it seems to me, in a sense, movies multiply how many lives we get to have. Each movie is like a little extra life we get ... read »