@@ -73,7 +73,7 @@ cwd: ?[]const u8,
73
73
/// Once that is done, `cwd` will be deprecated in favor of this field.
74
74
cwd_dir : ? fs.Dir = null ,
75
75
76
- err_pipe : ? if (native_os == .windows ) void else [ 2 ] posix .fd_t ,
76
+ err_pipe : if (native_os == .windows ) void else ? posix .fd_t ,
77
77
78
78
expand_arg0 : Arg0Expand ,
79
79
@@ -211,7 +211,7 @@ pub fn init(argv: []const []const u8, allocator: mem.Allocator) ChildProcess {
211
211
.argv = argv ,
212
212
.id = undefined ,
213
213
.thread_handle = undefined ,
214
- .err_pipe = null ,
214
+ .err_pipe = if ( native_os == .windows ) {} else null ,
215
215
.term = null ,
216
216
.env_map = null ,
217
217
.cwd = null ,
@@ -293,17 +293,49 @@ pub fn killPosix(self: *ChildProcess) !Term {
293
293
error .ProcessNotFound = > return error .AlreadyTerminated ,
294
294
else = > return err ,
295
295
};
296
- self .waitUnwrapped ();
296
+ self .waitUnwrappedPosix ();
297
297
return self .term .? ;
298
298
}
299
299
300
300
pub const WaitError = SpawnError || std .os .windows .GetProcessMemoryInfoError ;
301
301
302
+ /// On some targets, `spawn` may not report all spawn errors, such as `error.InvalidExe`.
303
+ /// This function will block until any spawn errors can be reported, and return them.
304
+ pub fn waitForSpawn (self : * ChildProcess ) SpawnError ! void {
305
+ if (native_os == .windows ) return ; // `spawn` reports everything
306
+ if (self .term ) | term | {
307
+ _ = term catch | spawn_err | return spawn_err ;
308
+ return ;
309
+ }
310
+
311
+ const err_pipe = self .err_pipe orelse return ;
312
+ self .err_pipe = null ;
313
+
314
+ // Wait for the child to report any errors in or before `execvpe`.
315
+ if (readIntFd (err_pipe )) | child_err_int | {
316
+ posix .close (err_pipe );
317
+ const child_err : SpawnError = @errorCast (@errorFromInt (child_err_int ));
318
+ self .term = child_err ;
319
+ return child_err ;
320
+ } else | _ | {
321
+ // Write end closed by CLOEXEC at the time of the `execvpe` call, indicating success!
322
+ posix .close (err_pipe );
323
+ }
324
+ }
325
+
302
326
/// Blocks until child process terminates and then cleans up all resources.
303
327
pub fn wait (self : * ChildProcess ) WaitError ! Term {
304
- const term = if (native_os == .windows ) try self .waitWindows () else self .waitPosix ();
328
+ try self .waitForSpawn (); // report spawn errors
329
+ if (self .term ) | term | {
330
+ self .cleanupStreams ();
331
+ return term ;
332
+ }
333
+ switch (native_os ) {
334
+ .windows = > try self .waitUnwrappedWindows (),
335
+ else = > self .waitUnwrappedPosix (),
336
+ }
305
337
self .id = undefined ;
306
- return term ;
338
+ return self . term .? ;
307
339
}
308
340
309
341
pub const RunResult = struct {
@@ -405,26 +437,6 @@ pub fn run(args: struct {
405
437
};
406
438
}
407
439
408
- fn waitWindows (self : * ChildProcess ) WaitError ! Term {
409
- if (self .term ) | term | {
410
- self .cleanupStreams ();
411
- return term ;
412
- }
413
-
414
- try self .waitUnwrappedWindows ();
415
- return self .term .? ;
416
- }
417
-
418
- fn waitPosix (self : * ChildProcess ) SpawnError ! Term {
419
- if (self .term ) | term | {
420
- self .cleanupStreams ();
421
- return term ;
422
- }
423
-
424
- self .waitUnwrapped ();
425
- return self .term .? ;
426
- }
427
-
428
440
fn waitUnwrappedWindows (self : * ChildProcess ) WaitError ! void {
429
441
const result = windows .WaitForSingleObjectEx (self .id , windows .INFINITE , false );
430
442
@@ -447,7 +459,7 @@ fn waitUnwrappedWindows(self: *ChildProcess) WaitError!void {
447
459
return result ;
448
460
}
449
461
450
- fn waitUnwrapped (self : * ChildProcess ) void {
462
+ fn waitUnwrappedPosix (self : * ChildProcess ) void {
451
463
const res : posix.WaitPidResult = res : {
452
464
if (self .request_resource_usage_statistics ) {
453
465
switch (native_os ) {
@@ -469,7 +481,7 @@ fn waitUnwrapped(self: *ChildProcess) void {
469
481
}
470
482
471
483
fn handleWaitResult (self : * ChildProcess , status : u32 ) void {
472
- self .term = self . cleanupAfterWait (status );
484
+ self .term = statusToTerm (status );
473
485
}
474
486
475
487
fn cleanupStreams (self : * ChildProcess ) void {
@@ -487,46 +499,6 @@ fn cleanupStreams(self: *ChildProcess) void {
487
499
}
488
500
}
489
501
490
- fn cleanupAfterWait (self : * ChildProcess , status : u32 ) ! Term {
491
- if (self .err_pipe ) | err_pipe | {
492
- defer destroyPipe (err_pipe );
493
-
494
- if (native_os == .linux ) {
495
- var fd = [1 ]posix.pollfd {posix.pollfd {
496
- .fd = err_pipe [0 ],
497
- .events = posix .POLL .IN ,
498
- .revents = undefined ,
499
- }};
500
-
501
- // Check if the eventfd buffer stores a non-zero value by polling
502
- // it, that's the error code returned by the child process.
503
- _ = posix .poll (& fd , 0 ) catch unreachable ;
504
-
505
- // According to eventfd(2) the descriptor is readable if the counter
506
- // has a value greater than 0
507
- if ((fd [0 ].revents & posix .POLL .IN ) != 0 ) {
508
- const err_int = try readIntFd (err_pipe [0 ]);
509
- return @as (SpawnError , @errorCast (@errorFromInt (err_int )));
510
- }
511
- } else {
512
- // Write maxInt(ErrInt) to the write end of the err_pipe. This is after
513
- // waitpid, so this write is guaranteed to be after the child
514
- // pid potentially wrote an error. This way we can do a blocking
515
- // read on the error pipe and either get maxInt(ErrInt) (no error) or
516
- // an error code.
517
- try writeIntFd (err_pipe [1 ], maxInt (ErrInt ));
518
- const err_int = try readIntFd (err_pipe [0 ]);
519
- // Here we potentially return the fork child's error from the parent
520
- // pid.
521
- if (err_int != maxInt (ErrInt )) {
522
- return @as (SpawnError , @errorCast (@errorFromInt (err_int )));
523
- }
524
- }
525
- }
526
-
527
- return statusToTerm (status );
528
- }
529
-
530
502
fn statusToTerm (status : u32 ) Term {
531
503
return if (posix .W .IFEXITED (status ))
532
504
Term { .Exited = posix .W .EXITSTATUS (status ) }
@@ -636,18 +608,9 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
636
608
}
637
609
};
638
610
639
- // This pipe is used to communicate errors between the time of fork
640
- // and execve from the child process to the parent process.
641
- const err_pipe = blk : {
642
- if (native_os == .linux ) {
643
- const fd = try posix .eventfd (0 , linux .EFD .CLOEXEC );
644
- // There's no distinction between the readable and the writeable
645
- // end with eventfd
646
- break :blk [2 ]posix.fd_t { fd , fd };
647
- } else {
648
- break :blk try posix .pipe2 (.{ .CLOEXEC = true });
649
- }
650
- };
611
+ // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
612
+ // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds.
613
+ const err_pipe : [2 ]posix.fd_t = try posix .pipe2 (.{ .CLOEXEC = true });
651
614
errdefer destroyPipe (err_pipe );
652
615
653
616
const pid_result = try posix .fork ();
@@ -687,6 +650,11 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
687
650
}
688
651
689
652
// we are the parent
653
+ errdefer comptime unreachable ; // The child is forked; we must not error from now on
654
+
655
+ posix .close (err_pipe [1 ]); // make sure only the child holds the write end open
656
+ self .err_pipe = err_pipe [0 ];
657
+
690
658
const pid : i32 = @intCast (pid_result );
691
659
if (self .stdin_behavior == .Pipe ) {
692
660
self .stdin = .{ .handle = stdin_pipe [1 ] };
@@ -705,7 +673,6 @@ fn spawnPosix(self: *ChildProcess) SpawnError!void {
705
673
}
706
674
707
675
self .id = pid ;
708
- self .err_pipe = err_pipe ;
709
676
self .term = null ;
710
677
711
678
if (self .stdin_behavior == .Pipe ) {
0 commit comments