Skip to content

Commit e7fbf88

Browse files
committed
Co-authored-by: Saleem Abdulrasool <[email protected]>
windows fixes
1 parent a7a2d86 commit e7fbf88

File tree

1 file changed

+59
-42
lines changed

1 file changed

+59
-42
lines changed

Sources/TSCBasic/Process.swift

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ public final class Process: ObjectIdentifierProtocol {
181181
// process execution mutable state
182182
private enum State {
183183
case idle
184-
case readingOutput(stdout: Thread, stderr: Thread?)
184+
case readingOutputThread(stdout: Thread, stderr: Thread?)
185+
case readingOutputPipe(sync: DispatchGroup)
185186
case outputReady(stdout: Result<[UInt8], Swift.Error>, stderr: Result<[UInt8], Swift.Error>)
186187
case complete(ProcessResult)
187188
}
@@ -232,7 +233,7 @@ public final class Process: ObjectIdentifierProtocol {
232233
private let stateLock = Lock()
233234

234235
/// The result of the process execution. Available after process is terminated.
235-
/// This will block while the process is running, as such equivalent to `waitUntilExit`
236+
/// This will block while the process is awaiting result
236237
@available(*, deprecated, message: "use waitUntilExit instead")
237238
public var result: ProcessResult? {
238239
return self.stateLock.withLock {
@@ -403,36 +404,44 @@ public final class Process: ObjectIdentifierProtocol {
403404
let stdinPipe = Pipe()
404405
_process?.standardInput = stdinPipe
405406

407+
let group = DispatchGroup()
408+
409+
var stdout: [UInt8] = []
410+
let stdoutLock = Lock()
411+
412+
var stderr: [UInt8] = []
413+
let stderrLock = Lock()
414+
406415
if outputRedirection.redirectsOutput {
407416
let stdoutPipe = Pipe()
408417
let stderrPipe = Pipe()
409418

410-
var pending: [UInt8]?
411-
let pendingLock = Lock()
412-
419+
group.enter()
413420
stdoutPipe.fileHandleForReading.readabilityHandler = { (fh : FileHandle) -> Void in
414-
let contents = fh.readDataToEndOfFile()
415-
self.outputRedirection.outputClosures?.stdoutClosure([UInt8](contents))
416-
pendingLock.withLock {
417-
if let stderr = pending {
418-
self.stateLock.withLock {
419-
self?.state = .outputReady(stdout: .success(contents), stderr: .success(stderr))
420-
}
421-
} else {
422-
pending = contents
421+
let data = fh.availableData
422+
if (data.count == 0) {
423+
stdoutPipe.fileHandleForReading.readabilityHandler = nil
424+
group.leave()
425+
} else {
426+
let contents = data.withUnsafeBytes { Array<UInt8>($0) }
427+
self.outputRedirection.outputClosures?.stdoutClosure(contents)
428+
stdoutLock.withLock {
429+
stdout += contents
423430
}
424431
}
425432
}
433+
434+
group.enter()
426435
stderrPipe.fileHandleForReading.readabilityHandler = { (fh : FileHandle) -> Void in
427-
let contents = fh.readDataToEndOfFile()
428-
self.outputRedirection.outputClosures?.stderrClosure([UInt8](contents))
429-
pendingLock.withLock {
430-
if let stdout = pending {
431-
self.stateLock.withLock {
432-
self?.state = .outputReady(stdout: .success(stdout), stderr: .success(contents))
433-
}
434-
} else {
435-
pending = contents
436+
let data = fh.availableData
437+
if (data.count == 0) {
438+
stderrPipe.fileHandleForReading.readabilityHandler = nil
439+
group.leave()
440+
} else {
441+
let contents = data.withUnsafeBytes { Array<UInt8>($0) }
442+
self.outputRedirection.outputClosures?.stderrClosure(contents)
443+
stderrLock.withLock {
444+
stderr += contents
436445
}
437446
}
438447
}
@@ -441,6 +450,20 @@ public final class Process: ObjectIdentifierProtocol {
441450
_process?.standardError = stderrPipe
442451
}
443452

453+
// first set state then start reading threads
454+
let sync = DispatchGroup()
455+
sync.enter()
456+
self.stateLock.withLock {
457+
self.state = .readingOutputPipe(sync: sync)
458+
}
459+
460+
group.notify(queue: .global()) {
461+
self.stateLock.withLock {
462+
self.state = .outputReady(stdout: .success(stdout), stderr: .success(stderr))
463+
}
464+
sync.leave()
465+
}
466+
444467
try _process?.run()
445468
return stdinPipe.fileHandleForWriting
446469
#else
@@ -640,7 +663,7 @@ public final class Process: ObjectIdentifierProtocol {
640663
}
641664
// first set state then start reading threads
642665
self.stateLock.withLock {
643-
self.state = .readingOutput(stdout: stdoutThread, stderr: stderrThread)
666+
self.state = .readingOutputThread(stdout: stdoutThread, stderr: stderrThread)
644667
}
645668
stdoutThread.start()
646669
stderrThread?.start()
@@ -653,22 +676,6 @@ public final class Process: ObjectIdentifierProtocol {
653676
/// Blocks the calling process until the subprocess finishes execution.
654677
@discardableResult
655678
public func waitUntilExit() throws -> ProcessResult {
656-
#if os(Windows)
657-
precondition(_process != nil, "The process is not yet launched.")
658-
let p = _process!
659-
p.waitUntilExit()
660-
stdout.thread?.join()
661-
stderr.thread?.join()
662-
663-
let executionResult = ProcessResult(
664-
arguments: arguments,
665-
environment: environment,
666-
exitStatusCode: p.terminationStatus,
667-
output: stdout.result,
668-
stderrOutput: stderr.result
669-
)
670-
return executionResult
671-
#else
672679
self.stateLock.lock()
673680
switch self.state {
674681
case .idle:
@@ -677,15 +684,25 @@ public final class Process: ObjectIdentifierProtocol {
677684
case .complete(let result):
678685
defer { self.stateLock.unlock() }
679686
return result
680-
case .readingOutput(let stdoutThread, let stderrThread):
687+
case .readingOutputThread(let stdoutThread, let stderrThread):
681688
self.stateLock.unlock() // unlock early since output read thread need to change state
682689
// If we're reading output, make sure that is finished.
683690
stdoutThread.join()
684691
stderrThread?.join()
685692
return try self.waitUntilExit()
693+
case .readingOutputPipe(let sync):
694+
self.stateLock.unlock() // unlock early since output read thread need to change state
695+
sync.wait()
696+
return try self.waitUntilExit()
686697
case .outputReady(let stdoutResult, let stderrResult):
687698
defer { self.stateLock.unlock() }
688699
// Wait until process finishes execution.
700+
#if os(Windows)
701+
precondition(_process != nil, "The process is not yet launched.")
702+
let p = _process!
703+
p.waitUntilExit()
704+
let exitStatusCode = p.terminationStatus
705+
#else
689706
var exitStatusCode: Int32 = 0
690707
var result = waitpid(processID, &exitStatusCode, 0)
691708
while result == -1 && errno == EINTR {
@@ -694,6 +711,7 @@ public final class Process: ObjectIdentifierProtocol {
694711
if result == -1 {
695712
throw SystemError.waitpid(errno)
696713
}
714+
#endif
697715

698716
// Construct the result.
699717
let executionResult = ProcessResult(
@@ -706,7 +724,6 @@ public final class Process: ObjectIdentifierProtocol {
706724
self.state = .complete(executionResult)
707725
return executionResult
708726
}
709-
#endif
710727
}
711728

712729
#if !os(Windows)

0 commit comments

Comments
 (0)