Critical Section in JavaScript or jQuery
Asked Answered
L

2

9

I have a webpage, in which a certain Ajax event is triggered asynchronously. This Ajax section could be called once or more than once. I do not have control over the number of times this event is triggered, nor the timing.

Also, there is a certain code in that Ajax section that should run as a critical section, meaning, when it is running, no other copy of that code should be running.

Here is a pseudo code:

  1. Run JavaScript or jQuery code
  2. Enter critical section that is Ajax (when a certain process is waiting for a response callback, then do not enter this section again, until this process is done)
  3. Run more JavaScript or jQuery code

My question is, how can I run step 2 the way described above? How do I create/guarantee a mutual exclusion section using JavaScript or jQuery.

I understand the theory (semaphores, locks, ...etc.), but I could not implement a solution using either JavaScript or jQuery.

EDIT

In case you are suggesting a Boolean variable to get into the critical section, this would not work, and the lines below will explain why.

the code for a critical section would be as follows (using the Boolean variable suggestions):

load_data_from_database = function () {

    // Load data from the database. Only load data if we almost reach the end of the page
    if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) {

        // Enter critical section
        if (window.lock == false) {

            // Lock the critical section
            window.lock = true;

            // Make Ajax call
            jQuery.ajax({
                type:        'post',
                dataType:    'json',
                url:         path/to/script.php,
                data:        {
                    action:  'action_load_posts'
                },
                success:     function (response) {
                    // First do some stuff when we get a response

                    // Then we unlock the critical section
                    window.lock = false;
                }

            });


            // End of critical section
        }
    }
};

// The jQuery ready function (start code here)
jQuery(document).ready(function() {
    var window.lock = false; // This is a global lock variable

    jQuery(window).on('scroll', load_data_from_database);
});

