Failing to write/read to/from child process

Hey there! First time posting to ziggit, hoping a relatively easy question here.

I’m attempting to spawn a local “server” as a child process that supports communication over stdin / stdout. I’ve tried a few different ways of doing this, but I keep running into some odd errors, and I’m not sure how to proceed.

Here’s a minimal example case, just using tail as the “server”.

const std = @import("std");
const Io = std.Io;

pub fn main(init: std.process.Init) !void {
    const alloc = init.arena.allocator();
    const io = init.io;

    var tail: std.process.Child = try std.process.spawn(io, .{
        .argv = &.{"tail"},
        .stdin = .pipe,
        .stdout = .pipe,
    });

    const buf_w = try alloc.alloc(u8, 1024);
    const buf_r = try alloc.alloc(u8, 1024);

    var w: Io.Writer = tail.stdin.?.writer(io, buf_w).interface;
    var r: Io.Reader = tail.stdout.?.reader(io, buf_r).interface;

    _ = try w.write("123456789\n");
    try w.flush();

    var out: [10]u8 = @splat(0);
    try r.readSliceAll(&out);

    std.debug.print("{s}\n", .{out});
}

In this case, I get a failure on the write side:

General protection exception (no address available)
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io.zig:453:29: 0x1039bb5 in operate (std.zig)
    return io.vtable.operate(io.userdata, operation);
                            ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/File.zig:614:27: 0x11dace9 in writeStreaming (std.zig)
    return (try io.operate(.{ .file_write_streaming = .{
                          ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/File/Writer.zig:126:36: 0x11da786 in drainStreaming (std.zig)
    const n = w.file.writeStreaming(io, header, data, splat) catch |err| {
                                   ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/File/Writer.zig:93:63: 0x11da19c in drain (std.zig)
        .streaming, .streaming_simple => return drainStreaming(w, data, splat),
                                                              ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/Writer.zig:319:39: 0x10365e2 in defaultFlush (std.zig)
    while (w.end != 0) _ = try drainFn(w, &.{""}, 1);
                                      ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/Writer.zig:313:26: 0x11279e3 in flush (std.zig)
    return w.vtable.flush(w);
                         ^
/home/nickmonad/code/test-zig/src/test.zig:21:16: 0x11d25e5 in main (test.zig)
    try w.flush();
               ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/start.zig:737:30: 0x11d32fe in callMain (std.zig)
    return wrapMain(root.main(.{
                             ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/start.zig:190:5: 0x11d20e1 in _start (std.zig)
    asm volatile (switch (native_arch) {
    ^
fish: Job 1, 'zig run src/test.zig' terminated by signal SIGABRT (Abort)

If I change the write to use writeAll, that seems to progress, but then I get a similar error on the read side

    _ = try w.writeAll("123456789\n");

    var out: [10]u8 = @splat(0);
    try r.readSliceAll(&out);

    std.debug.print("{s}\n", .{out});
General protection exception (no address available)
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io.zig:453:29: 0x1039bb5 in operate (std.zig)
    return io.vtable.operate(io.userdata, operation);
                            ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/File.zig:475:27: 0x10390f7 in readStreaming (std.zig)
    return (try io.operate(.{ .file_read_streaming = .{
                          ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/File/Reader.zig:283:35: 0x1038bfb in readVecStreaming (std.zig)
    const n = r.file.readStreaming(io, dest) catch |err| switch (err) {
                                  ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/File/Reader.zig:236:65: 0x10376a0 in readVec (std.zig)
        .streaming, .streaming_simple => return readVecStreaming(r, data),
                                                                ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/Reader.zig:429:37: 0x10eaefa in readVec (std.zig)
        return n + (r.vtable.readVec(r, data[i..]) catch |err| switch (err) {
                                    ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/Reader.zig:688:21: 0x10ea4a7 in readSliceShort (std.zig)
        i += readVec(r, &data) catch |err| switch (err) {
                    ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/Io/Reader.zig:661:33: 0x10e9e17 in readSliceAll (std.zig)
    const n = try readSliceShort(r, buffer);
                                ^
/home/nickmonad/code/test-zig/src/test.zig:23:23: 0x11d2634 in main (test.zig)
    try r.readSliceAll(&out);
                      ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/start.zig:737:30: 0x11d326e in callMain (std.zig)
    return wrapMain(root.main(.{
                             ^
/nix/store/m8gzpx3c3w39wh36h5hvsy2m7vgig998-zig-0.16.0/lib/std/start.zig:190:5: 0x11d20e1 in _start (std.zig)
    asm volatile (switch (native_arch) {
    ^
fish: Job 1, 'zig run src/test.zig' terminated by signal SIGABRT (Abort)

Any insight here would be greatly appreciated. Am I doing something weird with the stack allocated resources here, either the File handles or the Io interfaces, such that it’s pointing to invalid memory? Does the writing and reading need to be in separate threads? I know I’m not cleaning up resources fully, but I would assume I’d at least see the expected print result before there would be an issue there.

Thanks!

should be

    var w: Io.File.Writer = tail.stdin.?.writer(io, buf_w);
    var r: Io.File.Reader = tail.stdout.?.reader(io, buf_r);
    try w.interface.writeAll("asfasdf"); // write wont always write *all* the data
    try w.interface.flush();

The Reader/Writer style of interfaces (called “intrusive interfaces”) are used via pointers to fields, you copied them out of the fields invalidating an assumption used for pointer shenanigans.
Whereas the Allocator/Io interfaces just store a pointer to the implementation

You don’t need to understand the nuances of different interface styles.

Just that they need some way to refer to implementation state, which necessitates the implementation be in a variable somewhere that is valid to access for the duration you need the interface.


In the future zig will be able to catch this mistake and provide a more useful error, it just hasn’t been implemented yet.

6 Likes

Thank you! I’m hitting other errors related to unexpected EndOfStream, etc, but that can be resolved. Much appreciated.

for future reference if you encounter a legitimate error.ReadFailed, it means the implementation encountered an error that doesn’t map to the other errors the interface defines.

Usually there is a way to get a more specific error from the implementation, for the case of file reader/writer there is just an optional err field. In fact, it has other _err fields as well, for specific operations they support outside the interface.

when an interface has its own state, like Reader and Writer do, the intrusive style makes it easy to extend them with features that would have to interact with the interface state

1 Like