# Processes

libuv offers considerable child process management, abstracting the platform differences and allowing communication with the child process using streams or named pipes.

A common idiom in Unix is for every process to do one thing and do it well. In such a case, a process often uses multiple child processes to achieve tasks (similar to using pipes in shells). A multi-process model with messages may also be easier to reason about compared to one with threads and shared memory.

A common refrain against event-based programs is that they cannot take advantage of multiple cores in modern computers. In a multi-threaded program the kernel can perform scheduling and assign different threads to different cores, improving performance. But an event loop has only one thread. The workaround can be to launch multiple processes instead, with each process running an event loop, and each process getting assigned to a separate CPU core.

## Spawning child processes

The simplest case is when you simply want to launch a process and know when it exits. This is achieved using `uv_spawn`.

spawn/main.c

     1uv_loop_t *loop;
     2uv_process_t child_req;
     3uv_process_options_t options;
     4int main() {
     5    loop = uv_default_loop();
     6
     7    char* args[3];
     8    args[0] = "mkdir";
     9    args[1] = "test-dir";
    10    args[2] = NULL;
    11
    12    options.exit_cb = on_exit;
    13    options.file = "mkdir";
    14    options.args = args;
    15
    16    int r;
    17    if ((r = uv_spawn(loop, &child_req, &options))) {
    18        fprintf(stderr, "%s\n", uv_strerror(r));
    19        return 1;
    20    } else {
    21        fprintf(stderr, "Launched process with ID %d\n", child_req.pid);
    22    }
    23
    24    return uv_run(loop, UV_RUN_DEFAULT);
    25}

Note

`options` is implicitly initialized with zeros since it is a global variable. If you change `options` to a local variable, remember to initialize it to null out all unused fields:

    uv_process_options_t options = {0};

The `uv_process_t` struct only acts as the handle, all options are set via `uv_process_options_t`. To simply launch a process, you need to set only the `file` and `args` fields. `file` is the program to execute. Since `uv_spawn` uses execvp(3) internally, there is no need to supply the full path. Finally as per underlying conventions, the arguments array has to be one larger than the number of arguments, with the last element being NULL.

After the call to `uv_spawn`, `uv_process_t.pid` will contain the process ID of the child process.

The exit callback will be invoked with the exit status and the type of signal which caused the exit.

Note that it is important not to call `uv_close` before the exit callback.

