Skip to content

Commit 5364083

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 9da3aa5 commit 5364083

File tree

8 files changed

+133
-7
lines changed

8 files changed

+133
-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,130 @@
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+
public final class ProcessSet {
29+
30+
/// Array to hold the processes.
31+
private var processes: Set<Process> = []
32+
33+
/// Queue to mutate internal states of the process group.
34+
private let serialQueue = DispatchQueue(label: "org.swift.swiftpm.process-set")
35+
36+
/// If the process group was asked to cancel all active processes.
37+
private var cancelled = false
38+
39+
/// The timeout (in seconds) after which the processes should be killed if they don't respond to SIGINT.
40+
public let killTimeout: Double
41+
42+
/// Condition to block kill thread until timeout.
43+
private var killingCondition = Condition()
44+
45+
/// Boolean predicate for killing condition.
46+
private var shouldKill = false
47+
48+
/// Create a process set.
49+
public init(killTimeout: Double = 5) {
50+
self.killTimeout = killTimeout
51+
}
52+
53+
/// Add a process to the process set. This method will throw if the process set is terminated using the terminate()
54+
/// method.
55+
///
56+
/// Call remove() method to remove the process from set once it has terminated.
57+
///
58+
/// - Parameters:
59+
/// - process: The process to add.
60+
/// - Throws: ProcessGroupError
61+
public func add(_ process: TSCBasic.Process) throws {
62+
return try serialQueue.sync {
63+
guard !cancelled else {
64+
throw ProcessSetError.cancelled
65+
}
66+
self.processes.insert(process)
67+
}
68+
}
69+
70+
/// Terminate all the processes. This method blocks until all processes in the set are terminated.
71+
///
72+
/// A process set cannot be used once it has been asked to terminate.
73+
public func terminate() {
74+
// Mark a process set as cancelled.
75+
serialQueue.sync {
76+
cancelled = true
77+
}
78+
79+
// Interrupt all processes.
80+
signalAll(SIGINT)
81+
82+
// Create a thread that will kill all processes after a timeout.
83+
let thread = TSCBasic.Thread {
84+
// Compute the timeout date.
85+
let timeout = Date() + self.killTimeout
86+
// Block until we timeout or notification.
87+
self.killingCondition.whileLocked {
88+
while !self.shouldKill {
89+
// Block until timeout expires.
90+
let timeLimitReached = !self.killingCondition.wait(until: timeout)
91+
// Set should kill to true if time limit was reached.
92+
if timeLimitReached {
93+
self.shouldKill = true
94+
}
95+
}
96+
}
97+
// Send kill signal to all processes.
98+
#if os(Windows)
99+
self.signalAll(SIGTERM)
100+
#else
101+
self.signalAll(SIGKILL)
102+
#endif
103+
}
104+
105+
thread.start()
106+
107+
// Wait until all processes terminate and notify the kill thread
108+
// if everyone exited to avoid waiting till timeout.
109+
for process in self.processes {
110+
_ = try? process.waitUntilExit()
111+
}
112+
killingCondition.whileLocked {
113+
shouldKill = true
114+
killingCondition.signal()
115+
}
116+
117+
// Join the kill thread so we don't exit before everything terminates.
118+
thread.join()
119+
}
120+
121+
/// Sends signal to all processes in the set.
122+
private func signalAll(_ signal: Int32) {
123+
serialQueue.sync {
124+
// Signal all active processes.
125+
for process in self.processes {
126+
process.signal(signal)
127+
}
128+
}
129+
}
130+
}

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)