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)
check: setImmediate() 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.
.
Reference
No comments:
Post a Comment