Search This Blog

2017/10/08

Event Loop overview

The Event Loop is a programming construct that waits for & dispatches events  in a program.
Event is usually defined as significant change in state of an object.

Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model.

Event Loop lies at the heart of Node.js that is responsible for making it event driven (asynchronous).

Event Loop overview:

In most of the modern operating systems, system kernels are multi-threaded, they are capable of handling multiple operations executing in the background.Node.js tries to capitalize on OS infrastructure.
Whenever a task arrives for execution with node.js it gets queued, task gets pulled for execution from this queue sequentially. Event loop checks if current task requires any Blocking IO Operations or may demands more time for complex computation tasks embedded in it.
 If it does not require blocking IO or complex computation then Event Loop will process all steps demanded in that task & serve response.
If task requires very complex computation or Blocking IO, Even Loop does not process it, but offloads the operations to the system kernel directly using asynchronous interfaces provided by operating system or by using internal thread pool that communicate with OS which are not providing such asynchronous API also add this offloaded task for listening for update of status (such change of state usually referred as an event) via event listeners.
 Rather than idle waiting for offloaded task to complete it moves on to new task in queue. Meanwhile when offloaded task finishes and Event Loop comes to know about it through listeners, now EventLoop starts executing this task from where it left after finishing tasks in hand it.
To understand EventLoop in more details let’s look at event loop phases.

Phases of Event Loop:                                                                                                                Event loop goes through its phases in cyclic manner. Each phase has own FIFO callback queue also called Event queue. Callbacks are event in themselves. When the event loop enters a given phase, it performs any operations specific to that phase, then execute callbacks in that phase's queue until the queue has been exhausted or the maximum number of callbacks has executed.

Event loop has following phases

             1)     timers: this phase executes callbacks scheduled by setTimeout() and setInterval().
2)     I/O callbacks: executes almost all callbacks with the exception of close callbacks, the ones scheduled by timers, and setImmediate().
3)     idle, prepare: only used internally.
4)     poll: retrieve new I/O events; node will block here when appropriate.
5)     checksetImmediate() callbacks are invoked here.
6)     close callbacks: e.g. socket.on(‘close’ ...).

Suppose EventLoop is inside poll phase & at the same time system kernel adds more callbacks into poll callback queue then poll phase has to run much longer than a timer's threshold

Event Loop Phases details:

I/O callbacks Phase: I/O callbacks executes callbacks for some system operations such as types of TCP errors e.g. ECONNREFUSED for *nix systems which requires wait to report the error.
Poll Phase: Poll phase Executes scripts for timers whose threshold has elapsed, then Processes events in the poll queue.
If there are no timers scheduled and poll queue is not empty event loop will iterate through its queue of callbacks executing them synchronously until either the queue has been exhausted, or the system-dependent hard limit is reached.
For empty poll queue If scripts have been scheduled by setImmediate(), the event loop will end the poll phase and continue to the check phase to execute those scheduled scripts rather than waiting.
 But If scripts have not been scheduled by setImmediate(), the event loop will wait for callbacks to be added to the queue, then execute them immediately.
Check phase: This phase allows a person to execute callbacks immediately after the poll phase has completed. Runs all callbacks registered via setImmediate().
close callbacks phase: If a socket or handle is closed abruptly (e.g. socket.destroy ()), the 'close' event will be emitted in this phase. Otherwise it will be emitted via process.nextTick().


Event loop's order of operations:



Event Loop is a single threaded and semi-infinite loop. The reason why this is called a semi-infinite loop is because this actually quits at some point when there is no more work to be done.
What is process.nextTick ()?

In Node.js, each iteration of an Event Loop is called a tick. process.nextTick() get processed after the current operation completes regardless of the current phase of the event loop.

Suppose if the event loop is in Timer phase and there were 5 callbacks in the timer queue already; and event loop is busy executing the third one. By that time if few process.nextTick() callbacks are pushed to nextTickQueue, the event loop will execute all of them synchronously after completing the current callback execution (which is 3rd one) and will resume the Timer callback execution again from the 4th callback.

Consider following code snippet

function callback(){
    console.log('wrapped inside nexttick');
  }
  process.nextTick(callback);
  console.log('task1');
  console.log('task2');


Output of this snippet is as follows

C:\Users\Sangram\Desktop>node test.js
task1
task2
wrapped inside nexttick
console.log associated with function callback() get called after task of printing task1 & task2 as task of printing task1 & task2.  

process.nextTick() is technically not a part of the event loop.

What is Libuv?

Node.js consumes the low-level non-blocking, asynchronous hardware I/O functionalities provided by Operating System implementations. epoll in Linux, kqueue in BSD systems (MacOS), event ports in Solaris, IOCP (Input Output Completion Port) in Windows. Not all the types of I/O can be performed using these implementations. Even on the same OS platform, there are complexities in supporting different types of I/O. Certain systems, such as Linux does not support complete asynchrony for file system access. And there are limitations in file system event notifications/signaling with kqueue in MacOS systems.

    NodeJS DNS functions such as dns.lookupaccesses system configuration files such as nsswitch.conf,resolv.conf and /etc/hosts , file system complexities described above are also applicable to dns.resolve function.

Therefore, a thread pool has been introduced to support I/O functions which cannot be directly addressed by hardware asynchronous I/O utils such as epoll/kqueue/event ports or IOCP. Now we know that not all the I/O functions happen in the thread pool.Some I/O get performed using native hardware implementations while preserving complete asynchrony, and there are certain I/O types which should be performed in the thread pool so that the asynchronous nature can be guaranteed.
To govern this entire process while supporting cross-platform I/O, there should be an abstraction layer which encapsulates these inter-platform and intra-platform complexities and expose a generalized API for the upper layers of Node. Libuv is that abstraction layer.
libuv is cross-platform support library which was originally written for NodeJS. It’s designed around the event-driven asynchronous I/O model.

The library provides much more than a simple abstraction over different I/O polling mechanisms: ‘handles’ and ‘streams’ provide a high level abstraction for sockets and other entities; cross-platform file I/O and threading functionality is also provided, amongst other things.

Below diagram summarizes libuv stack w.r.t. Operating system IO library .


.
Reference            

No comments:

Post a Comment