This branch introduces Io.Operation, std.Io.operate, std.Io.Batch, and implements them for FileReadStreaming. In std.Io.Threaded, the implementation is based on poll().
The idea here is that every VTable function that makes sense to have error{Canceled,Timeout} in its error set would be moved into this Operation tagged union. Note that the higher level API, e.g. std.Io.File.readFileStreaming is unchanged. We expect this to be lowerable in a reasonable fashion using IoUring, Kqueue, and Windows.
Motivation
- general support for batching, timeouts, and non-blocking across all I/O operations
- ability to migrate code that used
poll() without causing error.ConcurrencyUnavailable on -fsingle-threaded builds
- for example, processing stdout and stderr of child processes in a single-threaded application
Demonstration
Using std.process.run to collect both stdout and stderr in a single-threaded program using std.Threaded.Io. The point of this example is, if the parent naively reads from either stdout or stderr non-concurrently, it will deadlock.
child.zig:
conststd=@import("std");constIo=std.Io;pubfnmain(init:std.process.Init)!void{constio=init.io;varstdout:Io.File.Writer=.initStreaming(.stdout(),io,&.{});varstderr:Io.File.Writer=.initStreaming(.stderr(),io,&.{});trystdout.interface.splatByteAll('A',5000);trystderr.interface.splatByteAll('B',5000);trystdout.interface.splatByteAll('C',5000);trystderr.interface.splatByteAll('D',5000);}
parent.zig
conststd=@import("std");constIo=std.Io;pubfnmain(init:std.process.Init)!void{constio=init.io;constarena=init.arena.allocator();constresult=trystd.process.run(arena,io,.{.argv=&.{"./child"},});std.debug.print("stdout:\n{s}\nstderr:\n{s}",.{result.stdout,result.stderr});}
$ stage3/bin/zig build-exe parent.zig -fsingle-threaded
$ ./parent
stdout:
AAAAAA...(x5000)CCCCC....(x5000)
stderr:
BBBBBB...(x5000)DDDDD....(x5000)
$ strace ./parent
...
pipe2([8, 9], O_CLOEXEC) = 0
fork() = 3305035
close(9) = 0
close(4) = 0
close(6) = 0
munmap(0x7fb1cec93000, 53248) = 0
munmap(0x7fb1ceb40000, 131072) = 0
read(8, "", 8) = 0
close(8) = 0
mmap(0x7fb1ceca0000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1cf009000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=129}], 1) = 129
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=129}], 1) = 129
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=194}], 1) = 194
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=194}], 1) = 194
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=291}], 1) = 291
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=291}], 1) = 291
mremap(0x7fb1cf009000, 4096, 8192, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x7fb1cf00a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1cf007000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=436}], 1) = 436
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=436}], 1) = 436
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=654}], 1) = 654
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=654}], 1) = 654
mremap(0x7fb1cf007000, 8192, 12288, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x7fb1cf009000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1cf003000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=981}], 1) = 981
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=981}], 1) = 981
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=1472}], 1) = 1472
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=1472}], 1) = 1472
mremap(0x7fb1cf003000, 16384, 20480, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x7fb1cf007000, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1ced42000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=2208}], 1) = 2208
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=2208}], 1) = 2208
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"..., iov_len=3312}], 1) = 3312
readv(5, [{iov_base="DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"..., iov_len=3312}], 1) = 3312
mremap(0x7fb1ced42000, 32768, 49152, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x7fb1ced4a000, 73728, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1cec8e000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"..., iov_len=4968}], 1) = 323
readv(5, [{iov_base="DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"..., iov_len=4968}], 1) = 323
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLHUP}, {fd=5, revents=POLLHUP}])
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3305035, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
...
Merge Blockers
- fix bugs and failures
- Windows implementation
Followup Issues
- move more stuff from VTable to Operation
closes https://github.com/ziglang/zig/issues/25753
This branch introduces `Io.Operation`, `std.Io.operate`, `std.Io.Batch`, and implements them for `FileReadStreaming`. In `std.Io.Threaded`, the implementation is based on poll().
The idea here is that every VTable function that makes sense to have `error{Canceled,Timeout}` in its error set would be moved into this `Operation` tagged union. Note that the higher level API, e.g. `std.Io.File.readFileStreaming` is unchanged. We expect this to be lowerable in a reasonable fashion using IoUring, Kqueue, and Windows.
## Motivation
* general support for batching, timeouts, and non-blocking across all I/O operations
* ability to migrate code that used `poll()` without causing `error.ConcurrencyUnavailable` on `-fsingle-threaded` builds
- for example, processing stdout and stderr of child processes in a single-threaded application
## Demonstration
Using `std.process.run` to collect both stdout and stderr in a single-threaded program using `std.Threaded.Io`. The point of this example is, if the parent naively reads from either stdout or stderr non-concurrently, it will deadlock.
child.zig:
```zig
const std = @import("std");
const Io = std.Io;
pub fn main(init: std.process.Init) !void {
const io = init.io;
var stdout: Io.File.Writer = .initStreaming(.stdout(), io, &.{});
var stderr: Io.File.Writer = .initStreaming(.stderr(), io, &.{});
try stdout.interface.splatByteAll('A', 5000);
try stderr.interface.splatByteAll('B', 5000);
try stdout.interface.splatByteAll('C', 5000);
try stderr.interface.splatByteAll('D', 5000);
}
```
parent.zig
```zig
const std = @import("std");
const Io = std.Io;
pub fn main(init: std.process.Init) !void {
const io = init.io;
const arena = init.arena.allocator();
const result = try std.process.run(arena, io, .{
.argv = &.{"./child"},
});
std.debug.print("stdout:\n{s}\nstderr:\n{s}", .{ result.stdout, result.stderr });
}
```
```
$ stage3/bin/zig build-exe parent.zig -fsingle-threaded
$ ./parent
stdout:
AAAAAA...(x5000)CCCCC....(x5000)
stderr:
BBBBBB...(x5000)DDDDD....(x5000)
$ strace ./parent
...
pipe2([8, 9], O_CLOEXEC) = 0
fork() = 3305035
close(9) = 0
close(4) = 0
close(6) = 0
munmap(0x7fb1cec93000, 53248) = 0
munmap(0x7fb1ceb40000, 131072) = 0
read(8, "", 8) = 0
close(8) = 0
mmap(0x7fb1ceca0000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1cf009000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=129}], 1) = 129
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=129}], 1) = 129
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=194}], 1) = 194
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=194}], 1) = 194
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=291}], 1) = 291
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=291}], 1) = 291
mremap(0x7fb1cf009000, 4096, 8192, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x7fb1cf00a000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1cf007000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=436}], 1) = 436
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=436}], 1) = 436
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=654}], 1) = 654
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=654}], 1) = 654
mremap(0x7fb1cf007000, 8192, 12288, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x7fb1cf009000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1cf003000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=981}], 1) = 981
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=981}], 1) = 981
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=1472}], 1) = 1472
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=1472}], 1) = 1472
mremap(0x7fb1cf003000, 16384, 20480, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x7fb1cf007000, 32768, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1ced42000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., iov_len=2208}], 1) = 2208
readv(5, [{iov_base="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"..., iov_len=2208}], 1) = 2208
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"..., iov_len=3312}], 1) = 3312
readv(5, [{iov_base="DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"..., iov_len=3312}], 1) = 3312
mremap(0x7fb1ced42000, 32768, 49152, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x7fb1ced4a000, 73728, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fb1cec8e000
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLIN}, {fd=5, revents=POLLIN}])
readv(3, [{iov_base="CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"..., iov_len=4968}], 1) = 323
readv(5, [{iov_base="DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"..., iov_len=4968}], 1) = 323
poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN}], 2, -1) = 2 ([{fd=3, revents=POLLHUP}, {fd=5, revents=POLLHUP}])
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3305035, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
...
```
## Merge Blockers
* fix bugs and failures
* Windows implementation
## Followup Issues
* move more stuff from VTable to Operation
----
closes https://github.com/ziglang/zig/issues/25753