I have been personally reading the source code of node.js & v8.
I went into a similar problem like you when I tried to understand node.js architecture in order to write native modules.
What I am posting here is my understanding of node.js and this might be a bit off track as well.
Libev is the event loop which actually runs internally in node.js to perform simple event loop operations. It's written originally for *nix systems. Libev provides a simple yet optimized event loop for the process to run on. You can read more about libev here.
LibEio is a library to perform input output asynchronously. It handles file descriptors, data handlers, sockets etc. You can read more about it here here.
LibUv is an abstraction layer on the top of libeio , libev, c-ares ( for DNS ) and iocp (for windows asynchronous-io). LibUv performs, maintains and manages all the io and events in the event pool. ( in case of libeio threadpool ). You should check out Ryan Dahl's tutorial on libUv. That will start making more sense to you about how libUv works itself and then you will understand how node.js works on the top of libuv and v8.
To understand just the javascript event loop you should consider watching these videos
To see how libeio is used with node.js in order to create async modules you should see this example.
Basically what happens inside the node.js is that v8 loop runs and handles all javascript parts as well as C++ modules [ when they are running in a main thread ( as per official documentation node.js itself is single threaded) ]. When outside of the main thread, libev and libeio handle it in the thread pool and libev provide the interaction with the main loop. So from my understanding, node.js has 1 permanent event loop: that's the v8 event loop. To handle C++ async tasks it's using a threadpool [via libeio & libev ].
For example:
eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);
Which appears in all modules is usually calling the function Task
in the threadpool. When it's complete, it calls the AfterTask
function in the main thread. Whereas Eio_REQUEST
is the request handler which can be a structure / object whose motive is to provide communication between the threadpool and main thread.
Looks like some of the entities discussed (eg: libev etc.) have had lost relevance, due to the fact that it has been a while, but I think the question still has great potential.
Let me try to explain the working of event driven model with the help of an abstract example, in an abstract UNIX environment, in Node's context, as of today.
Program's perspective:
The event machinery above is called libuv AKA event loop framework. Node leverages this library to implement its event driven programming model.
Node's perspective:
While most of the functionalities are catered to in this manner, some (async versions) of the file operations are carried out with the help of additional threads, well integrated into the libuv. While network I/O operations can wait in expectation of an external event such as the other endpoint responding with data etc. the file operations need some work from node itself. For example, if you open a file and wait for the fd to be ready with data, it won't happen, as no one is reading actually! At the same time, if you read from the file inline in the main thread, it can potentially block other activities in the program, and can make visible problems, as file operations are very slow compared to cpu bound activities. So internal worker threads (configurable through UV_THREADPOOL_SIZE environment variable) are employed to operate on files, while the event driven abstraction works intact, from the program's perspective.
Hope this helps.
An Introduction to libuv
The node.js project began in 2009 as a JavaScript environment decoupled from the browser. Using Google’s V8 and Marc Lehmann’s libev, node.js combined a model of I/O – evented – with a language that was well suited to the style of programming; due to the way it had been shaped by browsers. As node.js grew in popularity, it was important to make it work on Windows, but libev ran only on Unix. The Windows equivalent of kernel event notification mechanisms like kqueue or (e)poll is IOCP. libuv was an abstraction around libev or IOCP depending on the platform, providing users an API based on libev. In the node-v0.9.0 version of libuv libev was removed.
Also one picture which describe the Event Loop in Node.js by @BusyRich
Update 05/09/2017
Per this doc Node.js event loop,
The following diagram shows a simplified overview of the event loop's order of operations.
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
note: each box will be referred to as a "phase" of the event loop.
Phases Overview
setTimeout()
and setInterval()
.setImmediate()
.setImmediate()
callbacks are invoked here.socket.on('close', ...)
.Between each run of the event loop, Node.js checks if it is waiting for any asynchronous I/O or timers and shuts down cleanly if there are not any.
There is one event loop in the NodeJs Architecture.
Node applications run in a single-threaded event-driven model. However, Node implements a thread pool in the background so that work can be performed.
Node.js adds work to an event queue and then has a single thread running an event loop pick it up. The event loop grabs the top item in the event queue, executes it, and then grabs the next item.
When executing code that is longer lived or has blocking I/O, instead of calling the function directly, it adds the function to the event queue along with a callback that will be executed after the function completes. When all events on the Node.js event queue have been executed, the Node.js application terminates.
The event loop starts to encouner problems when our application functions block on I/O.
Node.js uses event callbacks to avoid having to wait for blocking I/O. Therefore, any requests that perform blocking I/O are performed on a different thread in the background.
When an event that blocks I/O is retrieved from the event queue, Node.js retrieves a thread from the thread pool, and executes the function there instead of on the main event loop thread. This prevents the blocking I/O from holding up the rest of the events in the event queue.
There is only one event loop provided by libuv, V8 is just a JS runtime engine.
As a javascript beginner, I had also the same doubt, does NodeJS contains 2 event loops?. After a long research and discussing with one of V8 contributor, I got the following concepts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With