Skip to content

Commit c61b973

Browse files
committed
Migrate 'ProcessSet' implementation to the driver
This type is being deprecated and removed in 'swift-tools-support-core'. Move the implementation to the driver. Long term, the driver's executors should be re-architected to rely on structured concurrency primitives. In the meantime, absorb this functionality into the driver so that it can be freely removed from STSC.
1 parent 3459b44 commit c61b973

File tree

8 files changed

+134
-7
lines changed

8 files changed

+134
-7
lines changed

Sources/SwiftDriver/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_library(SwiftDriver
3333
Execution/DriverExecutor.swift
3434
Execution/ParsableOutput.swift
3535
Execution/ProcessProtocol.swift
36+
Execution/ProcessSet.swift
3637

3738
"IncrementalCompilation/Bitcode/Bitcode.swift"
3839
"IncrementalCompilation/Bitcode/BitcodeElement.swift"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
//===--------------- ProcessSet.swift - Swift Subprocesses ---------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import struct TSCBasic.Condition
14+
import class TSCBasic.Process
15+
import struct TSCBasic.ProcessResult
16+
import class TSCBasic.Thread
17+
import Dispatch
18+
import Foundation
19+
20+
public enum ProcessSetError: Swift.Error {
21+
/// The process group was cancelled and doesn't allow adding more processes.
22+
case cancelled
23+
}
24+
25+
/// A process set is a small wrapper for collection of processes.
26+
///
27+
/// This class is thread safe.
28+
// TODO: Use `TaskGroup` with async `Process` APIs instead
29+
public final class ProcessSet {
30+
31+
/// Array to hold the processes.
32+
private var processes: Set<Process> = []
33+
34+
/// Queue to mutate internal states of the process group.
35+
private let serialQueue = DispatchQueue(label: "org.swift.swiftpm.process-set")
36+
37+
/// If the process group was asked to cancel all active processes.
38+
private var cancelled = false
39+
40+
/// The timeout (in seconds) after which the processes should be killed if they don't respond to SIGINT.
41+
public let killTimeout: Double
42+
43+
/// Condition to block kill thread until timeout.
44+
private var killingCondition = Condition()
45+
46+
/// Boolean predicate for killing condition.
47+
private var shouldKill = false
48+
49+
/// Create a process set.
50+
public init(killTimeout: Double = 5) {
51+
self.killTimeout = killTimeout
52+
}
53+
54+
/// Add a process to the process set. This method will throw if the process set is terminated using the terminate()
55+
/// method.
56+
///
57+
/// Call remove() method to remove the process from set once it has terminated.
58+
///
59+
/// - Parameters:
60+
/// - process: The process to add.
61+
/// - Throws: ProcessGroupError
62+
public func add(_ process: TSCBasic.Process) throws {
63+
return try serialQueue.sync {
64+
guard !cancelled else {
65+
throw ProcessSetError.cancelled
66+
}
67+
self.processes.insert(process)
68+
}
69+
}
70+
71+
/// Terminate all the processes. This method blocks until all processes in the set are terminated.
72+
///
73+
/// A process set cannot be used once it has been asked to terminate.
74+
public func terminate() {
75+
// Mark a process set as cancelled.
76+
serialQueue.sync {
77+
cancelled = true
78+
}
79+
80+
// Interrupt all processes.
81+
signalAll(SIGINT)
82+
83+
// Create a thread that will kill all processes after a timeout.
84+
let thread = TSCBasic.Thread {
85+
// Compute the timeout date.
86+
let timeout = Date() + self.killTimeout
87+
// Block until we timeout or notification.
88+
self.killingCondition.whileLocked {
89+
while !self.shouldKill {
90+
// Block until timeout expires.
91+
let timeLimitReached = !self.killingCondition.wait(until: timeout)
92+
// Set should kill to true if time limit was reached.
93+
if timeLimitReached {
94+
self.shouldKill = true
95+
}
96+
}
97+
}
98+
// Send kill signal to all processes.
99+
#if os(Windows)
100+
self.signalAll(SIGTERM)
101+
#else
102+
self.signalAll(SIGKILL)
103+
#endif
104+
}
105+
106+
thread.start()
107+
108+
// Wait until all processes terminate and notify the kill thread
109+
// if everyone exited to avoid waiting till timeout.
110+
for process in self.processes {
111+
_ = try? process.waitUntilExit()
112+
}
113+
killingCondition.whileLocked {
114+
shouldKill = true
115+
killingCondition.signal()
116+
}
117+
118+
// Join the kill thread so we don't exit before everything terminates.
119+
thread.join()
120+
}
121+
122+
/// Sends signal to all processes in the set.
123+
private func signalAll(_ signal: Int32) {
124+
serialQueue.sync {
125+
// Signal all active processes.
126+
for process in self.processes {
127+
process.signal(signal)
128+
}
129+
}
130+
}
131+
}

Sources/SwiftDriver/SwiftScan/Loader.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
//===------------------------ SwiftScan.swift -----------------------------===//
1+
//===------------------------ Loader.swift -------------------------------===//
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information

Sources/SwiftDriver/ToolingInterface/ToolingUtil.swift

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
import class TSCBasic.DiagnosticsEngine
1414
import struct TSCBasic.Diagnostic
15-
import class TSCBasic.ProcessSet
1615
import enum TSCBasic.ProcessEnv
1716
import typealias TSCBasic.ProcessEnvironmentBlock
1817
import var TSCBasic.localFileSystem

Sources/SwiftDriverExecution/MultiJobExecutor.swift

-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import var Foundation.SIGINT
2121

2222
import class TSCBasic.DiagnosticsEngine
2323
import class TSCBasic.Process
24-
import class TSCBasic.ProcessSet
2524
import protocol TSCBasic.DiagnosticData
2625
import protocol TSCBasic.FileSystem
2726
import struct TSCBasic.Diagnostic

Sources/SwiftDriverExecution/SwiftDriverExecutor.swift

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import class Foundation.FileHandle
1515

1616
import class TSCBasic.DiagnosticsEngine
1717
import class TSCBasic.Process
18-
import class TSCBasic.ProcessSet
1918
import enum TSCBasic.ProcessEnv
2019
import func TSCBasic.exec
2120
import protocol TSCBasic.FileSystem

Sources/swift-build-sdk-interfaces/main.swift

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import Bionic
2424
#endif
2525

2626
import class TSCBasic.DiagnosticsEngine
27-
import class TSCBasic.ProcessSet
2827
import enum TSCBasic.ProcessEnv
2928
import func TSCBasic.withTemporaryFile
3029
import struct TSCBasic.AbsolutePath

Sources/swift-driver/main.swift

-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import func TSCBasic.exec
3636
import enum TSCBasic.ProcessEnv
3737
import class TSCBasic.DiagnosticsEngine
3838
import class TSCBasic.Process
39-
import class TSCBasic.ProcessSet
4039
import func TSCBasic.resolveSymlinks
4140
import protocol TSCBasic.DiagnosticData
4241
import var TSCBasic.localFileSystem

0 commit comments

Comments
 (0)