Skip to content

Conditionally adopt swift-subprocess - #638

Draft
jakepetroules wants to merge 4 commits into
swiftlang:mainfrom
jakepetroules:eng/PR-swift-subprocess
Draft

jakepetroules wants to merge 4 commits into
swiftlang:mainfrom
jakepetroules:eng/PR-swift-subprocess

Conversation

@jakepetroules

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Collaborator Author

@swift-ci test

@jakepetroules
jakepetroules requested a review from compnerd July 5, 2025 00:10
Comment thread Sources/SWBUtil/ProcessController.swift Outdated
platformOptions.teardownSequence = [.gracefulShutDown(allowedDurationToNextStep: .seconds(5))]
#if os(macOS)
if highPriority {
platformOptions.qualityOfService = .userInitiated

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Comment thread Sources/SWBUtil/Process.swift Outdated
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
Copy Markdown

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
Copy Markdown
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.

Comment thread Sources/SWBUtil/Process.swift Outdated
}
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
Copy Markdown

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
Copy Markdown
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

Comment thread Sources/SWBUtil/Process.swift Outdated
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
Copy Markdown

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is existing behavior; we can address it separately.

@jakepetroules

Copy link
Copy Markdown
Collaborator Author

@swift-ci test

@jakepetroules

Copy link
Copy Markdown
Collaborator Author

@swift-ci test

@jakepetroules
jakepetroules marked this pull request as draft March 14, 2026 08:28
@jakepetroules

Copy link
Copy Markdown
Collaborator Author

Moving to draft -- SwiftSubprocess is on its way to 1.0 with some API changes, so we might as well hold off until then.

@jakepetroules
jakepetroules force-pushed the eng/PR-swift-subprocess branch from ceaf055 to bed300e Compare April 3, 2026 08:53
@jakepetroules

Copy link
Copy Markdown
Collaborator Author

@swift-ci test

@jakepetroules
jakepetroules force-pushed the eng/PR-swift-subprocess branch from bed300e to 4f3de6e Compare April 3, 2026 18:25
@jakepetroules

Copy link
Copy Markdown
Collaborator Author

@swift-ci test

@jakepetroules

Copy link
Copy Markdown
Collaborator Author

Please test with following PRs:
swiftlang/swift-subprocess#376

@swift-ci please build toolchain Linux platform

@jakepetroules

Copy link
Copy Markdown
Collaborator Author

Please test with following PRs:
swiftlang/swift-subprocess#376

@swift-ci please build toolchain macOS platform

@jakepetroules

Copy link
Copy Markdown
Collaborator Author

Please test with following PRs:
swiftlang/swift-subprocess#376

@swift-ci please build toolchain Windows platform

@jakepetroules

Copy link
Copy Markdown
Collaborator Author

Now waiting on a new tag of swift-subprocess with the recent CMake changes, and for swiftlang/swift to adopt that in update-checkout.

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
- Require swift-subprocess 1.0.1 and adapt to its API (Configuration(executable:),
  the Execution-based run overload, closureResult).
- Use .combinedWithOutput for merged output instead of a manually shared pipe.
- Collect output without relying on the SubprocessFoundation trait, which isn't
  enabled in CMake builds of swift-subprocess.
- Map swift-subprocess launch failures to RunProcessLaunchError, matching the
  Foundation.Process code path.
- Restore the @test attribute on ProcessTests.workingDirectory.
- Build swift-system before swift-subprocess in the CMake smoke test so that
  swift-subprocess doesn't fall back to fetching swift-system from the network.
Optionally find SwiftSubprocess and link it into SWBUtil, so CMake builds
(including the toolchain) use swift-subprocess rather than Foundation.Process.
SwiftBuildConfig.cmake loads SwiftSubprocess for clients when it was used, so
that the exported SWBUtil target's link interface resolves.
@jakepetroules
jakepetroules force-pushed the eng/PR-swift-subprocess branch from b7a5d07 to a92d36c Compare October 15, 2026 00:05
if("@SwiftSubprocess_FOUND@" AND NOT TARGET SwiftSubprocess::Subprocess)
find_package(SwiftSubprocess CONFIG REQUIRED
NO_DEFAULT_PATH PATHS "@SwiftSubprocess_DIR@")
endif()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we avoid this and use CMakeFindDependencyMacro instead? That is meant to propagate the dependency across the boundary to the consumers without them having to do find_package and pass along the variables. It isn't perfect because it might lead to under-specifying the dependencies, but I think that it is still a good option.

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