# Zio - async I/O framework

**URL:** <https://ziggit.dev/t/zio-async-i-o-framework/12213>\
**Category:** Showcase\
**Tags:** networking, llm, async\
**Created:** [September 26, 2025, 8:18am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213 "2025-09-26T08:18:54Z")\
**Posts on this page:** 20\
**Page:** 5

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [February 5, 2026, 7:17am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/81 "2026-02-05T07:17:56Z")

</div>

The thread pool size is limited, can only use up to N threads. But unlike `async`, it can’t run the code in-place when there is no free thread. And unlike `concurrent`, it can’t fail if it reached the N limit. In my implementation, the queue is a linked list and the tasks contain the list nodes, so it can never be full. Even if you use constrained array-based queue, e.g. for work-stealing, there should be an overflow queue that’s unlimited.

---

<div class="post-metadata">

**Author:** ![andrewrk](https://ziggit.dev/user_avatar/ziggit.dev/andrewrk/32/7011_2.png) [@andrewrk](https://ziggit.dev/u/andrewrk)\
**Post date:** [February 5, 2026, 7:48am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/82 "2026-02-05T07:48:38Z")

</div>

If it can’t run the code in-place, then you need somewhere to store the arguments. You can’t use an intrusive linked list for the queue, where would you store the node? You can’t use stack memory, you’re about to return from the function.

I just looked at the codebase and see that `spawnBlocking` is fallible because it heap allocates memory for the queue. Looks like it does grow unbounded…? I think this is where I dip out, I don’t like the LLM stench in here, sorry.

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [February 5, 2026, 8:07am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/83 "2026-02-05T08:07:19Z")

</div>

> [@andrewrk](#):
>
> I just looked at the codebase and see that `spawnBlocking` is fallible because it heap allocates memory for the queue. Looks like it does grow unbounded…?

Yes, closures need allocations, I meant thread count can’t grow unbounded.

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [February 9, 2026, 12:01pm UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/84 "2026-02-09T12:01:52Z")

</div>

I’ve just released another version. From now on, I expect the API to be more or less stable. Some minor changes could be necessary, but I don’t expect anything major.

> **[Release v0.7.0 · lalinsky/zio](https://github.com/lalinsky/zio/releases/tag/v0.7.0)**
>
> Added
> 
> Added CI for 32-bit ARM/Thumb and RISC-V CPUs to make sure these don't break.
> 
> Changed
> 
> BREAKING: Removed rt parameter from most functions. It's no longer needed.
> You can now use zio.spawn, ...

> **[Release v0.8.0 · lalinsky/zio](https://github.com/lalinsky/zio/releases/tag/v0.8.0)**
>
> Added
> 
> Added CompletionQueue for waiting on multiple I/O operations.
> Added blocking I/O support for socket, pipe, poll, timer, and work operations. These operations can now be called from any threa...

Three big changes:

1. None of the the public APIs now accept the `rt` parameter. It actually made it easier for multiple runtimes within the same process to coooperate. Additionally, it’s possible to communicate with the async context from a foreign thread. You can use e.g. `zio.Channel` or `zio.Mutex` from anywhere, and it will the right thing. Removing the parameter also makes it easier to support embedded, which is not a major goal for now, but I want to keep that in mind when doing decisions.

1. You can now use file and network I/O operations outside of the async context. You you use `zio.File` in a foreign thread, it will use regular blocking syscalls. I’ve done this as an easier upgrade path to Zig 0.16, because I need the equivalent of `std.fs` to use in a thread pool. I can’t depend on async file I/O to be efficient.

2. There is now `zio.CompletionQueue`, which is similar to `std.Io.Batch`, but more general. It allows you to use any of the event-loop operations from a coroutine. It provides io\_uring-like API no matter which backends you use. You use `cq.submit()` to add more requests, and `cq.wait()` or `cq.timedWait()` to iterate over completions. It uses no allocations internally, it’s all done via intrusive linked lists, and you can scale it to as many operations as you need. You can use it as small inner poller loop, you can also use it for more complex request handling, if you don’t want to use coroutine-per-connection, but also want to avoid callbacks.

---

<div class="post-metadata">

**Author:** ![Illusion](https://ziggit.dev/letter_avatar_proxy/v4/letter/i/e19b73/32.png) [@Illusion](https://ziggit.dev/u/Illusion)\
**Post date:** [February 16, 2026, 9:27pm UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/85 "2026-02-16T21:27:54Z")

</div>

@lalinsky Although you might say it’s not production ready, do you think `dusty` (derived from zio) is in a good enough place for basic web applications that just call other web services and communicate with a database? I wanted to consider writing a business application with it since it seems to be the most complete HTTP server that supports async in pure zig (a hard requirement since some vendors take minutes to respond).

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [February 16, 2026, 10:17pm UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/86 "2026-02-16T22:17:14Z")

</div>

@Illusion Yes, the HTTP client and server is ready and can be used in more serious apps, that’s the next plan for me, as well. However, you disqualified it right in the next sentence, Dusty is not pure Zig, it depends on a C project called llhttp (vendored in). Why is it a requirement?

---

<div class="post-metadata">

**Author:** ![Illusion](https://ziggit.dev/letter_avatar_proxy/v4/letter/i/e19b73/32.png) [@Illusion](https://ziggit.dev/u/Illusion)\
**Post date:** [February 16, 2026, 10:34pm UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/87 "2026-02-16T22:34:41Z")

</div>

It’s not really a requirement, it was just to qualify my words of “most complete HTTP server.” After all, I assume in pure C, or some other language there is another complete asynchronous HTTP server.

Edit: lol, I actually did get screwed by the C in the end. Errors on “undefined symbol: \_\_libc\_csu\_fini and \_\_libc\_csu\_init” which is a bit weird, since my OS has glibc 2.28, which should have them still. Idk, will try to debug.

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [February 16, 2026, 10:47pm UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/88 "2026-02-16T22:47:39Z")

</div>

Ok, then dusty/zio is definitely good for that use case. It’s essentially what I built it for. At first, I needed an async HTTP server that can communicate with other HTTP services without blocking, and later on also PostgreSQL database. All of that is already supported. I haven’t ran this in production yet myself, but I think it’s in a good shape.

---

<div class="post-metadata">

**Author:** ![zigster](https://ziggit.dev/user_avatar/ziggit.dev/zigster/32/51_2.png) [@zigster](https://ziggit.dev/u/zigster)\
**Post date:** [February 17, 2026, 3:31am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/89 "2026-02-17T03:31:21Z")

</div>

Heya - can you clarify exactly what you mean by “a HTTP server that supports async” or “another complete asynchronous HTTP server”

That can mean several completely different things, depending on what you mean

Most of the popular HTTP servers for earlier versions of Zig (ie - up to 0.15), pretty much all do non-blocking IO, concurrent handlers, use kqueue / epoll / io\_uring under the hood for the network action, etc.

Dusty + zio, does all that, but also allows proper co-routines, which is great for lots of long lived handlers / actor model.

So there is a whole lot of different “async” going on here - and a whole lot of different types of “concurrent” as well.

Wondered which kind of async you need that was blocking you from considering the other servers out there that you have looked at.

---

<div class="post-metadata">

**Author:** ![Illusion](https://ziggit.dev/letter_avatar_proxy/v4/letter/i/e19b73/32.png) [@Illusion](https://ziggit.dev/u/Illusion)\
**Post date:** [February 17, 2026, 3:40am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/90 "2026-02-17T03:40:03Z")

</div>

To preface: I am not well versed with asynchrony and concurrency with respect to programming. All I was trying to do was migrate off of Python to a language that is statically typed and can reduce memory usage.

My problem was simple: you receive a web request, you process that in a handler. As part of that handler’s function, it needs to, in parallel, query a bunch of external web services to aggregate information. That act, of making concurrent web requests didn’t seem as straight forward to me with other implementations. With zio, it seems as simple as zio.spawn.

---

<div class="post-metadata">

**Author:** ![zigster](https://ziggit.dev/user_avatar/ziggit.dev/zigster/32/51_2.png) [@zigster](https://ziggit.dev/u/zigster)\
**Post date:** [February 17, 2026, 3:51am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/91 "2026-02-17T03:51:49Z")

</div>

Cheers, fair enough, that makes sense.

If you are on 0.15.2 - you would use thread.spawn() I guess to do the same thing .. similar api, bit more overhead maybe because it’s using full threads.

With zig 0.16 (when it’s all finished), you will get the same thing using an Io.group with a bunch of concurrent jobs, either as threads or fibers. Gotta wait though.

Terminology wise - most languages and ecosystems use the term “async” to mean “both async and concurrent” … in the zig world, those terms are split and more narrowly defined, which may be confusing at times.

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [February 17, 2026, 6:41am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/92 "2026-02-17T06:41:32Z")

</div>

> [@zigster](#):
>
> Dusty + zio, does all that, but also allows proper co-routines, which is great for lots of long lived handlers / actor model.

The main benefit of dusty + zio is that it’s not just a HTTP server implemented using async I/O, but you can use the same system also for outgoing client requests. In my opinion, the HTTP client in dusty is the easiest-to-use HTTP client for Zig. And then you have other client libraries as well, which you typically need for web dev, all integrated.

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [February 17, 2026, 7:47am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/93 "2026-02-17T07:47:15Z")

</div>

> [@Illusion](#):
>
> Edit: lol, I actually did get screwed by the C in the end. Errors on “undefined symbol: \_\_libc\_csu\_fini and \_\_libc\_csu\_init” which is a bit weird, since my OS has glibc 2.28, which should have them still. Idk, will try to debug.

It could be that I just need to add some flags to the build config. Please let me know what OS are you using?

---

<div class="post-metadata">

**Author:** ![lukeflo](https://ziggit.dev/user_avatar/ziggit.dev/lukeflo/32/7514_2.png) [@lukeflo](https://ziggit.dev/u/lukeflo)\
**Post date:** [February 17, 2026, 9:59am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/94 "2026-02-17T09:59:43Z")

</div>

> [@zigster](#):
>
> Terminology wise - most languages and ecosystems use the term “async” to mean “both async and concurrent” … in the zig world, those terms are split and more narrowly defined, which may be confusing at times.

A little bit off topic, but because I recently dealed with a similar issue of understanding async/concurrent stuff in Zig (compared to Rust in my case) I think the following blog is really helpful for understanding (Got the link from Andrew Kelleys [post](https://andrewkelley.me/post/zig-new-async-io-text-version.html) an async I/O):

> **[Asynchrony is not Concurrency](https://kristoff.it/blog/asynchrony-is-not-concurrency/)**
>
> Yes I know about that one talk from Rob Pike.

Though, most of you might already know this post and about its content in general 😉

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [February 17, 2026, 10:20am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/95 "2026-02-17T10:20:19Z")

</div>

This how the term got confusing. 🙂

In all other contexts in computing, async / asynchronous means not blocking the current thread of execution, there are many ways to achieve that, but the concept is the same. On the other hand, `io.async` is specially allowed to block the current thread of execution.

---

<div class="post-metadata">

**Author:** ![kristoff](https://ziggit.dev/user_avatar/ziggit.dev/kristoff/32/9_2.png) [@kristoff](https://ziggit.dev/u/kristoff)\
**Post date:** [February 17, 2026, 11:03am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/96 "2026-02-17T11:03:46Z")

</div>

That’s because in other languages the async keyword is overloaded to also mean concurrent. At the same time other languages don’t concern themselves with making sure that library code can be reused in both concurrent and non-concurrent applications, so they never bothered to split the two concepts apart.

We do in Zig, so we need to be more precise with our terminology.

> [@lalinsky](#):
>
> On the other hand, `io.async` is specially allowed to block the current thread of execution.

And in fact `io.concurrent` is not allowed to block the current thread of execution.

Terminology pain is unavoidable, roughly for the same reasons as this movie scene:

 ![image](https://ziggit.dev/uploads/default/original/2X/1/1d1b57144d7504d641757fe968aed66bfc86df79.png)

---

<div class="post-metadata">

**Author:** ![Illusion](https://ziggit.dev/letter_avatar_proxy/v4/letter/i/e19b73/32.png) [@Illusion](https://ziggit.dev/u/Illusion)\
**Post date:** [February 17, 2026, 2:23pm UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/97 "2026-02-17T14:23:01Z")

</div>

OS would be RHEL8, more specifically 8.10.

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [February 18, 2026, 7:56pm UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/98 "2026-02-18T19:56:51Z")

</div>

Hm, I’ve tried building on Rocky Linux 8, which should be binary compatible with RHEL8, using Zig 0.15.2 from the Zig website and I had no issues. So maybe it’s something else in your setup?

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [May 11, 2026, 10:32am UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/99 "2026-05-11T10:32:56Z")

</div>

I’ve released version 0.11 of zio, which fixes one critical bug in networking using the epoll backend, and more or less completes the `std.Io` implementation. You can now use zio in Zig 0.16 as an alternative to the future `std.Io.Evented`.

> **[Release v0.11.0 · lalinsky/zio](https://github.com/lalinsky/zio/releases/tag/v0.11.0)**
>
> Added
> 
> std.Io interface is now essentially complete. All major operations are implemented:
> 
> Spawn and wait on child processes, with non-blocking pipe I/O on POSIX.
> Iterate over directory entries.
> C...

I’ll keep aligning the library with the `Io` interface to improve the implementation, remove unnecessary wrappers/mappings. The zio API will probably not go away at least until the restricted function pointers are implemented and `Io` has native timeout support.

```zig
const zio = @import("zio");

pub fn main(init: std.process.Init) !void {
    const rt = try zio.Runtime.init(init.gpa, .{});
    defer rt.deinit();

    const io = rt.io();

    // do whatever with io
}

```

---

<div class="post-metadata">

**Author:** ![lalinsky](https://ziggit.dev/user_avatar/ziggit.dev/lalinsky/32/3720_2.png) [@lalinsky](https://ziggit.dev/u/lalinsky)\
**Post date:** [May 11, 2026, 7:33pm UTC](https://ziggit.dev/t/zio-async-i-o-framework/12213/100 "2026-05-11T19:33:36Z")

</div>

Blog post about the release [Async I/O in Zig 0.16, today | Lukáš Lalinský](https://lalinsky.com/2026/05/11/async-io-in-zig-016-today.html)

[Previous page](https://ziggit.dev/t/zio-async-i-o-framework/12213.md?page=4)

[Next page](https://ziggit.dev/t/zio-async-i-o-framework/12213.md?page=6)
