Search code examples
node.jsqueuetask-queue

nodejs: what's the correct way to monitor a task queue?


  1. I know I can write a while (true) loop to monitor the queue, but it will cause the CPU 100% problem.
  2. I can sleep some seconds inside the while (true) loop, but it's NOT efficient.
  3. In C language, I can wait for a semaphore inside the while (true) loop. When a task added into the queue, release the semaphore so that the while (true) loop can do its job. After the queue is empty, it can set the semaphore, and wait for it.

Is there similar way to do this in Nodejs?


Solution

  • Imagine we have this taskQueue:

    // Tasks will be added to the array randomly
    const tasks = [];
    

    Note: the taskQueue above is something completely different than the internal NodeJS micro/macro task queue, that I'm referring to throughout this post.

    A way of constantly monitoring this array would be to schedule a 'micro-task' or 'macro-task' that parses the array.

    As an example:

    function handleTasks() {
       if (tasks.length) {
           // Alternatively loop and pop all the current tasks in queue
           const task = tasks.pop();
           // Do something with the task
       }
    
       setImmediate(handleTasks)
    }
    
    setImmediate(handleTasks)
    

    The setImmediate function will add a task to the internal macro-task queue.

    The JS micro- and macro-tasks do not block the main thread and will only be executed when the event-loop picks it off the internal micro/macro task queue.

    In NodeJS there are 4 ways of scheduling a function in a non-blocking way. Which way you pick is based on how much priority you'd want to give to the function.

    Ordered by highest priority first the ways to do this are:

    1. process.nextTick(handleTask)
    2. new Promise((resolve) => { resolve() }).then(handleTask)
    3. setImmediate(handleTask) / setTimeout(handleTask, 0)
    4. setTimeout(handleTask, 1) # Every timeout value bigger than 0

    Be aware that executing this function with the highest priority recursively could slow down the rest of your code.

    Depending on how important clearing this taskQueue is, I'd generally suggest to use setTimeout with a reasonable value (as high as you can afford) to prevent affecting performance of your application. (Same goes for any other function that schedules itself on the micro/macro task queue.)

    Questions

    I know I can write a while (true) loop to monitor the queue, but it will cause the CPU 100% problem.

    In JavaScript the functions cannot be preempted, meaning that their execution cannot be halted somewhere in the middle.

    The consequence is that once a function start, it will have to finish before another line of code (somewhere else) can be executed.

    Therefore an infinite while-loop will not work.

    I can sleep some seconds inside the while (true) loop, but it's NOT efficient.

    while(true) {
       await timeout(1000);
       // Do sth
    }
    

    Is actually syntactic sugar for

    timeout(1000).then(() => {
      // Do sth
      timeout(1000).then(() => {
        // Do sth
        // ...etc
      })
    })
    

    Using await inside a loop is considered a bad-practice, but could work since it just schedules each next iteration on the micro-task queue.

    In C language, I can wait for a semaphore inside the while (true) loop. When a task added into the queue, release the semaphore so that the while (true) loop can do its job. After the queue is empty, it can set the semaphore, and wait for it.

    There is no such thing as a semaphore in JS. Something that might achieve a similar effect could be a callback function.

    Example:

    function heavyLoadTask() {
      // Do sth
      resumeExecution = () => {
         // What to do when execution is resumed
      }
    }
    
    
    // Somewhere else the execution could be resumed like this;
    if (typeof resumeExecution === "function"){
      resumeExecution();
    }
    

    Recommended reading

    https://javascript.info/event-loop

    https://nodejs.dev/learn/understanding-process-nexttick

    https://nodejs.dev/learn/understanding-setimmediate