@@ -180,10 +180,10 @@ public final class Process {
180
180
// process execution mutable state
181
181
private enum State {
182
182
case idle
183
- case readingOutputThread( stdout: Thread , stderr: Thread ? )
184
- case readingOutputPipe( sync: DispatchGroup )
183
+ case readingOutput( sync: DispatchGroup )
185
184
case outputReady( stdout: Result < [ UInt8 ] , Swift . Error > , stderr: Result < [ UInt8 ] , Swift . Error > )
186
185
case complete( ProcessResult )
186
+ case failed( Swift . Error )
187
187
}
188
188
189
189
/// Typealias for process id type.
@@ -230,6 +230,8 @@ public final class Process {
230
230
// process execution mutable state
231
231
private var state : State = . idle
232
232
private let stateLock = Lock ( )
233
+ private static let sharedCompletionQueue = DispatchQueue ( label: " org.swift.tools-support-core.process-completion " )
234
+ private var completionQueue = Process . sharedCompletionQueue
233
235
234
236
/// The result of the process execution. Available after process is terminated.
235
237
/// This will block while the process is awaiting result
@@ -395,13 +397,14 @@ public final class Process {
395
397
}
396
398
397
399
#if os(Windows)
398
- _process = Foundation . Process ( )
399
- _process? . arguments = Array ( arguments. dropFirst ( ) ) // Avoid including the executable URL twice.
400
- _process? . executableURL = executablePath. asURL
401
- _process? . environment = environment
400
+ let process = Foundation . Process ( )
401
+ _process = process
402
+ process. arguments = Array ( arguments. dropFirst ( ) ) // Avoid including the executable URL twice.
403
+ process. executableURL = executablePath. asURL
404
+ process. environment = environment
402
405
403
406
let stdinPipe = Pipe ( )
404
- _process ? . standardInput = stdinPipe
407
+ process . standardInput = stdinPipe
405
408
406
409
let group = DispatchGroup ( )
407
410
@@ -445,25 +448,25 @@ public final class Process {
445
448
}
446
449
}
447
450
448
- _process ? . standardOutput = stdoutPipe
449
- _process ? . standardError = stderrPipe
451
+ process . standardOutput = stdoutPipe
452
+ process . standardError = stderrPipe
450
453
}
451
454
452
455
// first set state then start reading threads
453
456
let sync = DispatchGroup ( )
454
457
sync. enter ( )
455
458
self . stateLock. withLock {
456
- self . state = . readingOutputPipe ( sync: sync)
459
+ self . state = . readingOutput ( sync: sync)
457
460
}
458
461
459
- group. notify ( queue: . global ( ) ) {
462
+ group. notify ( queue: self . completionQueue ) {
460
463
self . stateLock. withLock {
461
464
self . state = . outputReady( stdout: . success( stdout) , stderr: . success( stderr) )
462
465
}
463
466
sync. leave ( )
464
467
}
465
468
466
- try _process ? . run ( )
469
+ try process . run ( )
467
470
return stdinPipe. fileHandleForWriting
468
471
#elseif (!canImport(Darwin) || os(macOS))
469
472
// Initialize the spawn attributes.
@@ -596,6 +599,7 @@ public final class Process {
596
599
// Close the local read end of the input pipe.
597
600
try close ( fd: stdinPipe [ 0 ] )
598
601
602
+ let group = DispatchGroup ( )
599
603
if !outputRedirection. redirectsOutput {
600
604
// no stdout or stderr in this case
601
605
self . stateLock. withLock {
@@ -611,6 +615,7 @@ public final class Process {
611
615
try close ( fd: outputPipe [ 1 ] )
612
616
613
617
// Create a thread and start reading the output on it.
618
+ group. enter ( )
614
619
let stdoutThread = Thread { [ weak self] in
615
620
if let readResult = self ? . readOutput ( onFD: outputPipe [ 0 ] , outputClosure: outputClosures? . stdoutClosure) {
616
621
pendingLock. withLock {
@@ -622,11 +627,13 @@ public final class Process {
622
627
pending = readResult
623
628
}
624
629
}
630
+ group. leave ( )
625
631
} else if let stderrResult = ( pendingLock. withLock { pending } ) {
626
632
// TODO: this is more of an error
627
633
self ? . stateLock. withLock {
628
634
self ? . state = . outputReady( stdout: . success( [ ] ) , stderr: stderrResult)
629
635
}
636
+ group. leave ( )
630
637
}
631
638
}
632
639
@@ -637,6 +644,7 @@ public final class Process {
637
644
try close ( fd: stderrPipe [ 1 ] )
638
645
639
646
// Create a thread and start reading the stderr output on it.
647
+ group. enter ( )
640
648
stderrThread = Thread { [ weak self] in
641
649
if let readResult = self ? . readOutput ( onFD: stderrPipe [ 0 ] , outputClosure: outputClosures? . stderrClosure) {
642
650
pendingLock. withLock {
@@ -648,22 +656,26 @@ public final class Process {
648
656
pending = readResult
649
657
}
650
658
}
659
+ group. leave ( )
651
660
} else if let stdoutResult = ( pendingLock. withLock { pending } ) {
652
661
// TODO: this is more of an error
653
662
self ? . stateLock. withLock {
654
663
self ? . state = . outputReady( stdout: stdoutResult, stderr: . success( [ ] ) )
655
664
}
665
+ group. leave ( )
656
666
}
657
667
}
658
668
} else {
659
669
pendingLock. withLock {
660
670
pending = . success( [ ] ) // no stderr in this case
661
671
}
662
672
}
673
+
663
674
// first set state then start reading threads
664
675
self . stateLock. withLock {
665
- self . state = . readingOutputThread ( stdout : stdoutThread , stderr : stderrThread )
676
+ self . state = . readingOutput ( sync : group )
666
677
}
678
+
667
679
stdoutThread. start ( )
668
680
stderrThread? . start ( )
669
681
}
@@ -677,24 +689,35 @@ public final class Process {
677
689
/// Blocks the calling process until the subprocess finishes execution.
678
690
@discardableResult
679
691
public func waitUntilExit( ) throws -> ProcessResult {
692
+ let group = DispatchGroup ( )
693
+ group. enter ( )
694
+ var processResult : Result < ProcessResult , Swift . Error > ?
695
+ self . waitUntilExit ( ) { result in
696
+ processResult = result
697
+ group. leave ( )
698
+ }
699
+ group. wait ( )
700
+ return try processResult. unsafelyUnwrapped. get ( )
701
+ }
702
+
703
+ /// Executes the process I/O state machine, calling completion block when finished.
704
+ private func waitUntilExit( _ completion: @escaping ( Result < ProcessResult , Swift . Error > ) -> Void ) {
680
705
self . stateLock. lock ( )
681
706
switch self . state {
682
707
case . idle:
683
708
defer { self . stateLock. unlock ( ) }
684
709
preconditionFailure ( " The process is not yet launched. " )
685
710
case . complete( let result) :
686
- defer { self . stateLock. unlock ( ) }
687
- return result
688
- case . readingOutputThread( let stdoutThread, let stderrThread) :
689
- self . stateLock. unlock ( ) // unlock early since output read thread need to change state
690
- // If we're reading output, make sure that is finished.
691
- stdoutThread. join ( )
692
- stderrThread? . join ( )
693
- return try self . waitUntilExit ( )
694
- case . readingOutputPipe( let sync) :
695
- self . stateLock. unlock ( ) // unlock early since output read thread need to change state
696
- sync. wait ( )
697
- return try self . waitUntilExit ( )
711
+ self . stateLock. unlock ( )
712
+ completion ( . success( result) )
713
+ case . failed( let error) :
714
+ self . stateLock. unlock ( )
715
+ completion ( . failure( error) )
716
+ case . readingOutput( let sync) :
717
+ self . stateLock. unlock ( )
718
+ sync. notify ( queue: self . completionQueue) {
719
+ self . waitUntilExit ( completion)
720
+ }
698
721
case . outputReady( let stdoutResult, let stderrResult) :
699
722
defer { self . stateLock. unlock ( ) }
700
723
// Wait until process finishes execution.
@@ -710,7 +733,7 @@ public final class Process {
710
733
result = waitpid ( processID, & exitStatusCode, 0 )
711
734
}
712
735
if result == - 1 {
713
- throw SystemError . waitpid ( errno)
736
+ self . state = . failed ( SystemError . waitpid ( errno) )
714
737
}
715
738
#endif
716
739
@@ -723,7 +746,9 @@ public final class Process {
723
746
stderrOutput: stderrResult
724
747
)
725
748
self . state = . complete( executionResult)
726
- return executionResult
749
+ self . completionQueue. async {
750
+ self . waitUntilExit ( completion)
751
+ }
727
752
}
728
753
}
729
754
@@ -790,6 +815,25 @@ public final class Process {
790
815
}
791
816
792
817
extension Process {
818
+ /// Execute a subprocess and calls completion block when it finishes execution
819
+ ///
820
+ /// - Parameters:
821
+ /// - arguments: The arguments for the subprocess.
822
+ /// - environment: The environment to pass to subprocess. By default the current process environment
823
+ /// will be inherited.
824
+ /// - Returns: The process result.
825
+ static public func popen( arguments: [ String ] , environment: [ String : String ] = ProcessEnv . vars,
826
+ queue: DispatchQueue ? = nil , completion: @escaping ( Result < ProcessResult , Swift . Error > ) -> Void ) {
827
+ do {
828
+ let process = Process ( arguments: arguments, environment: environment, outputRedirection: . collect)
829
+ process. completionQueue = queue ?? Self . sharedCompletionQueue
830
+ try process. launch ( )
831
+ process. waitUntilExit ( completion)
832
+ } catch {
833
+ completion ( . failure( error) )
834
+ }
835
+ }
836
+
793
837
/// Execute a subprocess and block until it finishes execution
794
838
///
795
839
/// - Parameters:
0 commit comments