spawn/main.c

    1
    2void on_exit(uv_process_t *req, int64_t exit_status, int term_signal) {
    3    fprintf(stderr, "Process exited with status %" PRId64 ", signal %d\n", exit_status, term_signal);
    4    uv_close((uv_handle_t*) req, NULL);

It is required to close the process watcher after the process exits.

## Changing process parameters

Before the child process is launched you can control the execution environment using fields in `uv_process_options_t`.

### Change execution directory

Set `uv_process_options_t.cwd` to the corresponding directory.

### Set environment variables

`uv_process_options_t.env` is a null-terminated array of strings, each of the form `VAR=VALUE` used to set up the environment variables for the process. Set this to `NULL` to inherit the environment from the parent (this) process.

### Option flags

Setting `uv_process_options_t.flags` to a bitwise OR of the following flags, modifies the child process behaviour:

- `UV_PROCESS_SETUID` - sets the child’s execution user ID to `uv_process_options_t.uid`.

- `UV_PROCESS_SETGID` - sets the child’s execution group ID to `uv_process_options_t.gid`.

Changing the UID/GID is only supported on Unix, `uv_spawn` will fail on Windows with `UV_ENOTSUP`.

- `UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS` - No quoting or escaping of `uv_process_options_t.args` is done on Windows. Ignored on Unix.

- `UV_PROCESS_DETACHED` - Starts the child process in a new session, which will keep running after the parent process exits. See example below.

## Detaching processes

Passing the flag `UV_PROCESS_DETACHED` can be used to launch daemons, or child processes which are independent of the parent so that the parent exiting does not affect it.

detach/main.c

     1int main() {
     2    loop = uv_default_loop();
     3
     4    char* args[3];
     5    args[0] = "sleep";
     6    args[1] = "100";
     7    args[2] = NULL;
     8
     9    options.exit_cb = NULL;
    10    options.file = "sleep";
    11    options.args = args;
    12    options.flags = UV_PROCESS_DETACHED;
    13
    14    int r;
    15    if ((r = uv_spawn(loop, &child_req, &options))) {
    16        fprintf(stderr, "%s\n", uv_strerror(r));
    17        return 1;
    18    }
    19    fprintf(stderr, "Launched sleep with PID %d\n", child_req.pid);
    20    uv_unref((uv_handle_t*) &child_req);
    21
    22    return uv_run(loop, UV_RUN_DEFAULT);

Just remember that the handle is still monitoring the child, so your program won’t exit. Use `uv_unref()` if you want to be more fire-and-forget.

## Sending signals to processes

libuv wraps the standard `kill(2)` system call on Unix and implements one with similar semantics on Windows, with one caveat: all of `SIGTERM`, `SIGINT` and `SIGKILL`, lead to termination of the process. The signature of `uv_kill` is:

    uv_err_t uv_kill(int pid, int signum);

For processes started using libuv, you may use `uv_process_kill` instead, which accepts the `uv_process_t` watcher as the first argument, rather than the pid. In this case, remember to call `uv_close` on the watcher _after_ the exit callback has been called.

## Signals

libuv provides wrappers around Unix signals with some Windows support as well.

Use `uv_signal_init()` to initialize a handle and associate it with a loop. To listen for particular signals on that handler, use `uv_signal_start()` with the handler function. Each handler can only be associated with one signal number, with subsequent calls to `uv_signal_start()` overwriting earlier associations. Use `uv_signal_stop()` to stop watching. Here is a small example demonstrating the various possibilities:

signal/main.c

     1#include <stdio.h>
     2#include <stdlib.h>
     3#include <unistd.h>
     4#include <uv.h>
     5
     6uv_loop_t* create_loop()
     7{
     8    uv_loop_t *loop = malloc(sizeof(uv_loop_t));
     9    if (loop) {
    10      uv_loop_init(loop);
    11    }
    12    return loop;
    13}
    14
    15void signal_handler(uv_signal_t *handle, int signum)
    16{
    17    printf("Signal received: %d\n", signum);
    18    uv_signal_stop(handle);
    19}
    20
    21// two signal handlers in one loop
    22void thread1_worker(void *userp)
    23{
    24    uv_loop_t *loop1 = create_loop();
    25
    26    uv_signal_t sig1a, sig1b;
    27    uv_signal_init(loop1, &sig1a);
    28    uv_signal_start(&sig1a, signal_handler, SIGUSR1);
    29
    30    uv_signal_init(loop1, &sig1b);
    31    uv_signal_start(&sig1b, signal_handler, SIGUSR1);
    32
    33    uv_run(loop1, UV_RUN_DEFAULT);
    34}
    35
    36// two signal handlers, each in its own loop
    37void thread2_worker(void *userp)
    38{
    39    uv_loop_t *loop2 = create_loop();
    40    uv_loop_t *loop3 = create_loop();
    41
    42    uv_signal_t sig2;
    43    uv_signal_init(loop2, &sig2);
    44    uv_signal_start(&sig2, signal_handler, SIGUSR1);
    45
    46    uv_signal_t sig3;
    47    uv_signal_init(loop3, &sig3);
    48    uv_signal_start(&sig3, signal_handler, SIGUSR1);
    49
    50    while (uv_run(loop2, UV_RUN_NOWAIT) || uv_run(loop3, UV_RUN_NOWAIT)) {
    51    }
    52}
    53
    54int main()
    55{
    56    printf("PID %d\n", getpid());
    57
    58    uv_thread_t thread1, thread2;
    59
    60    uv_thread_create(&thread1, thread1_worker, 0);
    61    uv_thread_create(&thread2, thread2_worker, 0);
    62
    63    uv_thread_join(&thread1);
    64    uv_thread_join(&thread2);
    65    return 0;
    66}

Note

`uv_run(loop, UV_RUN_NOWAIT)` is similar to `uv_run(loop, UV_RUN_ONCE)` in that it will process only one event. UV_RUN_ONCE blocks if there are no pending events, while UV_RUN_NOWAIT will return immediately. We use NOWAIT so that one of the loops isn’t starved because the other one has no pending activity.

Send `SIGUSR1` to the process, and you’ll find the handler being invoked 4 times, one for each `uv_signal_t`. The handler just stops each handle, so that the program exits. This sort of dispatch to all handlers is very useful. A server using multiple event loops could ensure that all data was safely saved before termination, simply by every loop adding a watcher for `SIGINT`.

## Child Process I/O

A normal, newly spawned process has its own set of file descriptors, with 0, 1 and 2 being `stdin`, `stdout` and `stderr` respectively. Sometimes you may want to share file descriptors with the child. For example, perhaps your applications launches a sub-command and you want any errors to go in the log file, but ignore `stdout`. For this you’d like to have `stderr` of the child be the same as the stderr of the parent. In this case, libuv supports inheriting file descriptors. In this sample, we invoke the test program, which is:

proc-streams/test.c

    #include <stdio.h>

    int main()
    {
        fprintf(stderr, "This is stderr\n");
        printf("This is stdout\n");
        return 0;
    }

The actual program `proc-streams` runs this while sharing only `stderr`. The file descriptors of the child process are set using the `stdio` field in `uv_process_options_t`. First set the `stdio_count` field to the number of file descriptors being set. `uv_process_options_t.stdio` is an array of `uv_stdio_container_t`, which is:

    typedef struct uv_stdio_container_s {
        uv_stdio_flags flags;

        union {
            uv_stream_t* stream;
            int fd;
        } data;
    } uv_stdio_container_t;

where flags can have several values. Use `UV_IGNORE` if it isn’t going to be used. If the first three `stdio` fields are marked as `UV_IGNORE` they’ll redirect to `/dev/null`.

Since we want to pass on an existing descriptor, we’ll use `UV_INHERIT_FD`. Then we set the `fd` to `stderr`.

proc-streams/main.c

     1
     2int main() {
     3    loop = uv_default_loop();
     4
     5    /* ... */
     6
     7    options.stdio_count = 3;
     8    uv_stdio_container_t child_stdio[3];
     9    child_stdio[0].flags = UV_IGNORE;
    10    child_stdio[1].flags = UV_IGNORE;
    11    child_stdio[2].flags = UV_INHERIT_FD;
    12    child_stdio[2].data.fd = 2;
    13    options.stdio = child_stdio;
    14
    15    options.exit_cb = on_exit;
    16    options.file = args[0];
    17    options.args = args;
    18
    19    int r;
    20    if ((r = uv_spawn(loop, &child_req, &options))) {
    21        fprintf(stderr, "%s\n", uv_strerror(r));
    22        return 1;
    23    }
    24
    25    return uv_run(loop, UV_RUN_DEFAULT);
    26}

If you run `proc-stream` you’ll see that only the line “This is stderr” will be displayed. Try marking `stdout` as being inherited and see the output.

It is dead simple to apply this redirection to streams. By setting `flags` to `UV_INHERIT_STREAM` and setting `data.stream` to the stream in the parent process, the child process can treat that stream as standard I/O. This can be used to implement something like CGI.

A sample CGI script/executable is:

cgi/tick.c

    #include <stdio.h>
    #include <unistd.h>

    int main() {
        int i;
        for (i = 0; i < 10; i++) {
            printf("tick\n");
            fflush(stdout);
            sleep(1);
        }
        printf("BOOM!\n");
        return 0;
    }

The CGI server combines the concepts from this chapter and Networking so that every client is sent ten ticks after which that connection is closed.

cgi/main.c

     1
     2void on_new_connection(uv_stream_t *server, int status) {
     3    if (status == -1) {
     4        // error!
     5        return;
     6    }
     7
     8    uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
     9    uv_tcp_init(loop, client);
    10    if (uv_accept(server, (uv_stream_t*) client) == 0) {
    11        invoke_cgi_script(client);
    12    }
    13    else {
    14        uv_close((uv_handle_t*) client, NULL);
    15    }

Here we simply accept the TCP connection and pass on the socket (stream) to `invoke_cgi_script`.

cgi/main.c

     1
     2    args[1] = NULL;
     3
     4    /* ... finding the executable path and setting up arguments ... */
     5
     6    options.stdio_count = 3;
     7    uv_stdio_container_t child_stdio[3];
     8    child_stdio[0].flags = UV_IGNORE;
     9    child_stdio[1].flags = UV_INHERIT_STREAM;
    10    child_stdio[1].data.stream = (uv_stream_t*) client;
    11    child_stdio[2].flags = UV_IGNORE;
    12    options.stdio = child_stdio;
    13
    14    options.exit_cb = cleanup_handles;
    15    options.file = args[0];
    16    options.args = args;
    17
    18    // Set this so we can close the socket after the child process exits.
    19    child_req.data = (void*) client;
    20    int r;
    21    if ((r = uv_spawn(loop, &child_req, &options))) {
    22        fprintf(stderr, "%s\n", uv_strerror(r));

The `stdout` of the CGI script is set to the socket so that whatever our tick script prints, gets sent to the client. By using processes, we can offload the read/write buffering to the operating system, so in terms of convenience this is great. Just be warned that creating processes is a costly task.

## Parent-child IPC

A parent and child can have one or two way communication over a pipe created by settings `uv_stdio_container_t.flags` to a bit-wise combination of `UV_CREATE_PIPE` and `UV_READABLE_PIPE` or `UV_WRITABLE_PIPE`. The read/write flag is from the perspective of the child process. In this case, the `uv_stream_t* stream` field must be set to point to an initialized, unopened `uv_pipe_t` instance.

### New stdio Pipes

The `uv_pipe_t` structure represents more than just pipe(7) (or `|`), but supports any streaming file-like objects. On Windows, the only object of that description is the Named Pipe. On Unix, this could be any of Unix Domain Socket, or derived from mkfifo(1), or it could actually be a pipe(7). When `uv_spawn` initializes a `uv_pipe_t` due to the UV_CREATE_PIPE flag, it opts for creating a socketpair(2).

This is intended for the purpose of allowing multiple libuv processes to communicate with IPC. This is discussed below.

### Arbitrary process IPC

Since domain sockets [1] can have a well known name and a location in the file-system they can be used for IPC between unrelated processes. The D-BUS system used by open source desktop environments uses domain sockets for event notification. Various applications can then react when a contact comes online or new hardware is detected. The MySQL server also runs a domain socket on which clients can interact with it.

When using domain sockets, a client-server pattern is usually followed with the creator/owner of the socket acting as the server. After the initial setup, messaging is no different from TCP, so we’ll re-use the echo server example.

pipe-echo-server/main.c

     1void remove_sock(int sig) {
     2    uv_fs_t req;
     3    uv_fs_unlink(loop, &req, PIPENAME, NULL);
     4    exit(0);
     5}
     6
     7int main() {
     8    loop = uv_default_loop();
     9
    10    uv_pipe_t server;
    11    uv_pipe_init(loop, &server, 0);
    12
    13    signal(SIGINT, remove_sock);
    14
    15    int r;
    16    if ((r = uv_pipe_bind(&server, PIPENAME))) {
    17        fprintf(stderr, "Bind error %s\n", uv_err_name(r));
    18        return 1;
    19    }
    20    if ((r = uv_listen((uv_stream_t*) &server, 128, on_new_connection))) {
    21        fprintf(stderr, "Listen error %s\n", uv_err_name(r));
    22        return 2;
    23    }
    24    return uv_run(loop, UV_RUN_DEFAULT);
    25}

We name the socket `echo.sock` which means it will be created in the local directory. This socket now behaves no different from TCP sockets as far as the stream API is concerned. You can test this server using socat:

    $ socat - /path/to/socket

A client which wants to connect to a domain socket will use:

    void uv_pipe_connect(uv_connect_t *req, uv_pipe_t *handle, const char *name, uv_connect_cb cb);

where `name` will be `echo.sock` or similar. On Unix systems, `name` must point to a valid file (e.g. `/tmp/echo.sock`). On Windows, `name` follows a `\\?\pipe\echo.sock` format.

### Sending file descriptors over pipes

The cool thing about domain sockets is that file descriptors can be exchanged between processes by sending them over a domain socket. This allows processes to hand off their I/O to other processes. Applications include load-balancing servers, worker processes and other ways to make optimum use of CPU. libuv only supports sending TCP sockets or other pipes over pipes for now.

To demonstrate, we will look at an echo server implementation that hands off clients to worker processes in a round-robin fashion. This program is a bit involved, and while only snippets are included in the book, it is recommended to read the full code to really understand it.

The worker process is quite simple, since the file-descriptor is handed over to it by the master.

multi-echo-server/worker.c

     1
     2uv_loop_t *loop;
     3uv_pipe_t queue;
     4int main() {
     5    loop = uv_default_loop();
     6
     7    uv_pipe_init(loop, &queue, 1 /* ipc */);
     8    uv_pipe_open(&queue, 0);
     9    uv_read_start((uv_stream_t*)&queue, alloc_buffer, on_new_connection);
    10    return uv_run(loop, UV_RUN_DEFAULT);
    11}

`queue` is the pipe connected to the master process on the other end, along which new file descriptors get sent. It is important to set the `ipc` argument of `uv_pipe_init` to 1 to indicate this pipe will be used for inter-process communication! Since the master will write the file handle to the standard input of the worker, we connect the pipe to `stdin` using `uv_pipe_open`.

multi-echo-server/worker.c

     1void on_new_connection(uv_stream_t *q, ssize_t nread, const uv_buf_t *buf) {
     2    if (nread < 0) {
     3        if (nread != UV_EOF)
     4            fprintf(stderr, "Read error %s\n", uv_err_name(nread));
     5        uv_close((uv_handle_t*) q, NULL);
     6        return;
     7    }
     8
     9    uv_pipe_t *pipe = (uv_pipe_t*) q;
    10    if (!uv_pipe_pending_count(pipe)) {
    11        fprintf(stderr, "No pending count\n");
    12        return;
    13    }
    14
    15    uv_handle_type pending = uv_pipe_pending_type(pipe);
    16    assert(pending == UV_TCP);
    17
    18    uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
    19    uv_tcp_init(loop, client);
    20    if (uv_accept(q, (uv_stream_t*) client) == 0) {
    21        uv_os_fd_t fd;
    22        uv_fileno((const uv_handle_t*) client, &fd);
    23        fprintf(stderr, "Worker %d: Accepted fd %d\n", getpid(), fd);
    24        uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);
    25    }
    26    else {
    27        uv_close((uv_handle_t*) client, NULL);
    28    }
    29}

First we call `uv_pipe_pending_count()` to ensure that a handle is available to read out. If your program could deal with different types of handles, `uv_pipe_pending_type()` can be used to determine the type. Although `accept` seems odd in this code, it actually makes sense. What `accept` traditionally does is get a file descriptor (the client) from another file descriptor (The listening socket). Which is exactly what we do here. Fetch the file descriptor (`client`) from `queue`. From this point the worker does standard echo server stuff.

Turning now to the master, let’s take a look at how the workers are launched to allow load balancing.

multi-echo-server/main.c

    1struct child_worker {
    2    uv_process_t req;
    3    uv_process_options_t options;
    4    uv_pipe_t pipe;
    5} *workers;

The `child_worker` structure wraps the process, and the pipe between the master and the individual process.

multi-echo-server/main.c

     1void setup_workers() {
     2    round_robin_counter = 0;
     3
     4    // ...
     5
     6    // launch same number of workers as number of CPUs
     7    uv_cpu_info_t *info;
     8    int cpu_count;
     9    uv_cpu_info(&info, &cpu_count);
    10    uv_free_cpu_info(info, cpu_count);
    11
    12    child_worker_count = cpu_count;
    13
    14    workers = calloc(cpu_count, sizeof(struct child_worker));
    15    while (cpu_count--) {
    16        struct child_worker *worker = &workers[cpu_count];
    17        uv_pipe_init(loop, &worker->pipe, 1);
    18
    19        uv_stdio_container_t child_stdio[3];
    20        child_stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
    21        child_stdio[0].data.stream = (uv_stream_t*) &worker->pipe;
    22        child_stdio[1].flags = UV_IGNORE;
    23        child_stdio[2].flags = UV_INHERIT_FD;
    24        child_stdio[2].data.fd = 2;
    25
    26        worker->options.stdio = child_stdio;
    27        worker->options.stdio_count = 3;
    28
    29        worker->options.exit_cb = close_process_handle;
    30        worker->options.file = args[0];
    31        worker->options.args = args;
    32
    33        uv_spawn(loop, &worker->req, &worker->options);
    34        fprintf(stderr, "Started worker %d\n", worker->req.pid);
    35    }
    36}

In setting up the workers, we use the nifty libuv function `uv_cpu_info` to get the number of CPUs so we can launch an equal number of workers. Again it is important to initialize the pipe acting as the IPC channel with the third argument as 1. We then indicate that the child process’ `stdin` is to be a readable pipe (from the point of view of the child). Everything is straightforward till here. The workers are launched and waiting for file descriptors to be written to their standard input.

It is in `on_new_connection` (the TCP infrastructure is initialized in `main()`), that we accept the client socket and pass it along to the next worker in the round-robin.

multi-echo-server/main.c

     1void on_new_connection(uv_stream_t *server, int status) {
     2    if (status == -1) {
     3        // error!
     4        return;
     5    }
     6
     7    uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
     8    uv_tcp_init(loop, client);
     9    if (uv_accept(server, (uv_stream_t*) client) == 0) {
    10        uv_write_t *write_req = (uv_write_t*) malloc(sizeof(uv_write_t));
    11        dummy_buf = uv_buf_init("a", 1);
    12        struct child_worker *worker = &workers[round_robin_counter];
    13        uv_write2(write_req, (uv_stream_t*) &worker->pipe, &dummy_buf, 1, (uv_stream_t*) client, NULL);
    14        round_robin_counter = (round_robin_counter + 1) % child_worker_count;
    15    }
    16    else {
    17        uv_close((uv_handle_t*) client, NULL);
    18    }
    19}

The `uv_write2` call handles all the abstraction and it is simply a matter of passing in the handle (`client`) as the right argument. With this our multi-process echo server is operational.

Thanks to Kyle for pointing out that `uv_write2()` requires a non-empty buffer even when sending handles.

[1]

In this section domain sockets stands in for named pipes on Windows as well.