Writergate: reading from the stdout of a child process

Reading from the stdout of a spawned process on Windows with the new std.Io.Reader seems to cause problem. I used to be able to read from the stdout of a child process (see read_child_stdout_150()). I migrated it to use the new std.Io.Reader (read_child_stdout_151()) and started getting unreachable panic on handling the IO_PENDING error at line 640 in std.os.windows.zig.

The stdout of a child process is set up as Named Pipe via windowsMakeAsyncPipe() in std.process.Child.zig as a windows.FILE_FLAG_OVERLAPPED file handle (line 1336). IO_PENDING error can occur with an OVERLAPPED file handle. I don’t understand why it was working pre-0.15.1 and now it doesn’t.

Is it something wrong in the way std.Io.Reader is being used in read_child_stdout_151()?

const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const alloc = gpa.allocator();

    const argv: [2][]const u8 = .{ "/usr/bin/echo", "foo bar" };

    var child = std.process.Child.init(&argv, alloc);
    child.stdin_behavior  = .Pipe;
    child.stdout_behavior = .Pipe;
    child.stderr_behavior = .Ignore;
    try child.spawn();

    // try read_child_stdout_150(child.stdout.?);
    try read_child_stdout_151(child.stdout.?);
    _ = try child.wait();
}

fn read_child_stdout_150(child_stdout_f: std.fs.File) !void {
    const reader = child_stdout_f.reader();
    var chunk: [1024]u8 = undefined;
    while (true) {
        const len = try reader.read(&chunk);
        if (len == 0) break;
        std.debug.print("Chunk: {s}\n", .{chunk[0..len]});
    }
}

fn read_child_stdout_151(child_stdout_f: std.fs.File) !void {
    var reader_buf: [1024]u8 = undefined;
    var f_reader = child_stdout_f.reader(&reader_buf);
    var reader = &f_reader.interface;
    var chunk: [1024]u8 = undefined;
    while (true) {
        const len = try reader.readSliceShort(&chunk);
        if (len == 0) break;
        std.debug.print("Chunk: {s}\n", .{chunk[0..len]});
    }
}

I have checked the Windows API ReadFile(). For a FILE_FLAG_OVERLAPPED file handle, ReadFile() can return ERROR_IO_PENDING. It means “the read has been queued, but not finished yet”, not an error condition.

The typical case to handle ERROR_IO_PENDING as a blocking read is to wait on the OVERLAPPED.hEvent.

OVERLAPPED ol = {0};
ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

BOOL ok = ReadFile(hFile, buffer, size, NULL, &ol);
if (!ok) {
    if (GetLastError() == ERROR_IO_PENDING) {
        WaitForSingleObject(ol.hEvent, INFINITE);
        DWORD bytesRead;
        GetOverlappedResult(hFile, &ol, &bytesRead, TRUE);
    }
}

I believe this will be fixed by Handle getSize in File for windows pipes by marler8997 · Pull Request #24873 · ziglang/zig · GitHub

Try forcing streaming mode via readerStreaming()

Good to know it’s being addressed. BTW I had tried both readerStreaming() and reader() and had the same issue.

I’m just not sure whether it’s a real bug or it’s something transient. If it’s a real bug, I’ll file a bug report. I just don’t want to clog up the bug pipeline with non-issues.

The FILE_FLAG_OVERLAPPED flag on a file handle is used for async IO on Windows. Not sure if the upcoming async work will resolve it.

1 Like

If readerStreaming doesn’t fix it, then it’s likely a different problem.

Maybe it’s related to this:

Opening an issue is probably still a good idea, though. There are unresolved bugs with writergate, and that’s especially likely/true for Windows (see, for example, the reason that Zig effectively skipped 0.15.0 and went straight to 0.15.1)

Thanks for the info. I’ve filed a bug report.

1 Like