Skip to content

Commit 13dc95b

Browse files
authored
Provide more context for close errors in Process (#434)
This will provide the arguments being used when we encounter such errors. rdar://117861543
1 parent 3b13e43 commit 13dc95b

File tree

1 file changed

+74
-65
lines changed

1 file changed

+74
-65
lines changed

Sources/TSCBasic/Process.swift

+74-65
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public struct ProcessResult: CustomStringConvertible, Sendable {
3232

3333
/// The process had a non zero exit.
3434
case nonZeroExit(ProcessResult)
35+
36+
/// The process failed with a `SystemError` (this is used to still provide context on the process that was launched).
37+
case systemError(arguments: [String], underlyingError: Swift.Error)
3538
}
3639

3740
public enum ExitStatus: Equatable, Sendable {
@@ -728,91 +731,95 @@ public final class Process {
728731
throw SystemError.posix_spawn(rv, arguments)
729732
}
730733

731-
// Close the local read end of the input pipe.
732-
try close(fd: stdinPipe[0])
733-
734-
let group = DispatchGroup()
735-
if !outputRedirection.redirectsOutput {
736-
// no stdout or stderr in this case
737-
self.stateLock.withLock {
738-
self.state = .outputReady(stdout: .success([]), stderr: .success([]))
739-
}
740-
} else {
741-
var pending: Result<[UInt8], Swift.Error>?
742-
let pendingLock = NSLock()
743-
744-
let outputClosures = outputRedirection.outputClosures
745-
746-
// Close the local write end of the output pipe.
747-
try close(fd: outputPipe[1])
748-
749-
// Create a thread and start reading the output on it.
750-
group.enter()
751-
let stdoutThread = Thread { [weak self] in
752-
if let readResult = self?.readOutput(onFD: outputPipe[0], outputClosure: outputClosures?.stdoutClosure) {
753-
pendingLock.withLock {
754-
if let stderrResult = pending {
755-
self?.stateLock.withLock {
756-
self?.state = .outputReady(stdout: readResult, stderr: stderrResult)
757-
}
758-
} else {
759-
pending = readResult
760-
}
761-
}
762-
group.leave()
763-
} else if let stderrResult = (pendingLock.withLock { pending }) {
764-
// TODO: this is more of an error
765-
self?.stateLock.withLock {
766-
self?.state = .outputReady(stdout: .success([]), stderr: stderrResult)
767-
}
768-
group.leave()
734+
do {
735+
// Close the local read end of the input pipe.
736+
try close(fd: stdinPipe[0])
737+
738+
let group = DispatchGroup()
739+
if !outputRedirection.redirectsOutput {
740+
// no stdout or stderr in this case
741+
self.stateLock.withLock {
742+
self.state = .outputReady(stdout: .success([]), stderr: .success([]))
769743
}
770-
}
744+
} else {
745+
var pending: Result<[UInt8], Swift.Error>?
746+
let pendingLock = NSLock()
771747

772-
// Only schedule a thread for stderr if no redirect was requested.
773-
var stderrThread: Thread? = nil
774-
if !outputRedirection.redirectStderr {
775-
// Close the local write end of the stderr pipe.
776-
try close(fd: stderrPipe[1])
748+
let outputClosures = outputRedirection.outputClosures
777749

778-
// Create a thread and start reading the stderr output on it.
750+
// Close the local write end of the output pipe.
751+
try close(fd: outputPipe[1])
752+
753+
// Create a thread and start reading the output on it.
779754
group.enter()
780-
stderrThread = Thread { [weak self] in
781-
if let readResult = self?.readOutput(onFD: stderrPipe[0], outputClosure: outputClosures?.stderrClosure) {
755+
let stdoutThread = Thread { [weak self] in
756+
if let readResult = self?.readOutput(onFD: outputPipe[0], outputClosure: outputClosures?.stdoutClosure) {
782757
pendingLock.withLock {
783-
if let stdoutResult = pending {
758+
if let stderrResult = pending {
784759
self?.stateLock.withLock {
785-
self?.state = .outputReady(stdout: stdoutResult, stderr: readResult)
760+
self?.state = .outputReady(stdout: readResult, stderr: stderrResult)
786761
}
787-
} else {
762+
} else {
788763
pending = readResult
789764
}
790765
}
791766
group.leave()
792-
} else if let stdoutResult = (pendingLock.withLock { pending }) {
767+
} else if let stderrResult = (pendingLock.withLock { pending }) {
793768
// TODO: this is more of an error
794769
self?.stateLock.withLock {
795-
self?.state = .outputReady(stdout: stdoutResult, stderr: .success([]))
770+
self?.state = .outputReady(stdout: .success([]), stderr: stderrResult)
796771
}
797772
group.leave()
798773
}
799774
}
800-
} else {
801-
pendingLock.withLock {
802-
pending = .success([]) // no stderr in this case
775+
776+
// Only schedule a thread for stderr if no redirect was requested.
777+
var stderrThread: Thread? = nil
778+
if !outputRedirection.redirectStderr {
779+
// Close the local write end of the stderr pipe.
780+
try close(fd: stderrPipe[1])
781+
782+
// Create a thread and start reading the stderr output on it.
783+
group.enter()
784+
stderrThread = Thread { [weak self] in
785+
if let readResult = self?.readOutput(onFD: stderrPipe[0], outputClosure: outputClosures?.stderrClosure) {
786+
pendingLock.withLock {
787+
if let stdoutResult = pending {
788+
self?.stateLock.withLock {
789+
self?.state = .outputReady(stdout: stdoutResult, stderr: readResult)
790+
}
791+
} else {
792+
pending = readResult
793+
}
794+
}
795+
group.leave()
796+
} else if let stdoutResult = (pendingLock.withLock { pending }) {
797+
// TODO: this is more of an error
798+
self?.stateLock.withLock {
799+
self?.state = .outputReady(stdout: stdoutResult, stderr: .success([]))
800+
}
801+
group.leave()
802+
}
803+
}
804+
} else {
805+
pendingLock.withLock {
806+
pending = .success([]) // no stderr in this case
807+
}
803808
}
809+
810+
// first set state then start reading threads
811+
self.stateLock.withLock {
812+
self.state = .readingOutput(sync: group)
813+
}
814+
815+
stdoutThread.start()
816+
stderrThread?.start()
804817
}
805-
806-
// first set state then start reading threads
807-
self.stateLock.withLock {
808-
self.state = .readingOutput(sync: group)
809-
}
810-
811-
stdoutThread.start()
812-
stderrThread?.start()
813-
}
814818

815-
return stdinStream
819+
return stdinStream
820+
} catch {
821+
throw ProcessResult.Error.systemError(arguments: arguments, underlyingError: error)
822+
}
816823
#else
817824
preconditionFailure("Process spawning is not available")
818825
#endif // POSIX implementation
@@ -1273,6 +1280,8 @@ extension Process.Error: CustomNSError {
12731280
extension ProcessResult.Error: CustomStringConvertible {
12741281
public var description: String {
12751282
switch self {
1283+
case .systemError(let arguments, let underlyingError):
1284+
return "error while executing `\(arguments.joined(separator: " "))`: \(underlyingError)"
12761285
case .illegalUTF8Sequence:
12771286
return "illegal UTF8 sequence output"
12781287
case .nonZeroExit(let result):

0 commit comments

Comments
 (0)