views:

268

answers:

5
+6  Q: 

Ajax concurrency

I have a web application where there is a timer that is constantly counting down. Meanwhile, the client frequently checks with the server to see if more time has been added to the timer. The code looks something like this:

function tick() {
    // This function is called once every second
    time -= 1;
    redisplay(time);
};
function update(newtime) {
    // This function is called whenever the ajax request
    // to the server yields a new time
    time = newtime;
};

It is, of course, a bit more complex than that, but you can see the inherent race condition. What if the update and the tick function are both trying to modify time at the same time?

Frankly, I don't know nearly enough javascript to understand how to deal with this sort of concurrency issue: is there an easy way to do this, or if not, can somebody point me towards resources where I can learn more?

Thank you.

A: 

As long as you add some more seconds to you current time you should be fine.

Instead of doing time = newtime; try time += newtime; This way you will not lose the second you're worried about.

Still, you are only losing a second at worst.

Horia Dragomir
Note, newtime is just how much more time to add.
Horia Dragomir
I don't think this addresses the race condition issue
Andrea Zilio
A: 

I was wrong: this does not solve the issue! (explaination after the code)

NEWTIME = false;
function tick() {
    // This function is called once every second
    if (NEWTIME) {
        time = NEWTIME;
        NEWTIME = false;
    }
    time -= 1;
    redisplay(time);
};
function update(newtime) {
    // This function is called whenever the ajax request
    // to the server yields a new time
    NEWTIME = newtime;
};

The problem with this wrong solution is that doing this way you just move the race condition issue from variable time to variable NEWTIME.

Just think this: the execution of tick reaches and executes the line time = NEWTIME; now, before continuing, update get called and NEWTIME gets a value X. Now tick execution continues executing NEWTIME = false;. This way you've lost the X value of NEWTIME and so the effect of an update() call!

Andrea Zilio
This is exactly what I intended to do, how does this not address the race condition? You might be off by a cycle, but it doesn't seem to me that `NEWTIME` will get lost.
Justin Johnson
@Justin This way you just move the race condition issue from variable `time` to variable `NEWTIME`.Just think this: the execution of tick reaches and executes the line `time = NEWTIME;` now, before continuing, `update` get called and `NEWTIME` gets a value `X`. Now `tick` execution continues executing `NEWTIME = false;`.This way you've lost the effect of an `update()` call!
Andrea Zilio
Yes, but you only lose it for a maximum of one tick, as opposed to the original way which causes it to be lost for ever *if* the language was concurrent.
Justin Johnson
@Justin: No, suppose you are in a point of execution where `time` is 100 and `NEWTIME` is 150; now `tick` executes the line `time = NEWTIME;` so now `time` is 150. Now, before continuing executing `tick`, `update` get called with the first argument `newtime` set to 200. In `update` the line `NEWTIME = newtime` is executed so `NEWTIME` now is 200.Now the `tick` execution resume and `NEWTIME = false` is executed and so the effect of the `update` call just executed is lost and you've lost 50 seconds in your timer! :)Obviously this is not the case anymore since javascript is single threaded :)
Andrea Zilio
So it's not "No" then.
Justin Johnson
You said that "you only lose it for a **maximum of one tick**, as opposed to the original way which causes it to be lost for ever **if the language was concurrent** ".This is wrong because taking into consideration the example explained in my previous comment, if the language was concurrent, you lose 50 seconds of your timer **for ever**.So the solution we're talking about does not address the race condition. That is why the answer is "No".
Andrea Zilio
A: 

The problem needs some semaphores. I do that a lot for sending ajax after previous one finishes. Your case is a bit alike ;)

Try something like that. It should ignore all attempts to decrement time that collide with ajax callback.

window.TimeKeeper = new function(){
this.time=0; //initial value, whatever
this.isopen=true;

this.decrement = function(){ 
 if(this.isopen){
  this.time--;
  redisplay(this.time);
  }
 }
this.set = function(val){
 if(this.isopen){
  this.isopen=false;
  this.time=val;
  this.isopen=true;
  } else {
  //another AJAX callback is modifying time. 
   //You can enqueue current value and put it later
  }
 }

}

function tick() {
    TimeKeeper.decrement();

};
function update(newtime) {
    TimeKeeper.set(newtime);
};

One more thing - the setTimeout works like a new thread and I would expect that browsers do the synchronising mem access, so It might be enough to check if the value didn't grow before decrementing. But the above solution is more flexible and safe.

And one little tip - avoid querying with AJAX too often - it may cause extra problems - like requests coming back in different order than sent, and firefox memory usage building up a lot on too much ajax a minute ;)

naugtur
Thanks. What happens if decrement fails, though? The time doesn't get redisplayed. In this case, you could just move the redisplay call outside the if, but for a more general case, is there a non-blocking way to wait and try again in a moment? Also, I need to check fairly frequently for updates from the server, which at the moment means I am doing one ajax call per second. If I need updates that frequently, is there a better way to do it than ajax? There's no way for the server to send a notice to the clients when an event occurs, is there?
Nate
I don't have time now to check deeply, but this doesn't seem a good solution to me...
Andrea Zilio
Javascript is single threaded. You don't need semaphores. The worst that happens is that the decrement happens (followed by redisplay), and then the value is immediately overwritten by a newly supplied value from update. In that case, the time will be decremented and displayed a second later. Not a problem.
PanCrit
This answer is very much wrong :(. setTimeout does not at all work like a new thread, and you don't need a semaphore, or any sync primitive at all to ensure atomicity.
ShZ
+5  A: 

Javascript is single threaded. There is no race condition. There's no way in javascript for the two lines time = newtime; and time -= 1; to overlap during execution. In fact, the two functions are guaranteed not to overlap. One of them will run and then the other will run.

PanCrit
+7  A: 

You don't have a race condition, because Javascript doesn't execute concurrently.

Every time a callback is fired from an async operation (AJAX, setTimeout, etc), that callback must finish executing before another callback from another async operation can be called. So if update() is running, no other Javascript is. Once update() finishes, other callbacks from async operations can be fired (for example, tick()). Interestingly enough, this is also why setTimeout and its ilk aren't guaranteed to execute at the precise moment the timeout is reached: some other javascript could be blocking the callback's execution.

Check out http://ejohn.org/blog/how-javascript-timers-work/ for some good information on how all this works.

ShZ