codecowboy
codecowboy

Reputation: 10095

Why does a while loop block the event loop?

The following example is given in a Node.js book:

var open = false;

setTimeout(function() {
  open = true
}, 1000)

while (!open) {
  console.log('wait');
}

console.log('open sesame');

Explaining why the while loop blocks execution, the author says:

Node will never execute the timeout callback because the event loop is stuck on this while loop started on line 7, never giving it a chance to process the timeout event!

However, the author doesn't explain why this happens in the context of the event loop or what is really going on under the hood.

Can someone elaborate on this? Why does node get stuck? And how would one change the above code, whilst retaining the while control structure so that the event loop is not blocked and the code will behave as one might reasonably expect; wait will be logged for only 1 second before the setTimeout fires and the process then exits after logging 'open sesame'.

Generic explanations such as the answers to this question about IO and event loops and callbacks do not really help me rationalise this. I'm hoping an answer which directly references the above code will help.

Upvotes: 33

Views: 25833

Answers (6)

jfriend00
jfriend00

Reputation: 707328

It's fairly simple really. Internally, node.js consists of this type of loop:

  • Get something from the event queue
  • Run whatever task is indicated and run it until it returns
  • When the above task is done, get the next item from the event queue
  • Run whatever task is indicated and run it until it returns
  • Rinse, lather, repeat - over and over

If at some point, there is nothing in the event queue, then go to sleep until something is placed in the event queue or until it's time for a timer to fire.


So, if a piece of Javascript is sitting in a while() loop, then that task is not finishing and per the above sequence, nothing new will be picked out of the event queue until that prior task is completely done. So, a very long or forever running while() loop just gums up the works. Because Javascript only runs one task at a time (single threaded for JS execution), if that one task is spinning in a while loop, then nothing else can ever execute.

Here's a simple example that might help explain it:

 var done = false;

 // set a timer for 1 second from now to set done to true
 setTimeout(function() {
      done = true;
 }, 1000);

 // spin wait for the done value to change
 while (!done) { /* do nothing */}

 console.log("finally, the done value changed!");

Some might logically think that the while loop will spin until the timer fires and then the timer will change the value of done to true and then the while loop will finish and the console.log() at the end will execute. That is NOT what will happen. This will actually be an infinite loop and the console.log() statement will never be executed.

The issue is that once you go into the spin wait in the while() loop, NO other Javascript can execute. So, the timer that wants to change the value of the done variable cannot execute. Thus, the while loop condition can never change and thus it is an infinite loop.

Here's what happens internally inside the JS engine:

  1. done variable initialized to false
  2. setTimeout() schedules a timer event for 1 second from now
  3. The while loop starts spinning
  4. 1 second into the while loop spinning, the timer is ready to fire, but it won't be able to actually do anything until the interpreter gets back to the event loop
  5. The while loop keeps spinning because the done variable never changes. Because it continues to spin, the JS engine never finishes this thread of execution and never gets to pull the next item from the event queue or run the pending timer.

node.js is an event driven environment. To solve this problem in a real world application, the done flag would get changed on some future event. So, rather than a spinning while loop, you would register an event handler for some relevant event in the future and do your work there. In the absolute worst case, you could set a recurring timer and "poll" to check the flag ever so often, but in nearly every single case, you can register an event handler for the actual event that will cause the done flag to change and do your work in that. Properly designed code that knows other code wants to know when something has changed may even offer its own event listener and its own notification events that one can register an interest in or even just a simple callback.

Upvotes: 52

Salah Ben Bouzid
Salah Ben Bouzid

Reputation: 411

var open = false;
const EventEmitter = require("events");
const eventEmitter = new EventEmitter();

setTimeout(function () {
  open = true;
  eventEmitter.emit("open_var_changed");
}, 1000);

let wait_interval = setInterval(() => {
console.log("waiting");
}, 100);

eventEmitter.on("open_var_changed", () => {
clearInterval(wait_interval);
console.log("open var changed to  ", open);
});

this exemple works and you can do setInterval and check if the open value changed inside it and it will work

Upvotes: 0

AlbertS
AlbertS

Reputation: 736

There is another solution. You can get access to event loop almost every cycle.

let done = false;

setTimeout(() => {
  done = true
}, 5);

const eventLoopQueue = () => {
  return new Promise(resolve => 
    setImmediate(() => {
      console.log('event loop');
      resolve();
    })
  );
}

const run = async () => {
  while (!done) {
    console.log('loop');
    await eventLoopQueue();
  }
}

run().then(() => console.log('Done'));

Upvotes: 4

Ziyi Wang
Ziyi Wang

Reputation: 73

because timer needs to comeback and is waiting loop to finish to add to the queue, so although the timeout is in a separate thread, and may indeed finsihed the timer, but the "task" to set done = true is waiting on that infinite loop to finish

Upvotes: 0

danday74
danday74

Reputation: 56986

This is a great question but I found a fix!

var sleep = require('system-sleep')
var done = false

setTimeout(function() {
  done = true
}, 1000)

while (!done) {
  sleep(100)
  console.log('sleeping')
}

console.log('finally, the done value changed!')

I think it works because system-sleep is not a spin wait.

Upvotes: 9

tsturzl
tsturzl

Reputation: 3137

Node is a single serial task. There is no parallelism, and its concurrency is IO bound. Think of it like this: Everything is running on a single thread, when you make an IO call that is blocking/synchronous your process halts until the data is returned; however say we have a single thread that instead of waiting on IO(reading disk, grabbing a url, etc) your task continues on to the next task, and after that task is complete it checks that IO. This is basically what node does, its an "event-loop" its polling IO for completion(or progress) on a loop. So when a task does not complete(your loop) the event loop does not progress. To put it simply.

Upvotes: 2

Related Questions