Why Does a While Loop Block the Event Loop

Why does a while loop block the event loop?

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.

How can we block event loop?

There are lots of ways to block the event loop. Some ways block it just for awhile (like using synchronous file I/O) and some ways block it forever.

For example, this blocks it forever:

let flag = false;
setTimeout(() => {
// this callback never gets called
// because event loop is blocked
flag = true;
}, 1000);

while (!flag) {
console.log("still waiting")
}
// never get here

The issue is that the while() loop runs until the flag changes value. As long as that while loop is running, the event loop is blocked. There's a setTimeout() that wants to fire in 1 second, but it can't actually call its callback until the interpreter gets back to the event loop. But, it won't get back to the event loop until the while() loop is done. It's a deadlock which results in an infinite loop and the event loop is permanently blocked.

The setTimeout() can't call its callback until the while loop is done and the while loop won't finish until the setTimeout() runs its callback. Deadlock, infinite loop.


This blocks it for awhile while all the file operations are going on and all the processing of the files:

setTimeout(() => {
// this doesn't get to run until all the synchronous file I/O
// finishes in the code below, even though the timer is set
// for only 10ms
console.log("finally got to run the timer callback");
}, 10);

let files = some array of files;
for (let file of files) {
let data = fs.readFileSync(file);
let lines = data.split("\n");
for (let line of lines) {
// do something
}
}

js while(true){} blocks event loop

You have to understand

How browser internally does setTimeout?

I will explain in brief.

To learn more about this

Here is more detail explanation of event loop by Philip Roberts
in jsconf2014

Philip Roberts: What the heck is the event loop anyway?

Also, to see this process in action there is a great tool take a look
at loupe

To understand that you have to know about event queue in javascript. There are event queues implemented in browser. Whenever an event get triggered in js, all of these events (like click etc.. ) are added to this queue. When your browser has nothing to execute it takes an event from queue and executes them one by one.

Now, when you call setTimeout or setInterval your callback get registered to an timer in browser and it gets added to the event queue after the given time expires and eventually javascript takes the event from the queue and executes it.

This happens so, because javascript execution is single threaded and they can execute only one thing at a time. So, they cannot execute other javascript and keep track of your timer. That is why these timers are registered with browser (browser are not single threaded) and it can keep track of timer and add an event in the queue after the timer expires.

same happens for setInterval only in this case the event is added to the queue again and again after the specified interval until it gets cleared or browser page refreshed.

Note

The delay parameter you pass to these functions is the minimum delay
time to execute the callback. This is because after the timer expires
the browser adds the event to the queue to be executed by the
javascript engine but the execution of the callback depends upon your
events position in the queue and as the engine is single threaded it
will execute all the events in the queue one by one.

Hence, your callback may sometime take more than the specified delay time to be called specially when your other code blocks the thread and not giving it time to process what's there in the queue.

And as I mentioned javascript is single thread. So, if you block the thread for long.

Like this code

while(true) { //infinite loop 
}

Your user may get a message saying page not responding.

And here, your setTimeout event will never execute.

How is the event loop never blocking but messages in the queue are run to completion?

You're right, the two citations contradict each other.

In the event loop, all messages are run-to-completion, as it is written in the first text, therefore they do block the event loop while they execute.

This is why timer2 won't execute before the loop in timer1 finishes in this example:

console.log('start');

setTimeout(() => {
const startTime = Date.now()
while(Date.now() - startTime < 3000);
console.log('timer1 end');
}, 0)
setTimeout(() => console.log('timer2'), 0);

/*
start
-- three seconds later --
timer1 end
timer2
*/

Why does nodejs stop execution of while true loop inside async function

You haven't mistaken how NodeJS works. Your code just has a bug: resolve never gets called.

If you change it to the following, "hello" prints forever at 1 second intervals:

(async () => {
while (true) {
await new Promise(resolve => setTimeout(() => {
console.log("hello")
resolve();
}, 1000))
}
})();

The reason your code would still end is because, in NodeJS, the resolve function falls out of scope, indicating to the V8 JS engine that the Promise can never resolve. Therefore it ends the async () => {...}, which in turn quits since it's the last function still running.

What is the difference between the event loop in JavaScript and async non-blocking I/O in Node.js?

The event loop is the mechanism. Asynchronous I/O is the goal.

Asynchronous I/O is a style of programming in which I/O calls do not wait for the operation to complete before returning, but merely arrange for the caller to be notified when that happens, and for the result to be returned somewhere. In JavaScript, the notification is usually performed by invoking a callback or resolving a promise. As far as the programmer is concerned, it doesn’t matter how this happens: it just does. I request the operation, and when it’s done, I get notified about it.

An event loop is how this is usually achieved. The thing is, in most JavaScript implementations, there is literally a loop somewhere that ultimately boils down to:

while (poll_event(&ev)) {
dispatch_event(&ev);
}

Performing an asynchronous operation is then done by arranging for the completion of the operation to be received as an event by that loop, and having it dispatch to a callback of the caller’s choice.

There are ways to achieve asynchronous programming not based on an event loop, for example using threads and condition variables. But historical reasons make this programming style quite difficult to realise in JavaScript. So in practice, the predominant implementation of asynchrony in JavaScript is based on dispatching callbacks from a global event loop.

Put another way, ‘the event loop’ describes what the host does, while ‘asynchronous I/O’ describes what the programmer does.

From a non-programmer’s bird’s eye view this may seem like splitting hairs, but the distinction can be occasionally important.



Related Topics



Leave a reply



Submit