Non Blocking I/O using libuv

shashank Jain
4 min readSep 2, 2018

--

In one of the previous blogs we discussed the non blocking i/o mechanisms in linux called epoll (https://medium.com/@jain.sm/magical-epoll-60e10ca48c96)

The non blocking apis differ from OS to OS depending on the I/O multiplexer on the specific environment. In case of Linux its epoll , in Solaris its kqueue and in Windows IOCP (IO Completion ports)

In today’s blog we talk of an abstraction over the OS specific non blocking I/O mechanisms and its known as libuv. Libuv is the main driver for the event loop mechanism in Nodejs. Apart from that libuv is being used with Rust, Python(pyuv).

Libuv provides the following features:

  • Event loop
  • Timers
  • TCP/UDP sockets
  • Named pipes
  • Filesystem operations
  • Signal handling
  • Child processes
  • TTY
  • Threading utilities

At a high level this is the architecture of libuv.

As we can see the libuv library is abstracting out the different async i/o mechanisms like epoll, kqueue and IOCP. So we as programmers don’t have to deal with semantics of individual underlying mechanisms.

There are basically 3 major abstractions

  1. Event loop — The workhorse of libuv which is responsible for orchestrating callbacks based on different i/o events. Basically the loop blocks only when there are no events and is woken up as and when events arise like a new connection event or say data arrived on a file descriptor.

2. Handles — Example being a TCP Server handle which gets called back on a new connection event.

3. Requests — Requests are short lived operations performed on handles, as an example a read request as and when data is available on the underlying file descriptor.

The event loop is a single threaded mechanism and it iterates over different queues to look for callbacks which are available for execution. Some of the queues for callbacks are

1. Timers

2. Callbacks like Promises (Native). This is specific to Nodejs and not specific to libuv.

3. I/O

The timers basically represent the callbacks which are due to expire. So in every iteration of the event loop the Timers queue is the first one to be drained and callbacks executed.

The i/o queue has the callbacks which represent the reads and writes fd’s ready for i/o operation. If none if available the call blocks for a period of poll timeout. We can correlate it to the epoll_wait which we discussed in the epoll blog ().

Libuv also supports a thread pool to support calls which are blocking in nature. All blocking calls are taken off the event loop and processed by a separate thread pool.

libuv types

1. Uv_loop_t — this type represents the main event loop. This is generally a single instance but if we want to run concurrent event loops, multiple instances of this type have to be created.

2. Uv_handle_t — This type represents a resource like timer, tcp. They generally invoke callbacks as and when the event happens on that resource.

3. Uv_request_t — This type represents the operations on the handles, as an example uv_tcp_t represents a handle to a socket where in uv_tcp_send_t represents a request on the handle.

We go over a very simple program which starts a tcp server and listens for new connections.

The basic workflow is this

1. Initialize a default loop (uv_default_loop)

2. Initialize the tcp server (uv_tcp_init). The type of server is uv_tcp_t

3. Bind to the interface using uv_tcp_bind

4. Start listening via uv_listen . Pass the callback object conn_cb to this. As and when a new connection arrives (connection being a fd in linux), the callback is invoked.

5. Start the loop using uv_run

6. Once a connection is received, the conn_cb is invoked

a. A uv_tcp_t handle is created for the connection

b. The uv_tcp_init is called to register this with the event loop

c. Uv_read_start is called to register now the read callback

7. Once data arrives the read callback is invoked

Will now compile the program

gcc -o server server.c -luv

From a different window do a netcat

We can see the new connection getting called in the conn_cb callback method

Now we push some data on the connection

This example shows how a simple tcp based server based on libuv can be created in C.

In summary in this blog we tried to understand the underlying guts of the non-blocking i/o mechanism prevalent in systems like nodejs called libuv. To explore more details one can go over the libuv documentation (https://github.com/libuv/libuv)

Disclaimer : The views expressed above are personal and not of the company I work for.

--

--