Skip to content

Understanding Async Gem and Its OS Primitives

Today, I read the article Async Ruby is the Future, which gave a great overview of how Ruby's async gem uses Fibers and an event loop to handle I/O without blocking.

After reading it, I started wondering how the OS knows when a Fiber can resume. I dug deeper and learned about epoll_ctl / epoll_wait on Linux, and their macOS equivalent, kqueue and kevent.

With some help from ChatGPT, I wrote a C program that uses kevent to wait for STDIN input asynchronously. It was a good exercise to learn these low level OS primitives

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/event.h>
#include <sys/time.h>
#include <fcntl.h>
 
int main() {
    int kq = kqueue();
    if (kq == -1) {
        perror("kqueue");
        exit(1);
    }
 
    struct kevent change;
    EV_SET(&change, STDIN_FILENO, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
 
    printf("Waiting for input (kqueue)...\n");
 
    struct kevent event;
    int nev = kevent(kq, &change, 1, &event, 1, NULL);  // NULL = block forever
 
    if (nev == -1) {
        perror("kevent");
        exit(1);
    }
 
    if (event.filter == EVFILT_READ) {
        char buffer[1024];
        ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
        if (n > 0) {
            buffer[n] = '\0';
            printf("Read from STDIN: %s\n", buffer);
        }
    }
 
    close(kq);
    return 0;
}

This gave me a better understanding of OS Kernel abstractions.