MirrorMirror
MirrorMirror

Reputation: 188

possible javascript ajax call race condition

so i am implementing in javascript this feature: when the document is opened by the user its data gets saved every 10 seconds and when the user closes it is saved one more time. It works seemingly well. Here is the implementation:

var data = "blabla";
var saveagain = false;
var t1;

function onDocumentOpen() {
    saveagain = true;
    savedata();
}

function onDocumentClose() {
    saveagain = false;
    savedata();
}

function savedata() {
    if (!saveagain) clearTimeout(t1);
    SaveDataAjaxCall(data, function() {
        //data was saved
        if (saveagain) t1 = setTimeout("savedata()", 10000);
    });
}

I was wondering if my approach is correct and if it can lead to some possible race condition in extreme circumstances such as:

when the savedata() instance called from onDocumentClose() is after the if(!saveagain) step, the savedata() instance called from the previous timer of the setTimeout() is before that step, so it gets to be called once more. Can this or anything more weird happen ?

Thanks in advance

EDIT:

After considering T.J. Crowder's and Bengi's comments I finalized the code as such:

var data = "";
var saveagain = false;
var t1;
var beingsaved = false;

function onDocumentOpen() {
    saveagain = true;
    savedata();
}

function onDocumentClose() {
    saveagain = false;
    savedata();
}

function saveData() {
    if (beingsaved) {
        if (!saveagain) setTimeout(saveData, 100);
        return false;
    }
    beingsaved=true;

    if (!saveagain) clearTimeout(t1);

    data=getData();

    SaveDataAjaxCall(data, function() {
        //data was saved
        beingsaved=false;
        if (saveagain) t1 = setTimeout(saveData, 10000);
    });

}

I think I have handled every occasion now. I think the beingsaved solution is equal to the atomic countr that T.J Crowder suggested.

EDIT2: Hm, I'm not sure i solved it because there may be a case when if(beingsaved) gets evaluated by the setTimeout call JUST before the beingsaved is set to true by the onDocumentClose call. Can this happen ?

Upvotes: 2

Views: 689

Answers (2)

Bergi
Bergi

Reputation: 664195

No. Javascript is single-threaded (apart from innovations like WebWorkers and Co, which need to communicate on a event-based interface).

So once your synchronous execution is started (the global script, the timeout, the ajax event handler), it runs and can't be stopped. Everything else (new events, 0ms-timeouts, etc.) will be scheduled afterwards.

Your script contains 2 asynchronous scenarios: the timeout and the ajax callback. After you started the loop onDocumentOpen, it just goes like that:

  1. execute saveData: start ajax request
  2. wait (until ajax event happens)
  3. execute success callback: set a new timeout for saveData
  4. wait (until timeout event happens)
  5. execute saveData: start ajax request
  6. wait...

...and so on.

Your onDocumentClose can only execute during a wait period, when no other execution runs. You'd exspect the following:

  1. ...execute saveData: start ajax request
  2. nothing happens
  3. ajax event: execute success callback: set a new timeout for saveData
  4. nothing happens
  5. documentClose event: clears the timeout, starts ajax request
  6. nothing happens
  7. ajax event: execute success callback: does not set a new timeout any more. End.

But you didn't secure the case when documentClose happens during the ajax request:

  1. ...execute saveData: start ajax request
  2. nothing happens
  3. ajax event: execute success callback: set a new timeout for saveData
  4. nothing happens
  5. timeout event: execute saveData, start ajax request
  6. nothing happens
  7. documentClose event: clears the (non-existing) timeout, starts ajax request (a second time)
  8. nothing happens
  9. one of the ajax event: execute success callback: does not set a new timeout any more.
  10. nothing happens
  11. other ajax event: execute success callback: does not set a new timeout any more. End.

So it will always come to an end. If one of the events would fire during something executes, there just would be no "nothing happens" in between - but it will get executed one after the other. Even if the timeout should end during the execution of the ajax callback and before it is cleared, it will be de-scheduled when it's getting cleared after its end:

var id = setTimeout(function(){
    alert("still waiting for execution"); // never alerts
}, 500);
setTimeout(function(){
    alert("still waiting for execution"); // this alerts
}, 500);
for(var d = Date.now(); Date.now()-d < 1000; ) {
    ; // wait - the timeouts end during this heavy processing
}
clearTimeout(id);

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1073978

I assume your save operation is asynchronous, as all good ajax operations should be. If so, you'll want to put some kind of guarding condition around it so that the two save triggers don't overlap. Or, of course, allow them to overlap but handle it server side (perhaps with a sequence number or timestamp), where it ignores an earlier save if a later one has already been committed.

Upvotes: 2

Related Questions