Skip to content

Commit 91d1a0b

Browse files
authored
Use a concurrent queue to wait for process completion (#450)
Calling blocking `waitpid` on Swift concurrency threads can cause freezes for `async` code when spawning too many processes with the existing `Process` API.
1 parent 35afcbf commit 91d1a0b

File tree

4 files changed

+16
-22
lines changed

4 files changed

+16
-22
lines changed

Sources/TSCBasic/CMakeLists.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ add_library(TSCBasic
3737
WritableByteStream.swift
3838
Path.swift
3939
PathShims.swift
40-
Process.swift
41-
ProcessEnv.swift
42-
ProcessSet.swift
40+
Process/Process.swift
41+
Process/ProcessEnv.swift
42+
Process/ProcessSet.swift
4343
RegEx.swift
4444
Result.swift
4545
SortedArray.swift

Sources/TSCBasic/Process.swift renamed to Sources/TSCBasic/Process/Process.swift

+12-18
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,15 @@ public struct ProcessResult: CustomStringConvertible, Sendable {
130130
}
131131
}
132132

133-
#if swift(<5.6)
134-
extension Process: UnsafeSendable {}
135-
#else
136133
extension Process: @unchecked Sendable {}
137-
#endif
134+
135+
extension DispatchQueue {
136+
// a shared concurrent queue for running concurrent asynchronous operations
137+
static let processConcurrent = DispatchQueue(
138+
label: "swift.org.swift-tsc.process.concurrent",
139+
attributes: .concurrent
140+
)
141+
}
138142

139143
/// Process allows spawning new subprocesses and working with them.
140144
///
@@ -829,25 +833,15 @@ public final class Process {
829833
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
830834
@discardableResult
831835
public func waitUntilExit() async throws -> ProcessResult {
832-
#if compiler(>=5.6)
833-
return try await withCheckedThrowingContinuation { continuation in
834-
waitUntilExit(continuation.resume(with:))
835-
}
836-
#else
837-
if #available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) {
838-
return try await withCheckedThrowingContinuation { continuation in
839-
waitUntilExit(continuation.resume(with:))
836+
try await withCheckedThrowingContinuation { continuation in
837+
DispatchQueue.processConcurrent.async {
838+
self.waitUntilExit(continuation.resume(with:))
840839
}
841-
} else {
842-
preconditionFailure("Unsupported with Swift 5.5 on this OS version")
843840
}
844-
#endif
845841
}
846842

847843
/// Blocks the calling process until the subprocess finishes execution.
848-
// #if compiler(>=5.8)
849-
// @available(*, noasync)
850-
// #endif
844+
@available(*, noasync)
851845
@discardableResult
852846
public func waitUntilExit() throws -> ProcessResult {
853847
let group = DispatchGroup()

Sources/TSCBasic/ProcessEnv.swift renamed to Sources/TSCBasic/Process/ProcessEnv.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import Foundation
1212
import TSCLibc
1313

14-
/// Provides functionality related a process's enviorment.
14+
/// Provides functionality related a process's environment.
1515
public enum ProcessEnv {
1616

1717
/// Returns a dictionary containing the current environment.

0 commit comments

Comments
 (0)