1

EDIT: found the answer - https://www.youtube.com/watch?v=8aGhZQkoFbQ

_

Okay, so I have some C# background and C# "async environment" is a bit of a mixed bag of concurrency and "small parallelism" i.e. when it comes to heavy async environment you can have race conditions, deadlock and you need to protect shared resources.

Now, I'm trying to understand how does JavaScript/ES6 async environment works. Consider following code:

// current context: background "page"
// independent from content page
let busy = false;

// This is an event handler that receives event from content page
// It can happen at any time
runtimeObj.onMessage.addListener((request) =>
{
    if(request.action === 'AddEntry')
    {
        AddEntry(request);
        return true;
    }
    return false;
} );


function AddEntry(data)
{
    if (!busy)
        group.push({url: data.url, time: Date.now(), session: data.session});
    else
        setTimeout(AddEntry(data),10000) // simulating Semaphore wait
}

// called from asynchronous function setInterval()
function SendPOST()
{
    if (groups.length < 1 || groups === undefined)
        return;

    busy = true; // JS has no semaphores so I "simulate it"
    let del = [];
    groups.forEach(item =>
    {
        if (Date.now() - item.time > 3600000)
        {
            del.push(item);
            let xhr = new XMLHttpRequest();
            let data = new FormData();
            data.append('action', 'leave');
            data.append('sessionID', item.session);
            xhr.withCredentials = true;
            http.onreadystatechange = function()
            {
                if(http.readyState == 4 && http.status !== 200) {
                    console.log(`Unable to part group ${item.url}! Reason: ${http.status}. Leave group manually.`)
            }
}
            xhr.open('POST', item.url, true);
            xhr.send(data);
        }
    });
    del.forEach(item => groups.slice(item,1));
    busy = false;
}


setInterval(SendPOST, 60000);

This is not really the best example, since it does not have bunch of async keyword paired with functions but in my understanding both sendPost() and AddEntry() arent really pure sequential operations. Nonetheless, I've been told that in AddEntry(), busy always will be false because:

sendPost is queued to execute in a minute

an event is add to the event loop

the event is processed and AddEntry is called

since busy = false the group is pushed

a minute passes and SendPost is added to the event loop

the event is process and SendPost is called

groups.length === 1 so it continues

busy = true

each group causes a request to get queued

an event comes in

busy = false

the event is processed, AddEntry is called

busy is false, like it will always be

group is pushed

eventually the requests from before are resolved, and t he onreadystatechange callbacks are put on the event loop

eventually each of the callbacks are processed and the logging statements are executed

Is this correct? From what I understand it essentially means there can be no race conditions or deadlock or I ever need to protect shared resource.

If I'm two write a similar code for C# runtime where sendPost() and AddEntry() are asynchronous task methods that can be called in non-blocking way from different event sources there could situation when I access shared resource while the iteration context is temporally suspended in context switch by Thread Sheduler.

5
  • This: Is this correct? From what I understand it essentially means there can be no race conditions or deadlock or I ever need to protect shared resource. Yes - it is called "Run to Completion" in JavaScript. Logic errors can create race conditions where a dev expects something to be set prior to completion, but that is due to a code problem - not a concurrency problem. Commented May 22, 2019 at 21:44
  • Have you seen this? developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop Commented May 22, 2019 at 21:45
  • @RandyCasburn Yes, that is what essentially the quoted text in my post explains. Commented May 22, 2019 at 21:51
  • As I suspected, just wanted to make sure. @jonas has a terrific answer for you. Commented May 22, 2019 at 21:52
  • You should switch this: groups.length < 1 || groups === undefined into this !groups || groups.length < 1 the former might throw an error. Commented May 22, 2019 at 23:53

1 Answer 1

2

Is this correct?

Yes, it describes adequately whats happening.

JS runs in a single threaded way, which means that a function will always run till it's end.

 setTimeout(function concurrently() {
   console.log("this will never run");
 });

  while(true) console.log("because this is blocking the only thread JS has");

there can be no race conditions ...

There can (in a logical way). However, they cannot appear in synchronous code, as JavaScript runs single threaded¹. If you add a callback to something, or await a promise, then other code might run in the meantime (but never at the same time!), which might cause a race condition:

 let block = false;

 async function run() {
   if(block) return // only run this once
   // If we'd do block = true here, this would be totally safe
   await Promise.resolve(); // things might get out of track here
   block = true;
   console.log("whats going on?");
}

run(); run();

... nor deadlocks ...

Yes, they are quite impossible (except if you use Atomics¹).

I never need to protect shared resource.

Yes. Because there are no shared ressources (okay, except SharedBuffers¹).


¹: By using WebWorkers (or threads on NodeJS), you actually control multiple JS threads. However each thread runs its own JS code, so variables are not shared. These threads can pass messages between each other and they can also share a specific memory construct (a SharedArrayBuffer). That can then be accessed concurrently, and all the effects of concurrent access apply (but you will rarely use it, so ...).

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

10 Comments

"Because there are no shared ressources[...]" /// how come? The groups array in my code supposedly a shared collection. i say supposedly because I thought AddEntry() can execute any time, even during the moment when sendPOST() iterates through it because AddEntry() is a callback, technically. Also, the code example you provided makes no sense, you made erroneous lock on purpose, you do not use lock that way. I'm talking about race condition due to concurrency when no locks used.
@Kirikan what is your definition of "shared resource" (in context of thread safety)? Since regular JavaScript is singlethreaded (as Jonas Wilms mentioned in the answer) there is no "shared resources" if regular thread safety meaning (resource accessed by multiple thread)...
@Kirikan there are no locks in code in the answer - could you please clarify what you mean "you made erroneous lock on purpose"? (again - locks make no sense for single-threaded environment)
@kirikan no. No code will run at the same time. Yes AddEntry is a callback. That means that code might run in the mea time before it was called and after it finished, but the function itself does only run if no other code is executing.
@Kirikan there is no "context switching" in JavaScript. All code runs till completion synchronously and only after that engine will pick next piece of code to run synchronously. "Next piece" is either top level code (next JS file) or next callback (external like click/http response or timer). await/async is just nice syntax for constructing callback on promise resolution (which is itself just wrapper around callbacks).
|

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.