Przeglądaj źródła

Fix truncated command output

The issue was that poll_fds contained state: When one of the fds was
closed then it would be set to -1, which usually would make `poll()`
ignore it.  But because we recreated poll_fds with every call to
`poll()` we would not save that state, then try to remove it again and
increment `dead_fds` twice for one fd which meant it would not check the
other (still possibly open) fd.

🎉
Luna Stadler 4 lat temu
rodzic
commit
252c08e2e4
1 zmienionych plików z 26 dodań i 18 usunięć
  1. 26 18
      zig/sdl/hello_sdl.zig

+ 26 - 18
zig/sdl/hello_sdl.zig

@ -34,6 +34,7 @@ const ProcessWithOutput = struct {
34 34
    stdout_buf: std.ArrayList(u8),
35 35
    stderr_buf: std.ArrayList(u8),
36 36
37
    poll_fds: [2]std.os.pollfd,
37 38
    dead_fds: usize = 0,
38 39
    max_output_bytes: usize,
39 40
@ -47,7 +48,17 @@ const ProcessWithOutput = struct {
47 48
        child.stderr_behavior = std.ChildProcess.StdIo.Pipe;
48 49
        try child.spawn();
49 50
50
        return ProcessWithOutput{ .process = child, .stdout_buf = std.ArrayList(u8).init(allocator), .stderr_buf = std.ArrayList(u8).init(allocator), .dead_fds = 0, .max_output_bytes = max_output_bytes };
51
        return ProcessWithOutput{
52
            .process = child,
53
            .stdout_buf = std.ArrayList(u8).init(allocator),
54
            .stderr_buf = std.ArrayList(u8).init(allocator),
55
            .poll_fds = [_]std.os.pollfd{
56
                .{ .fd = child.stdout.?.handle, .events = std.os.POLL.IN, .revents = undefined },
57
                .{ .fd = child.stderr.?.handle, .events = std.os.POLL.IN, .revents = undefined },
58
            },
59
            .dead_fds = 0,
60
            .max_output_bytes = max_output_bytes,
61
        };
51 62
    }
52 63
53 64
    fn is_running(self: *ProcessWithOutput) bool {
@ -73,11 +84,6 @@ const ProcessWithOutput = struct {
73 84
            return;
74 85
        }
75 86
76
        var poll_fds = [_]std.os.pollfd{
77
            .{ .fd = self.process.stdout.?.handle, .events = std.os.POLL.IN, .revents = undefined },
78
            .{ .fd = self.process.stderr.?.handle, .events = std.os.POLL.IN, .revents = undefined },
79
        };
80
81 87
        // We ask for ensureTotalCapacity with this much extra space. This has more of an
82 88
        // effect on small reads because once the reads start to get larger the amount
83 89
        // of space an ArrayList will allocate grows exponentially.
@ -85,11 +91,11 @@ const ProcessWithOutput = struct {
85 91
86 92
        const err_mask = std.os.POLL.ERR | std.os.POLL.NVAL | std.os.POLL.HUP;
87 93
88
        if (self.dead_fds >= poll_fds.len) {
94
        if (self.dead_fds >= self.poll_fds.len) {
89 95
            return;
90 96
        }
91 97
92
        const events = try std.os.poll(&poll_fds, 0);
98
        const events = try std.os.poll(&self.poll_fds, 0);
93 99
        if (events == 0) {
94 100
            return;
95 101
        }
@ -100,45 +106,47 @@ const ProcessWithOutput = struct {
100 106
        // conditions.
101 107
        // It's still pstd.ossible to read after a POLL.HUP is received, always
102 108
        // check if there's some data waiting to be read first.
103
        if (poll_fds[0].revents & std.os.POLL.IN != 0) {
109
        if (self.poll_fds[0].revents & std.os.POLL.IN != 0) {
104 110
            // stdout is ready.
105 111
            const new_capacity = std.math.min(self.stdout_buf.items.len + bump_amt, self.max_output_bytes);
106 112
            try self.stdout_buf.ensureTotalCapacity(new_capacity);
107 113
            const buf = self.stdout_buf.unusedCapacitySlice();
108 114
            if (buf.len == 0) return error.StdoutStreamTooLong;
109
            const nread = try std.os.read(poll_fds[0].fd, buf);
115
            const nread = try std.os.read(self.poll_fds[0].fd, buf);
110 116
            self.stdout_buf.items.len += nread;
111 117
112 118
            std.debug.print("read {d} bytes ({d} total, {d} max)\n", .{ nread, self.stdout_buf.items.len, self.max_output_bytes });
113 119
114 120
            // Remove the fd when the EOF condition is met.
115
            remove_stdout = nread == 0;
121
            //remove_stdout = nread == 0;
116 122
        } else {
117
            remove_stdout = poll_fds[0].revents & err_mask != 0;
123
            remove_stdout = (self.poll_fds[0].revents & err_mask) != 0;
118 124
        }
119 125
120
        if (poll_fds[1].revents & std.os.POLL.IN != 0) {
126
        if (self.poll_fds[1].revents & std.os.POLL.IN != 0) {
121 127
            // stderr is ready.
122 128
            const new_capacity = std.math.min(self.stderr_buf.items.len + bump_amt, self.max_output_bytes);
123 129
            try self.stderr_buf.ensureTotalCapacity(new_capacity);
124 130
            const buf = self.stderr_buf.unusedCapacitySlice();
125 131
            if (buf.len == 0) return error.StderrStreamTooLong;
126
            const nread = try std.os.read(poll_fds[1].fd, buf);
132
            const nread = try std.os.read(self.poll_fds[1].fd, buf);
127 133
            self.stderr_buf.items.len += nread;
128 134
129 135
            // Remove the fd when the EOF condition is met.
130
            remove_stderr = nread == 0;
136
            //remove_stderr = nread == 0;
131 137
        } else {
132
            remove_stderr = poll_fds[1].revents & err_mask != 0;
138
            remove_stderr = self.poll_fds[1].revents & err_mask != 0;
133 139
        }
134 140
135 141
        // Exclude the fds that signaled an error.
136 142
        if (remove_stdout) {
137
            poll_fds[0].fd = -1;
143
            std.debug.print("remove stdout\n", .{});
144
            self.poll_fds[0].fd = -1;
138 145
            self.dead_fds += 1;
139 146
        }
140 147
        if (remove_stderr) {
141
            poll_fds[1].fd = -1;
148
            std.debug.print("remove stderr\n", .{});
149
            self.poll_fds[1].fd = -1;
142 150
            self.dead_fds += 1;
143 151
        }
144 152
    }