Commit 072c3202c3

LemonBoy <LemonBoy@users.noreply.github.com>
2021-06-05 20:26:59
std: Better handing of POLLHUP in ChildProcess (#8988)
* std: Better handing of POLLHUP in ChildProcess Upon hitting the EOF condition there are two main differences between how Linux and the *BSD-derived systems behave: the former sets POLLHUP and POLLIN and, after reading any residual data, only POLLHUP remains set. The latter signal the EOF condition by setting both flags thus requiring some extra checks to determine if the stream is "done". DragonFly workaround/hack for POLLHUP is no longer required. Closes #8969
1 parent 07852a4
Changed files (1)
lib/std/child_process.zig
@@ -203,16 +203,18 @@ pub const ChildProcess = struct {
         // of space an ArrayList will allocate grows exponentially.
         const bump_amt = 512;
 
-        // TODO https://github.com/ziglang/zig/issues/8724
-        // parent process does not receive POLLHUP events
-        const dragonfly_workaround = builtin.os.tag == .dragonfly;
+        const err_mask = os.POLLERR | os.POLLNVAL | os.POLLHUP;
 
         while (dead_fds < poll_fds.len) {
             const events = try os.poll(&poll_fds, std.math.maxInt(i32));
             if (events == 0) continue;
 
+            var remove_stdout = false;
+            var remove_stderr = false;
             // Try reading whatever is available before checking the error
             // conditions.
+            // It's still possible to read after a POLLHUP is received, always
+            // check if there's some data waiting to be read first.
             if (poll_fds[0].revents & os.POLLIN != 0) {
                 // stdout is ready.
                 const new_capacity = std.math.min(stdout.items.len + bump_amt, max_output_bytes);
@@ -222,9 +224,12 @@ pub const ChildProcess = struct {
                 const nread = try os.read(poll_fds[0].fd, buf);
                 stdout.items.len += nread;
 
-                // insert POLLHUP event because dragonfly fails to do so
-                if (dragonfly_workaround and nread == 0) poll_fds[0].revents |= os.POLLHUP;
+                // Remove the fd when the EOF condition is met.
+                remove_stdout = nread == 0;
+            } else {
+                remove_stdout = poll_fds[0].revents & err_mask != 0;
             }
+
             if (poll_fds[1].revents & os.POLLIN != 0) {
                 // stderr is ready.
                 const new_capacity = std.math.min(stderr.items.len + bump_amt, max_output_bytes);
@@ -234,16 +239,18 @@ pub const ChildProcess = struct {
                 const nread = try os.read(poll_fds[1].fd, buf);
                 stderr.items.len += nread;
 
-                // insert POLLHUP event because dragonfly fails to do so
-                if (dragonfly_workaround and nread == 0) poll_fds[1].revents |= os.POLLHUP;
+                // Remove the fd when the EOF condition is met.
+                remove_stderr = nread == 0;
+            } else {
+                remove_stderr = poll_fds[1].revents & err_mask != 0;
             }
 
             // Exclude the fds that signaled an error.
-            if (poll_fds[0].revents & (os.POLLERR | os.POLLNVAL | os.POLLHUP) != 0) {
+            if (remove_stdout) {
                 poll_fds[0].fd = -1;
                 dead_fds += 1;
             }
-            if (poll_fds[1].revents & (os.POLLERR | os.POLLNVAL | os.POLLHUP) != 0) {
+            if (remove_stderr) {
                 poll_fds[1].fd = -1;
                 dead_fds += 1;
             }
@@ -294,6 +301,10 @@ pub const ChildProcess = struct {
 
         var stdout = std.ArrayList(u8).init(args.allocator);
         var stderr = std.ArrayList(u8).init(args.allocator);
+        errdefer {
+            stdout.deinit();
+            stderr.deinit();
+        }
 
         try collectOutputPosix(child, &stdout, &stderr, args.max_output_bytes);