Noteflakes iconnoteflakes

OSS Friday Update

21·11·2025

Note: while my schedule is quite hectic these last few weeks, I’ve taken the decision to dedicate at least one day per week for developing open-source tools, and henceforth I plan to post an update on my progress in this regard every Friday evening. Here’s the first update:

UringMachine Grant Work

As I wrote here previously, a few weeks ago I learned I’ve been selected as one of the recipients of a grant from the Ruby Association in Japan, for working on UringMachine, a new gem that brings low-level io_uring I/O to Ruby. For this project, I’ve been paired with a terrific mentor - Samuel Williams - who is the authority on all things related to Ruby fibers. We’ve had a talk about the project and discussed the different things that I’ll be able to work on. I’m really glad to be doing this project under his guidance.

UringMachine implements a quite low-level API for working with I/O. You basically work with raw file descriptors, you can spin up fibers for doing multiple things concurrently, and there are low-level classes for mutexes and queues (based on the io_uring implementation of the futex API). Incidentally, I find it really cool that futexes can be used with io_uring to synchronize fibers, with very low overhead.

The problem with this, of course, is that this API is useless when you want to use the standard Ruby I/O classes, or any third-party library that relies on those standard classes.

This is where the Ruby fiber scheduler comes into the picture. Early on in my work on UringMachine, it occurred to me that the Fiber::Scheduler added to Ruby by Samuel is a perfect way to integrate such a low-level API with the Ruby I/O layer and the entire Ruby ecosystem. An implementation of Fiber::Scheduler for UringMachine would use the different scheduler hooks to punt work to the low-level UringMachine API.

So this week I finally got around to making some progress on the UringMachine fiber scheduler, and there’s finally a basic working version that can do basic I/O, as well as some other stuff like sleeping, waiting on blocking operations (such as locking a mutex or waiting on a queue), and otherwise managing the life cycle of a scheduler.

This is also a learning process. The Ruby IO class implementation is really complex: the io.c file itself is about 10K LOCs! I’m still figuring out the mechanics of the fiber scheduler as I go, and lots of things are still unclear, but I’m taking it one step at a time, and when I hit a snag I just try to take the problem apart and try to understand what’s going on. But now that I have moved from a rough sketch to something that works and has some tests, I intend to continue working on it by adding more and more tests and TDD’ing my way to an implementation that is both complete (feature-wise) and robust.

Here are some of the things I’ve learned while working on the fiber scheduler:

Improving and extending the fiber scheduler interface

One of the things I’ve discussed with Samuel is the possibility of extending the fiber scheduler interface by adding more hooks, for example a hook for closing an IO (from what I saw there’s already some preparation for that in the Ruby runtime), or a hook for doing a splice. We’ve also discussed working with pidfd_open to prevent race conditions when waiting on child processes. I think there’s still a lot of cool stuff that can be done by bringing low-level I/O functionality to Ruby.

I’ve also suggested to Samuel to use the relatively recent io_uring_prep_waitid API to wait for child processes, and more specifically to do this in Samuel’s own io-event gem, which provides a low-level cross-platform API For building async programs in Ruby. With the io_uring version of waitid, there’s no need to use pidfd_open (in order to poll for readiness when the relevant process terminates). Instead, we use the io_uring interface to directly wait for the process to terminate. Upon termination, the operation completes and we get back the pid and status of the terminated process. This is also has the added advantage that you can wait for any child process, or any child process in the process group, which means better compatibility with the Process.wait and associated methods.

One problem is that the fiber scheduler process_wait hook is supposed to return an instance of Process::Status. This is a core Ruby class, but you cannot create instances of it. So, if we use io_uring to directly wait for a child process to terminate, we also need a way to instantiate a Process::Status object with the information we get back from io_uring. I’ve submitted a PR that hopefully will be merged before the release of Ruby 4.0. I’ve also submitted a PR to io-event with the relevant changes.

Going forward

So here’s where the UringMachine project is currently at:

If you appreciate my OSS work, please consider sponsoring me.

My Consulting Work

Apart from my open-source work, I’m also doing consulting work for. Here’s some of the things I’m currently working on for my clients: