What Happened

std.Io.Threaded implementations of Zig’s new Io interface that enables concurrency. This is a boring “just use threads” impl. I personally though — I wanted , knowledge properly, and implements it better than I thought to be possible.

Io.Threaded uses blocking syscalls and fully supports cancelation.

I think this definition is correct, but doesn’t provide useful intuition directly. Concurrency transducers? Yes, obviously, but not really illuminating ’d program the thing.

For intuition, I litmus tests. First, parallelism is deterministic or “declarative”:

Why It Matters

use rayon::prelude::*; fn sum_of_squares(input: &[i32]) -> i32 { input.par_iter() .map(|i| i * i) .sum() } You describe problem into independent partitions, and implement a function to process one partition at a time . It’s platform’s job to verify the partitioning to be correct (non-racy), process all partitions, and yield control .

Second, concurrency invariably involves cancelation. Whenever asynchronous computations happening , there comes a moment when one computation becomes second computation is no longer necessary, canceled, actively. In general, possible computation completes: often, the reason cancel precisely because you’ve learned ’t complete (e.g., it is waiting for a message receive).

Well, , , while you totally threads, this often requires system-wide configuration change, which is a non-starter for most application. But absence of cancelation really a wall sooner or later. The problem are syscalls. It’s easy enough, , to do something like

while (true) { if (is_canceled()) return error.Canceld; /// Easy! ... } But, the thread is instead blocked inside the syscall in the kernel, programming language APIs generally doesn’t unblock it:

What Comes Next

const read_size = try read(fd, buffer); // ??? Wouldn’t standard OS threads, blocking APIs, avoid new shinies like io_uring, cancel any work reliably? That’s exactly what Zig’s std.Io.Threaded provides.

a bit cursed. Turns out, the kernel actually provides a roundabout way to cancel a blocking syscall — signals. When a thread is blocked in the kernel, and a signal is delivered to the thread, the thread syscall returns EINTR. It is customary -try the syscall , ’t have to.

By itself, signals are not a cancelation mechanism — signaling a thread is inherently racy, the signal might get delivered before the relevant syscall starts, finishes. Conversely, a syscall might get interrupted by signal unrelated to cancelation.

The actual protocol canceling thread sets a flag in shared memory to request cancelation, and then signals the cancelee, in a loop, until the cancelation is acknowledged (a different value for a shared memory). Upon receiving EINTR from a syscall, the thread potentially being canceled checks either retries the syscall, or acknowledges the cancelation and begins unwinding. See signalCanceledSyscall and, eg fileReadPositionalPosix halves of the protocol.

-side, cancelation request is materialized as error.Canceled. Error management as a feature is a combination of cancelation, branching, and reporting, and Zig implements . Cancelation isn’t because it is serendipitous success, but because, vice versa, a cancelation plus a payload.

On Windows, there’s a much more direct NtCancelSynchronousIoFile !. In general, between fibers, IO Completion Ports, Job objects, and this, a better thought through concurrency .

In Java, there’s a similarly looking thread interruption mechanism. Critically, it doesn’t support interrupting syscalls: IOException and InterruptedException are both checked and unrelated, meaning that IOing functions are not interruptible. In Zig, reader and writer interfaces completely type erase errors and therefore support cancelation, though this requires handle correctly, ’t forget to flush.

Explore more: Software & AI Guide