From 91d1a0b70e36d2557229af2655d5a041a02324a8 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 12 Dec 2023 18:53:27 +0000 Subject: [PATCH] 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. --- Sources/TSCBasic/CMakeLists.txt | 6 ++-- Sources/TSCBasic/{ => Process}/Process.swift | 30 ++++++++----------- .../TSCBasic/{ => Process}/ProcessEnv.swift | 2 +- .../TSCBasic/{ => Process}/ProcessSet.swift | 0 4 files changed, 16 insertions(+), 22 deletions(-) rename Sources/TSCBasic/{ => Process}/Process.swift (98%) rename Sources/TSCBasic/{ => Process}/ProcessEnv.swift (97%) rename Sources/TSCBasic/{ => Process}/ProcessSet.swift (100%) diff --git a/Sources/TSCBasic/CMakeLists.txt b/Sources/TSCBasic/CMakeLists.txt index 0f30dfff..a1f5bcc2 100644 --- a/Sources/TSCBasic/CMakeLists.txt +++ b/Sources/TSCBasic/CMakeLists.txt @@ -37,9 +37,9 @@ add_library(TSCBasic WritableByteStream.swift Path.swift PathShims.swift - Process.swift - ProcessEnv.swift - ProcessSet.swift + Process/Process.swift + Process/ProcessEnv.swift + Process/ProcessSet.swift RegEx.swift Result.swift SortedArray.swift diff --git a/Sources/TSCBasic/Process.swift b/Sources/TSCBasic/Process/Process.swift similarity index 98% rename from Sources/TSCBasic/Process.swift rename to Sources/TSCBasic/Process/Process.swift index 5a396823..12a144c0 100644 --- a/Sources/TSCBasic/Process.swift +++ b/Sources/TSCBasic/Process/Process.swift @@ -130,11 +130,15 @@ public struct ProcessResult: CustomStringConvertible, Sendable { } } -#if swift(<5.6) -extension Process: UnsafeSendable {} -#else extension Process: @unchecked Sendable {} -#endif + +extension DispatchQueue { + // a shared concurrent queue for running concurrent asynchronous operations + static let processConcurrent = DispatchQueue( + label: "swift.org.swift-tsc.process.concurrent", + attributes: .concurrent + ) +} /// Process allows spawning new subprocesses and working with them. /// @@ -829,25 +833,15 @@ public final class Process { @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) @discardableResult public func waitUntilExit() async throws -> ProcessResult { - #if compiler(>=5.6) - return try await withCheckedThrowingContinuation { continuation in - waitUntilExit(continuation.resume(with:)) - } - #else - if #available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) { - return try await withCheckedThrowingContinuation { continuation in - waitUntilExit(continuation.resume(with:)) + try await withCheckedThrowingContinuation { continuation in + DispatchQueue.processConcurrent.async { + self.waitUntilExit(continuation.resume(with:)) } - } else { - preconditionFailure("Unsupported with Swift 5.5 on this OS version") } - #endif } /// Blocks the calling process until the subprocess finishes execution. -// #if compiler(>=5.8) -// @available(*, noasync) -// #endif + @available(*, noasync) @discardableResult public func waitUntilExit() throws -> ProcessResult { let group = DispatchGroup() diff --git a/Sources/TSCBasic/ProcessEnv.swift b/Sources/TSCBasic/Process/ProcessEnv.swift similarity index 97% rename from Sources/TSCBasic/ProcessEnv.swift rename to Sources/TSCBasic/Process/ProcessEnv.swift index bf7e3c62..226406aa 100644 --- a/Sources/TSCBasic/ProcessEnv.swift +++ b/Sources/TSCBasic/Process/ProcessEnv.swift @@ -11,7 +11,7 @@ import Foundation import TSCLibc -/// Provides functionality related a process's enviorment. +/// Provides functionality related a process's environment. public enum ProcessEnv { /// Returns a dictionary containing the current environment. diff --git a/Sources/TSCBasic/ProcessSet.swift b/Sources/TSCBasic/Process/ProcessSet.swift similarity index 100% rename from Sources/TSCBasic/ProcessSet.swift rename to Sources/TSCBasic/Process/ProcessSet.swift