Skip to main content
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with: Aaron Wolfe and Lance Smith
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with: Aaron Wolfe Lance Smith

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

By
Published in Comments (12)

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://some-image-domain.com/4584358_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.

Want to use code from this post? Check out the license.

Reader Comments

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)

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

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.

15,848 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.

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

15,848 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?

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

15,848 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.

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.

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

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?

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

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel