skip to Main Content

Javascript is single threaded and – Node.js uses an asynchronous event-driven design pattern, which means that multiple actions are taken at the same time while executing a program.

With this in mind, I have a pseudo code:

myFunction() // main flow
var httpCallMade = false // a global variable

async myFunction() {
   const someData = await callDB() // LINE 1 network call
   renderMethod() // LINE 2 flow1
}

redisPubSubEventHandler() { // a method that is called from redis subscription asynchronously somewhere from a background task in the program
   renderMethod() // LINE 3 flow2
}

renderMethod(){ 
   if(!httpCallMade) {
      httpCallMade = true //set a global flag
      const res = makeHTTPCall() // an asynchronous network call. returns a promise.
   } // I want to ensure that this block is "synchronized" and is not acessible by flow1 and flow2 simultaneously!
}

myFunction() is called in the main thread – while redisPubSubEventHandler() is called asynchronously from a background task in the program. Both flows would end in calling renderMethod(). The idea is to ensure makeHTTPCall() (inside renderMethod) is only allowed to be called once

Is it guaranteed that renderMethod() would never be executed in parallel by LINE2 and LINE3 at the same time? My understanding is that as soon as renderMethod() is executed – event loop will not allow anything else to happen in server – which guarantees that it is only executed once at a given time (even if it had a network call without await).

Is this understanding correct?

If not, how do I make synchronize/lock entry to renderMethod?

4

