I’m not sure I understand, if this is user-space std.Io impl, then why is it a problem, why it should ever go away? I haven’t tried yet so I might be missing something but surely your project looks very compelling ![]()
Zio is a backend with two frontend APIs, you can use e.g. zio.net.Stream or std.Io.net.Stream. The former is what I call native and it existed before std.Io was a thing.
congrats, as far as I know this is the first functioning third-party Io implementation
Running zig build examples fails:
examples
└─ install ev-demo
└─ compile exe ev-demo Debug native 2 errors
error: fatal linker error: unhandled relocation type R_X86_64_PC64 at offset 0x1c
note: in /usr/lib/gcc/x86_64-pc-linux-gnu/16.1.1/../../../../lib/crt1.o:.sframe
error: fatal linker error: unhandled relocation type R_X86_64_PC64 at offset 0x2c
note: in /usr/lib/gcc/x86_64-pc-linux-gnu/16.1.1/../../../../lib/crt1.o:.sframe
error: 2 compilation errors
I used:
/c/g/g/l/zio ((v0.11.0)) [1]> zig-0.16.0 build examples
Try with -Doptimize=ReleaseSafe.
Hm, apparently the fix is to use lld, never hit this myself.
@gonzo, Thanks, it works.
I tried to run the example from
https://lalinsky.com/2026/05/11/async-io-in-zig-016-today.html
with const num_tasks = 10_000_000;
The output was:
manlio@mini-linux /t/zigtmp> time ./zig-out/bin/zigtmp
error(zio): Failed to commit initial stack region: error.OutOfMemory
thread 15745 panic: reached unreachable code
Unwind error at address `???:0x102a5b1` (out of memory), remaining frames may be incorrect
Ran out of memory loading debug info, trace may be incomplete
???:?:?: 0x1081c71 in ??? (???)
Ran out of memory loading debug info, trace may be incomplete
???:?:?: 0x1029fb3 in ??? (???)
Ran out of memory loading debug info, trace may be incomplete
???:?:?: 0x7ff6bc827740 in ??? (???)
Ran out of memory loading debug info, trace may be incomplete
???:?:?: 0x7ff6bc827878 in ??? (???)
Ran out of memory loading debug info, trace may be incomplete
???:?:?: 0x10270d4 in ??? (???)
^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C^C
________________________________________________________
Executed in 118.34 secs fish external
usr time 0.44 secs 532.00 micros 0.44 secs
sys time 17.18 secs 402.00 micros 17.18 secs
fish: Job 1, 'time ./zig-out/bin/zigtmp' terminated by signal SIGKILL (Forced quit)
It seems that the program continued to run.
For 10M coroutines, you would need more memory, as each currently needs 256KB RAM commitment, or lower the stack settings. I wonder where is the unreachable coming from, it should normally fail with error.OutOfMemory.
My total memory is 15G, but the actual memory before the SIGABRT was about 9G.
Executed in 304.40 secs fish external
usr time 0.44 secs 0.00 millis 0.44 secs
sys time 26.99 secs 1.13 millis 26.99 secs
fish: Job 1, 'time ./zig-out/bin/zigtmp' terminated by signal SIGABRT.
Note that this is a different run.
For 10M concurrent tasks with the default stack settings, you would need 2.4TB of RAM. Even if you reduce the initial commitment to 64KB, which is fine, you would need 610GB of RAM. This is just not realistic with stackful coroutines. You can get the initial stack commitment much lower and run that many tasks, but be aware that many functions in stdlib expect fairly large stack size, multiple 64KB+ stack allocations per function are common. It will not overflow the stack, because the reservation is larger, but you will exhaust memory.
This reminds me of a general question about the new Io.Evented implementation being worked on in the std library. Will this be stackful or stackless, do you know?
As it is, it’s based on stackful coroutines, but actually allocating 60MB stacks per coroutine, so you can’t run many of them. The coroutine implementation is very similar to zio, but zio supports more platforms/cpus.
60MB is a lot. Why so big? I think not even the main stack size is that big (it’s usually in the 8-16MB range).
We had a drama some months ago in this thread, where Andrew was saying how I’ll be fragmenting the ecosystem if I depend on overcommit in zio, so I actually implemented stack growth after that. And then I saw this. Who is fragmenting the ecosystem now? ![]()
I’m sorry, I’ll stop with the negative comments, but this one is too much for me to hold.
Of course Io.Evented is not done yet, so we don’t know what the final stack size will be.
Still, the main thread the program runs on doesn’t have more than 16MB, 8MB is more common. There is no good reason to pick 60MB.
I see what you’ve done with zio, in combination with the new Io in std, as extremely productive. If both implementations can continue to learn from each other, the result will be much better than if there were only a single completion-based implementation.
Another question (if you don’t mind me taking advantage of your expertise on this topic): with the current Io interface, do you think there is a possibility of a stackless implementation in the future?
Hi, it has been discussed in the past, and to my knowledge there are no technical problems that should make that impossible.
However it will be a shit ton of work and it seems that implementing this is currently not a priority.
Robert
I’m not sure, but the proposal I’ve seen is far from the complete solution. I think the key factor will be limiting the language to disallow unbounded stack use. If you eliminate recursion and function pointers, you can analyze stack usage at comptime.
The hybrid solution, which I think is where Zig ends up, is doing those restrictions and then being able to allocate stacks just large enough to handle the functions. You get the benefit of seamless integration of stackful coroutines, but without the overly large stack allocations. I think that’s good enough even for embedded cases. But this depends on complete devirtualizion of function pointers and no recursion, so it’s not fully compatible with all code
Alternatively, you need to analyze the code, find yield points, determine if something is really an async function. And if you need to call such functions, you need to go through Io.Future, because the frame for the async function needs to be allocated and Zig will never really hide such allocations from you. In some cases, the frame allocation could be on stack, that’s the easier situation.