Skip to content

Conversation

jakepetroules
Copy link
Collaborator

This fully resolves working directory thread safety issues with subprocess spawning across all platforms. For now, subprocess is adopted conditionally in order to continue building in certain environments where the Subprocess module may not be available, in which case we fall back to Foundation Process.

Closes #441

(migrated from #584 in accordance with our new policy to require PRs from forks)

@jakepetroules
Copy link
Collaborator Author

@swift-ci test

@jakepetroules jakepetroules force-pushed the eng/PR-swift-subprocess branch from 7e290ca to 5d1747c Compare July 5, 2025 00:01
@jakepetroules
Copy link
Collaborator Author

@swift-ci test

@jakepetroules jakepetroules requested a review from compnerd July 5, 2025 00:10
platformOptions.teardownSequence = [.gracefulShutDown(allowedDurationToNextStep: .seconds(5))]
#if os(macOS)
if highPriority {
platformOptions.qualityOfService = .userInitiated
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@compnerd Would it seem reasonable to call SetPriorityClass with HIGH_PRIORITY_CLASS for Windows (REALTIME_PRIORITY_CLASS definitely sounded excessive)? Currently this is used to elevate the build service process so that it can serve high-priority requests more efficiently (e.g. with Swift Concurrency task priority set higher than normal).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we should try it - it can saturate the system to the point of being unusable though, so I am a little concerned about that.

@jakepetroules
Copy link
Collaborator Author

@swift-ci test

@jakepetroules jakepetroules force-pushed the eng/PR-swift-subprocess branch 2 times, most recently from 922c246 to bd4c32d Compare September 1, 2025 08:13
platformOptions.teardownSequence = [.gracefulShutDown(allowedDurationToNextStep: .seconds(5))]
}
let result = try await Subprocess.run(.path(FilePath(url.filePath.str)), arguments: .init(arguments), environment: environment.map { .custom(.init($0)) } ?? .inherit, workingDirectory: (currentDirectoryURL?.filePath.str).map { FilePath($0) } ?? nil, platformOptions: platformOptions, body: { execution, inputWriter, outputReader, errorReader in
try await inputWriter.finish()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potential deadlock here. You can't assume that you can fully unload the stdin w/o reading stdout/err

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no deadlock here, all this is doing is closing a file descriptor. We have no input. That said, I'll move it below the output reading just to be correct.

}
let result = try await Subprocess.run(.path(FilePath(url.filePath.str)), arguments: .init(arguments), environment: environment.map { .custom(.init($0)) } ?? .inherit, workingDirectory: (currentDirectoryURL?.filePath.str).map { FilePath($0) } ?? nil, platformOptions: platformOptions, body: { execution, inputWriter, outputReader, errorReader in
try await inputWriter.finish()
async let stdoutBytes = outputReader.collect().flatMap { $0.withUnsafeBytes(Array.init) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two *Unsafe*s here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so? we can't use Span yet, this still has to build with 6.1

let result = try await Subprocess.run(.path(FilePath(url.filePath.str)), arguments: .init(arguments), environment: environment.map { .custom(.init($0)) } ?? .inherit, workingDirectory: (currentDirectoryURL?.filePath.str).map { FilePath($0) } ?? nil, platformOptions: platformOptions, body: { execution, inputWriter, outputReader, errorReader in
try await inputWriter.finish()
async let stdoutBytes = outputReader.collect().flatMap { $0.withUnsafeBytes(Array.init) }
async let stderrBytes = errorReader.collect().flatMap { $0.withUnsafeBytes(Array.init) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

collect will load all of this potentially infinite stream into memory and I don't even see a limit, seems dangerous. If anything, limit it to something high like 32 MB and throw if we're blowing through that.

This fully resolves working directory thread safety issues with subprocess spawning across all platforms. For now, subprocess is adopted conditionally in order to continue building in certain environments where the Subprocess module may not be available, in which case we fall back to Foundation Process.

Closes swiftlang#441
@jakepetroules jakepetroules force-pushed the eng/PR-swift-subprocess branch from 5cd9b6f to ec02157 Compare September 15, 2025 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Amazon Linux 2 Support for Swift Build
4 participants