The overall concept

Libuv is a cross-platform, nodeJs-specific library designed around an event-driven asynchronous I/O model. Libuv provides more than simple abstract objects for different I/O polling mechanisms: Handle and Streams provide a high-level abstraction for Sockets and other instances. In addition, LiBUv provides cross-platform file I/O and threading capabilities

Here’s a diagram that illustrates the different parts that make up the Libuv and what subsystems they relate to:

Handles and request

Libuv provides users with two abstract objects to manipulate, a combination of EVnt loops: Handles and requests.

Handles can manipulate objects that have been active for a long time, such as:

  • The active handle is called one loop iteration during each event loop
  • The TCP service will call back to the connection service once a new connection is made

Requests represent operations that are active for a short time. These operations can be represented depending on the handle: write requests are used to write data on the handle. Or independent: the getAddrInfo request does not need to rely on the Handle and can be executed independently in the Event loop

I/O cycle

I/O or Event loop is the core of libuv. It creates content for all I/O operations, which means they are bound to a single thread. You can run multiple event loops as long as each thread is running in a different thread. However, libuv’s Event loop is not a safe thread

The Event Loop follows a single asynchronous I/O thread approach: all (network) I/O must be executed in an unblocked sockets, polling using the best mechanism available on a given platform (e.g. Epoll on Linux, KQueue on OSX and some other BSDs, SunOs event interfaces, and IOCP on Windows). As part of the loop iteration, the loop is blocked, waiting for I/O activity that has been added to the poller and callback sockets, which triggers the Sockets state (readable, writable hang), so that the handle can read, write, or perform the required I/O operations.

In order to better understand the operation of Event Loop, the following figure shows the state of all loop iteration

1. The “now” loop concept has been updated. The event loop caches the current time at the beginning of the event loop to reduce the number of time-dependent system calls.

2. If the loop is active, the loop iteration is started, otherwise the loop exits immediately. So when is an active cycle considered? If a loop has active and ref handles of its own, actively requesting or closing handles is considered active.

3. Because the timer is running. All the active time schedulers are scheduled to call their callbacks before the loop’s now concept

4. The mounted callback function will be called. All I/O callback functions are called after polling the event loop poll. However, there are some special cases: if a call to such a callback is deferred until the next iteration of the loop, then the delayed I/O callback from the previous event loop may be executed immediately.

5. The callback function of idle Handle is called. Although the name is not very nice, if idle Handle is active, then it must be called every time an event loop is performed

Call the prepare Handle callback function. Prepare Handles callback before I/O

7. The delay time is calculated in the poll phase. Before blocking I/O, loop calculates how long it should block. Here are the rules for calculating timeouts:

  • If the loop flag is UV_RUN_NOWAIT, the delay time is 0
  • If the loop is blocked by UV_STOP, the delay is 0
  • If no handles and requests are active at this point, the delay is 0
  • If there are any active IDEL handles, the delay is 0
  • If any pending handles are closed, the delay is 0
  • If none of the above is present, the most recent timeout is called, with an infinite delay if no active time timer exists

8. The I/O phase is blocked. At this point, the loop blocks the I/O for the duration calculated in the previous step. All I/ O-related handles that are monitoring a given file descriptor for a read or write operation will call their callback here.

9. Call the callback for check Handle. Check Handle is called after the I/O phase.

10. Call the CLose callback function. If a handes is closed by a call to uv_close(), then the close callback is called

11. When running in UV_RUN_ONCE mode, the I/O may not have a callback function to call after the I/O is blocked and there are timers that expire, thus calling the timers callback function

12. End of loop. If the loop is running in UV_RUN_NOWAIT or UV_RUN_ONCE mode, then the uv_run() method is returned. If the loop is running in UV_RUN_DEFAULT mode and the loop is still alive at the end of the loop, it will continue to run and iterate from the beginning, otherwise it will end

In each loop thread, Libuv uses thread pools for asynchronous file I/O operations, but network I/O is always performed in a single thread.

I/O file

Unlike network I/O, there is no platform-specific file I/O primitive libuv can rely on, so the current approach is to run blocking file I/O operations in the thread pool.

Libuv currently uses a global thread pool where all loops can be queued. Three operations are currently running in this pool:

File system operation DNS function (getAddrInfo and getNameInfo) The code specified by the user through uv_queue_work()