2

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 ?

2
  • 3
    Don't use strings for setTimeout - setTimeout(savedata, 10000) Commented May 21, 2012 at 17:27
  • What's that setTimeout(100) in case of beeingsave && !saveagain good for? What exactly do you want to achieve? Commented May 21, 2012 at 18:59

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

7 Comments

yes it is asynchronous. I thought about inserting a boolean IsBeingSaved = true before the SaveDataAjaxCall and then setting it to false after it gets saved, but then i figured it would be wrong because the document may have changed during the last autosave call, so i need it to overlap in that case when the document gets closed
@MIrrorMirror: It's a fun one, no question. I think I would tend toward the "sort it out on the server" side of things, using timestamps or a sequence value or similar to ensure that an older copy (perhaps held up by network congestion) can't overwrite a newer copy.
yes i understand the network congestion issue and i will solve it like you suggested. what bothers me is this: What happens when the autosave instance of the savedata() has JUST started JUST AFTER i cancel the Timer in the ondocumentclose instance of the savedata() ?
Yes, that's what my answer states: After you cancelled the timer, the whole autosave executes before any timeout function would start (it was cleared).
@MIrrorMirror: In that scenario (which sadly from reading the latest timeout spec information looks possible), you'd need to rely on sequence numbering or similar: At the start of each save (auto or other), increment a client-side counter and include it with the doc data. The server records the doc data and counter. Since two copies of the JS portion of your autosave code cannot run simultaneously, you know the counter increment will be atomic. The server should disregard a save with a lower counter value than the latest save it has processed.
|
1

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

5 Comments

JavaScript is single-threaded on browsers, but ajax calls are not (not good ones, anyway). So it's entirely possible for the two triggers to overlap: saveData starts an ajax call because of the autosave, and then while that call is still running, onDocumentClose calls it again to start another save. Or vice-versa. In a normal ajax call, the triggering JavaScript function completes and returns, and the browser calls the completion callback later.
Yes, the ajax may be in an other tread. But it still won't happen that two pieces of code are executed simultaneous.
@ Bergi: The two saves can and will overlap, barring the OP doing something to prevent it. Again, the JavaScript code can't be running simultaneously, but the JavaScript code only runs for a tiny fraction of the total save time, which is mostly spent with the browser doing network stuff prior to calling the callback.
Yes, there may be two parallel ajax requests. But I think the OP asked about a race condition between the clearTimeout and the saveagain variable.
I modified the code taking into consideration both your comments, check my edited first post.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.