Register an eventfd

Going into the details of the eventfd(2) system call is out-of-scope. You might want to check the eventfd(2) man page for the description on that system call. eventfd(2) is a Linux-specific synchronization mechanism.

io_uring is capable of posting events on an eventfd instance whenever completions occur. This capability enables processes that are multiplexing I/O using poll(2) or epoll(7) to add a io_uring registered eventfd instance file descriptor to the interest list so that poll(2) or epoll(7) can notify them when a completion via io_uring occurred. This allows such programs to be busy processing their existing event loops rather than being blocked in a call to io_uring_wait_cqe().

#include <sys/eventfd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <liburing.h>
#include <fcntl.h>

#define BUFF_SZ   512

char buff[BUFF_SZ + 1];
struct io_uring ring;

void error_exit(char *message) {
    perror(message);
    exit(EXIT_FAILURE);
}

void *listener_thread(void *data) {
    struct io_uring_cqe *cqe;
    int efd = (int) data;
    eventfd_t v;
    printf("%s: Waiting for completion event...\n", __FUNCTION__);

    int ret = eventfd_read(efd, &v);
    if (ret < 0) error_exit("eventfd_read");

    printf("%s: Got completion event.\n", __FUNCTION__);

    ret = io_uring_wait_cqe(&ring, &cqe);
    if (ret < 0) {
        fprintf(stderr, "Error waiting for completion: %s\n",
                strerror(-ret));
        return NULL;
    }
    /* Now that we have the CQE, let's process it */
    if (cqe->res < 0) {
        fprintf(stderr, "Error in async operation: %s\n", strerror(-cqe->res));
    }
    printf("Result of the operation: %d\n", cqe->res);
    io_uring_cqe_seen(&ring, cqe);

    printf("Contents read from file:\n%s\n", buff);
    return NULL;
}

int setup_io_uring(int efd) {
    int ret = io_uring_queue_init(8, &ring, 0);
    if (ret) {
        fprintf(stderr, "Unable to setup io_uring: %s\n", strerror(-ret));
        return 1;
    }
    io_uring_register_eventfd(&ring, efd);
    return 0;
}

int read_file_with_io_uring() {
    struct io_uring_sqe *sqe;

    sqe = io_uring_get_sqe(&ring);
    if (!sqe) {
        fprintf(stderr, "Could not get SQE.\n");
        return 1;
    }

    int fd = open("/etc/passwd", O_RDONLY);
    io_uring_prep_read(sqe, fd, buff, BUFF_SZ, 0);
    io_uring_submit(&ring);

    return 0;
}

int main() {
    pthread_t t;
    int efd;

    /* Create an eventfd instance */
    efd = eventfd(0, 0);
    if (efd < 0)
        error_exit("eventfd");

    /* Create the listener thread */
    pthread_create(&t, NULL, listener_thread, (void *)efd);

    sleep(2);

    /* Setup io_uring instance and register the eventfd */
    setup_io_uring(efd);

    /* Initiate a read with io_uring */
    read_file_with_io_uring();

    /* Wait for th listener thread to complete */
    pthread_join(t, NULL);

    /* All done. Clean up and exit. */
    io_uring_queue_exit(&ring);
    return EXIT_SUCCESS;
}

How it works

In the main thread, we create an eventfd(2) instance. We then create a thread, passing it the eventfd file descriptor. In the thread, we print a message and immediately read from the eventfd file descriptor. This causes the thread to block since there should be no events posted yet on to the eventfd instance.

While the child thread is blocking on the read on the eventfd file descriptor, we sleep for 2 seconds in the parent to perceive this sequence clearly. Next, in setup_io_uring(), we create an io_uring instance and register our eventfd file descriptor with it. This will cause io_uring to post an event on this eventfd for every completion event.

We then call read_file_with_io_uring() from main. In this, we submit a request to read a file. This will cause io_uring to post an event on the registered eventfd instance. This should now cause the read(2) call in which listener_thread() is blocked on to unblock and continue execution. In this thread, we fetch the completion and print out the data.

Note

Please note that eventfd_read() is a library function provided by glibc. It essentially calls read on the eventfd.

Source code

Source code for this and other examples is available on Github.