Skip to content

Adopt ~Copyable in Subprocess #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 17, 2025

Conversation

iCharlesHu
Copy link
Contributor

@iCharlesHu iCharlesHu commented May 7, 2025

This PR introduces ~Copyable into Subprocess, including CreatedPipe, TrackedFileDescriptor, and TrackedDispatchIO.

Resolves #37
Resolves #2

Additionally, we opted to slightly modify the API by moving the .standardOutput and .standardError AsyncSequence from Execution to the closure parameter, alongside Execution. This adjustment eliminated the necessity for Atomic (and AtomicBox) within Execution.

Furthermore, this change rendered SequenceOutput internal, as we now rely on overloads to infer the output and error types.


This patch also drops support for Swift 6.0 because it can't properly support the ~Copyable work. Now Swift 6.1 is the minimal required Swift version to build Subprocess

// Input
var result: Int32 = -1
if let inputRead = inputPipe.readFileDescriptor {
result = posix_spawn_file_actions_adddup2(&fileActions, inputRead.platformDescriptor, 0)
if inputReadFileDescriptor != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest using if let inputReadFileDescriptor { so you don't need the force unwrap below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately this pattern, along with if let a = a is not compatible with ~Copyable types. Writing if let inputReadFileDescriptor would have consumed inputReadFileDescriptor so the line below would be double consume.

@iCharlesHu iCharlesHu force-pushed the charles/noncopyable-execution branch from f126559 to 2d28695 Compare May 14, 2025 21:56
@iCharlesHu iCharlesHu force-pushed the charles/noncopyable-execution branch from 2d28695 to 7c35edb Compare May 14, 2025 21:59
@iCharlesHu
Copy link
Contributor Author

Addressed review comments and removed Swift 6.0 support.

@iCharlesHu iCharlesHu merged commit 0f9a610 into swiftlang:main May 17, 2025
20 checks passed
@iCharlesHu iCharlesHu deleted the charles/noncopyable-execution branch May 17, 2025 01:46
cleanupHandler: { error in
// Close the file descriptor
if self.closeWhenDone {
try? self.safelyClose()
Copy link

Choose a reason for hiding this comment

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

why ignore the error? That can't be quite right

"FileDescriptor \(fileDescriptor.rawValue) is already closed"
)
}
// Otherwise ignore the error
Copy link

Choose a reason for hiding this comment

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

this is a bad idea. Closing in deinit for file descriptors is bad enough (close may block too depending on the fd) but ignoring the errors isn't a thing we can do.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @weissi here FDs cannot be modeled with ~Copyable types at this point both due the async close nature and the errors on close. My recommendation is to use with-style methods which allow to solve both issues.

Copy link

Choose a reason for hiding this comment

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

Yup, agree with Franz. with* solves those issues correctly and also clearly for a reader of the code.

Copy link
Contributor

Choose a reason for hiding this comment

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

@weissi can you open up issues for your comments? This PR is merged and discussions here might be lost.

}
}

internal var platformDescriptor: PlatformFileDescriptor {
deinit {
guard self.closeWhenDone else {
Copy link

Choose a reason for hiding this comment

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

This looks like we're hiding some lifecycle bugs here, no? We should know if we closed a file descriptor or not, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

close _ignores_ EBADF (.badFileDescriptor) which is super dangerous, needs to preconditionFailure this Making Execution ~Copyable instead of class
6 participants