Answers


  1. You need to differentiate between synchronous and asynchronous, and single- and multi-threaded.

    JavaScript is single-threaded so no two lines of the same execution context can run at the same time.

    But JavaScript allows asynchronous code execution (await/async), so the code in the execution context does not need to be in the order it appears in the code but that different parts of the code can be executed interleaved (not overlapped) – which could be called "running in parallel", even so, I think this is misleading.

    event-driven design pattern, which means that multiple actions are taken at the same time while executing a program.

    There are certain actions that can happen at the same time, like IO, multiprocessing (WebWorkers), but that is (with respect to JavaScript Code execution) not multi-threaded.

    Is it guaranteed that renderMethod() would never be executed in parallel by LINE2 and LINE3 at the same time?

    Depends on what you define as parallel at the same time.
    Parts of logic you describe in renderMethod() will (as you do the request asynchronously) run interleaved, so renderMethod(){ if(!httpCallMade) { could be executed multiple times before you get the response (not the Promise) back from makeHTTPCall but the code lines will never executed at the same time.

    My understanding is that as soon as renderMethod() is executed – event loop will not allow anything else to happen in server – which guarantees that it is only executed once at a given time (even if it had a network call without await).

    The problem here is, that you somehow need to get the data from your async response.

    Therefore you either need to mark your function as async and use const res = await makeHTTPCall() this would allow code interleaving at the point of await. Or use .then(…) with a callback, which will be executed asynchronously at a later point (after you left the function)

    But from the beginning of the function to the first await other the .then not interleaving could take place.

    So your httpCallMade = true would prevent that another makeHTTPCall could take place, before the currently running is finished, under the assumption that you set httpCallMade to false only when the request is finished (in .then callback, or after the await)

    // I want to ensure that this method is "synchronized" and is not called by flow1 and flow2 simultaneously!

    As soon as a get a result in an asynchronous way, you can’t go back to synchronous code execution. So you need to have a guard like httpCallMade to prevent that the logic described in renderMethod can run multiple times interleaved.

    Login or Signup to reply.
  2. It sounds like you want to do something like a ‘debounce’, where any event will cause makeHttpCall() execute, but it should only be executing once at a time, and should execute again after the last call if another event has occurred while it was executing. So like this:

    1. DB Call is made, and makeHttpCall() should execute
    2. While makeHttpCall() is executing, you get a redis pub/sub event that should execute makeHttpCall() again, but that is delayed because it is already executing
    3. Still before the first call is done, another DB call is made and requires makeHttpCall() to execute again. But even though you have received two events, you only need to have it called one time to update something with the most recent information you have.
    4. The first call to makeHttpCall() finishes, but since there have been two events, you need to make a call again.
    const makeHttpCall = () => new Promise(resolve => {
      // resolve after 2 seconds
      setTimeout(resolve, 2000); 
    });
    
    // returns a function to call that will call your function
    const createDebouncer = (fn) => {
      let eventCounter = 0;
      let inProgress = false;
        
      const execute = () => {
        if (inProgress) {
          eventCounter++;
          console.log('execute() called, but call is in progress.');
          console.log(`There are now ${eventCounter} events since last call.`);
          return;
        }
        
        console.log(`Executing...  There have been ${eventCounter} events.`);
        eventCounter = 0;
        inProgress = true;
        fn()
        .then(() => {
          console.log('async function call completed!');
          inProgress = false;
          if (eventCounter > 0) {
            // make another call if there are pending events since the last call
            execute();
          }
        });
      }
      
      return execute;
    }
    
    let debouncer = createDebouncer(makeHttpCall);
    
    document.getElementById('buttonDoEvent').addEventListener('click', () => {
      debouncer();
    });
    <button id="buttonDoEvent">Do Event</button>
    Login or Signup to reply.
  3. Your question really comes down to:

    Given this code:

    var flag = false;
    
    function f() {
        if (!flag) {
            flag = true;
            console.log("hello");
        }
    }
    

    and considering that flag is not modified anywhere else, and many different, asynchronous events may call this function f…:

    Can "hello" be printed twice?

    The answer is no: if this runs on an ECMAScript compliant JS engine, then the call stack must be empty first before the next job is pulled from an event/job queue. Asynchronous tasks/reactions are pushed on an event queue. They don’t execute before the currently executing JavaScript has run to completion, i.e. up until the call stack is empty. So they never interrupt running JavaScript code pre-emptively.

    This is true even if these asynchronous tasks/events/jobs are scheduled by other threads, lower-level non-JS code,…etc. They all must wait their turn to be consumed by the JS engine. And this will happen one after the other.

    For more information, see the ECMAScript specification on "Job". For instance 8.4 Jobs and Host Operations to Enqueue Jobs:

    A Job is an abstract closure with no parameters that initiates an ECMAScript computation when no other ECMAScript computation is currently in progress.

    […]

    • Only one Job may be actively undergoing evaluation at any point in time.
    • Once evaluation of a Job starts, it must run to completion before evaluation of any other Job starts.

    For example, promises generate such jobs — See 25.6.1.3.2 Promise Resolve Functions:

    When a promise resolve function is called with argument resolution, the following steps are taken:

    • […]
    • Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
    Login or Signup to reply.
  4. Javascript is single-threaded. Therefore, unless you are deliberately using threads (eg. worker_threads in node.js) no function in the current thread can be executed by two parallel threads at the same time.

    This explains why javascript has no mutex or semaphore capability – because generally it is not needed (note: you can still have race conditions because asynchronous code may be executed in a sequence you did not expect).

    There is a general confusion that asynchronous code means parallel code execution (multi-threaded). It can but most of the time when a system is labeled as asynchronous or non-blocking or event-oriented INSTEAD of multi-threaded it often means that the system is single-threaded.

    In this case asynchronous means parallel WAIT. Not parallel code execution. Code is always executed sequentially – only, due to the ability of waiting in parallel you may not always know the sequence the code is executed in.

    There are parts of javascript that execute in a separate thread. Modern browsers execute each tab and iframe in its own thread (but each tab or iframe are themselves single-threaded). But script cannot cross tabs, windows or iframes. So this is a non-issue. Script may access objects inside iframes but this is done via an API and the script itself cannot execute in the foreign iframe.

    Node.js and some browsers also do DNS queries in a separate thread because there is no standardized cross-platform non-blocking API for DNS queries. But this is C code and not your javascript code. Your only interaction with this kind of multi-threading is when you pass a URL to fetch() or XMLHttpRequest().

    Node.js also implement file I/O, zip compression and cryptographic functions in separate threads but again this is C code, not your javascript code. All results from these separate threads are returned to you asynchronously via the event loop so by the time your javascript code process the result we are back to executing sequentially in the main thread.

    Finally both node.js and browsers have worker APIs (web workers for browsers and worker threads for node.js). However, both these API use message passing to transfer data (in node only a pointer is passed in the message thus the underlying memory is shared) and it still protects functions from having their variables overwritten by another thread.

    In your code, both myFunction() and redisPubSubEventHandler() run in the main thread. It works like this:

    1. myFunction() is called, it returns immediately when it encounters the await.

    2. a bunch of functions are declared and compiled.

    3. we reach the end of your script:

         // I want to ensure that this method is "synchronized" and is not called by flow1 and flow2 simultaneously!
       }
       <----- we reach here
      
    4. now that we have reached the end of script we enter the event loop…

    5. either the callDB or the redis event completes, our process gets woken up

    6. the event loop figures out which handler to call based on what event happened

    7. either the await returns and call renderMethod() or redisPubSubEventHandler() gets executed and call renderMethod()

    In either case both your renderMethod() calls will execute on the main thread. Thus it is impossible for renderMethod() to run in parallel.

    It is possible for renderMethod() to be half executed and another call to renderMethod() happens IF it contains the await keyword. This is because the first call is suspended at the await allowing the interpreter to call renderMethod() again before the first call completes. But note that even in this case you are only in trouble if you have an await between if.. and httpCallMade = true.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search