Now this is the code for the lock section as suggested using a Boolean variable. This would not work as suggested below:

  1. The user scrolls down, (and based on the association jQuery(window).on('scroll', load_data_from_database); more than one scroll event is triggered.

  2. Assume two scroll events are triggered right at almost the same moment

  3. Both call the load_data_from_database function

  4. The first event checks if window.lock is false (answer is true, so if statement is correct)

  5. The second event checks if window.lock is false (answer is true, so if statement is correct)

  6. The first event enters the if statement

  7. The second event enters the if statement

  8. The first statement sets window.lock to true

  9. The second statement sets window.lock to true

  10. The first statement runs the Ajax critical section

  11. The second statement runs the Ajax critical section.

  12. Both finish the code

As you notice, both events are triggered almost at the same time, and both enter the critical section. So a lock is not possible.

Lysander answered 3/3, 2014 at 15:41 Comment(12)
Create a boolean variable and set it to true when the critical section is called. When it's done, set it to false. Of course, at the beginning of the code, make sure it's not already set to trueSillabub
You don't need need semaphores or locks because javascript is single threaded. You just need a "flag" variable.Subtilize
@Sillabub This would not work, I tried it and it fails. I will update the question to show why this solutions does not work.Lysander
@JamesMontagne This is the same suggestion as Ian, it would not work. Again, I will update the question above to show why it would not workLysander
@Lysander It sure would work. You must not be using the flag variable correctlySillabub
My understanding is that Javascript running in a browser is single-threaded, so there are no critical sections or semaphores. Is this a nodejs question? See: #2224745Embolism
@Sillabub This is the update I made. Please review it. An please make sure I am wrong becaue I want the correct answer :)Lysander
Unless you are actually seeing the problem occurring, I question your logic @Greeso. As others have stated, JavaScript is single-threaded. This means that there isn't a way for 2 threads to be running an event handler at the same time and be reading/updating the window.lock variable "at the same time" (or almost the same time). If you need all executions of the AJAX triggered by the scroll to fire, and the 2nd, 3rd, etc. to wait for the 1st to finish, that is a mildly different question (one for which I'm writing an answer).Conducive
@Conducive Yes, I am actually seeing the problem occuring! Do you think I want to waste my time and everyone else's just to ask a question? I am testing on FireFox and it does occurLysander
@Conducive I couldn't have said it better myself. When I saw one of the recent edits that explained the expected workflow, I thought the same things. Your last sentence is what concerns me most - I wonder if the OP's real question isn't what we're actually thinkingSillabub
@Sillabub Please read my response to squid314. I do see the problem on FireFox using FireBug. I would not want to waste your time nor mine if this was not a problem. And it happens consistantly.Lysander
@Lysander the way you describe the two event handlers hitting the "if" one after the other is impossible. There is no interleaving, so that isn't happening. When you have a problem in computer programming, it is easy to become embroiled by the answer you imagine, but try not to let that take over your faculties. What people are telling you is true: there is no interleaving. One event isn't interrupting the other. However, the callbacks from your callbacks might not be happening in the order you expect. This can be fixed with promises (I'm sure someone is working on a promise answer).Embolism
E
10

I think the most helpful information you provided above was your analysis of the locking.

  1. The user scrolls down, (and based on the association jQuery(window).on('scroll', load_data_from_database); more than one scroll event is triggered.

  2. Assume two scroll events are triggered right at almost the same moment

  3. Both call the load_data_from_database function

  4. The first event checks if window.lock is false (answer is true, so if statement is correct)

  5. The second event checks if window.lock is false (answer is true, so if statement is correct)

Right away this tells me that you have come to a common (and quite intuitive) misunderstanding.

Javascript is asynchronous, but asynchronous code is not the same thing as concurrent code. As far as I understand, "asynchronous" means that a function's subroutines aren't necessarily explored in depth-first order as we would expect in synchronous code. Some function calls (the ones you are calling "ajax") will be put in a queue and executed later. This can lead to some confusing code, but nothing is as confusing as thinking that your async code is running concurrently. "Concurrency" (as you know) is when statements from different functions can interleave with one another.

Solutions like locks and semaphores are not the right way to think about async code. Promises are the right way. This is the stuff that makes programming on the web fun and cool.

I'm no promise guru, but here is a working fiddle that (I think) demonstrates a fix.

load_data_from_database = function () {

    // Load data from the database. Only load data if we almost reach the end of the page
    if ( jQuery(window).scrollTop() >= jQuery(document).height() - jQuery(window).height() - 300) {

        console.log(promise.state());
        if (promise.state() !== "pending") {
            promise = jQuery.ajax({
                type:        'post',
                url:         '/echo/json/',
                data: {
                    json: { name: "BOB" },
                    delay: Math.random() * 10
                },
                success:     function (response) {

                    console.log("DONE");
                }
            });
        }
    }
};

var promise = new $.Deferred().resolve();

// The jQuery ready function (start code here)
jQuery(document).ready(function() {

    jQuery(window).on('scroll', load_data_from_database);
});

I'm using a global promise to ensure that the ajax part of your event handler is only called once. If you scroll up and down in the fiddle, you will see that while the ajax request is processing, new requests won't be made. Once the ajax request is finished, new requests can be made again. With any luck, this is the behaviour you were looking for.

However, there is a pretty important caveats to my answer: jQuery's implementation of promises is notoriously broken. This isn't just something that people say to sound smart, it is actually pretty important. I would suggest using a different promise library and mixing it with jQuery. This is especially important if you are just starting to learn about promises.

EDIT: On a personal note, I was recently in the same boat as you. As little as 3 months ago, I thought that some event handlers I was using were interleaving. I was stupefied and unbelieving when people started to tell me that javascript is single-threaded. What helped me is understanding what happens when an event is fired.

In syncronous coding, we are used to the idea of a "stack" of "frames" each representing the context of a function. In javascript, and other asynchronous programming environments, the stack is augmented by a queue. When you trigger an event in your code, or use an asynchronous request like that $.ajax call, you push an event to this queue. The event will be handled the next time that the stack is clear. So for example, if you have this code:

function () {
    this.on("bob", function () { console.log("hello"); })

    this.do_some_work();
    this.trigger("bob");
    this.do_more_work();
}

The two functions do_some_work and do_more_work will fire one after the other, immediately. Then the function will end and the event you enqueued will start a new function call, (on the stack) and "hello" will appear in the console. Things get more complicated if you trigger an event in your handler, or if you trigger and event in a subroutine.

This is all well and good, but where things start to get really crappy is when you want to handle an exception. The moment you enter asynchronous land, you leave behind the beautiful oath of "a function shall return or throw". If you are in an event handler, and you throw an exception, where will it be caught? This,

function () {

    try {
        $.get("stuff", function (data) {
            // uh, now call that other API
            $.get("more-stuff", function (data) {
                // hope that worked...
            };
        });
    } catch (e) {
        console.log("pardon me?");
    }
}

won't save you now. Promises allow you to take back this ancient and powerful oath by giving you a way to chain your callbacks together and control where and when they return. So with a nice promises API (not jQuery) you chain those callbacks in a way that lets you bubble exceptions in the way you expect, and to control the order of execution. This, in my understanding, is the beauty and magic of promises.

Someone stop me if I'm totally off.

Embolism answered 3/3, 2014 at 17:19 Comment(5)
Here it is! I was looking for this: github.com/kriskowal/q/wiki/General-Promise-Resources These videos are great. I seriously recommend watching them all. While you are out there, learning the secrets of the webs, google "monad" too. :)Embolism
This is a reasonable solution (solving the problem of at most one "critical execution" at a time), but I'm not sure that I like the idea of "polling" the promise to ask if it is still pending. That isn't much different from just having a flag sitting in the function's scope which states "there is/isn't a request currently pending". It doesn't appear to use the power of Promises to relieve the code of any burden except setting the flag back to false. (I will go watch the videos.)Conducive
@Embolism Thanks for a GREAT explanation. I will watch the vids. One question, which promisses API do you recommend?Lysander
@Greeso, ideally I would use q because the promises/A+ dude maintains it. In reality, more often than not, I end up using jQuery (the lazy way out). github.com/kriskowal/qEmbolism
@Conducive probably if it was my code I would combine this with debounce. I would debounce the scroll callback, so that it doesn't get called 30 times in a row. But yeah, as I mentioned, I'm not a promise guru or anything! (I'm just not afraid of them anymore ^o^//)Embolism
C
2

I would recommend a queue which only allows one item to be running at a time. This will require some modification (though not much) to your critical function:

function critical(arg1, arg2, completedCallback) {
    $.ajax({
        ....
        success: function(){
            // normal stuff here.
            ....

            // at the end, call the completed callback
            completedCallback();
        }
    });
}

var queue = [];
function queueCriticalCalls(arg1, arg2) {
    // this could be done abstractly to create a decorator pattern
    queue.push([arg1, arg2, queueCompleteCallback]);

    // if there's only one in the queue, we need to start it
    if (queue.length === 1) {
        critical.apply(null, queue[0]);
    }


    // this is only called by the critical function when one completes
    function queueCompleteCallback() {
        // clean up the call that just completed
        queue.splice(0, 1);

        // if there are any calls waiting, start the next one
        if (queue.length !== 0) {
            critical.apply(null, queue[0]);
        }
    }
}

UPDATE: Alternative solution using jQuery's Promise (requires jQuery 1.8+)

function critical(arg1, arg2) {
    return $.ajax({
        ....
    });
}

// initialize the queue with an already completed promise so the
// first call will proceed immediately
var queuedUpdates = $.when(true);

function queueCritical(arg1, arg2) {
    // update the promise variable to the result of the new promise
    queuedUpdates = queuedUpdates.then(function() {
        // this returns the promise for the new AJAX call
        return critical(arg1, arg2);
    });
}

Yup, the Promise of cleaner code was realized. :)

Conducive answered 3/3, 2014 at 16:32 Comment(6)
Hey squid, good idea. The ajax request returns a promise, though, so couldn't the OP just poll the promise instead of a queue?Embolism
Thanks squid314. I will def. try that and see what I can make of it.Lysander
The jQuery AJAX does return a promise, and that could probably be used. Promises are probably a "cleaner" solution in general, but do require an additional library if you have asynchronous operations which don't return promises.Conducive
I added an implementation which uses promises to handle the queue instead of managing the queue manually. It does require jQuery 1.8+ which could be a limiting factor.Conducive
Promises are sweet. It is important to note, though, that jQuery's promise API is missing some important things. It doesn't let you handle exceptions raised in the callbacks, which is basically the most important part. Check out Q maybe?Embolism
Definitely, Q is a lot better. However, I didn't want to add additional weight to his application if he didn't want it.Conducive

© 2022 - 2024 — McMap. All rights reserved.